2026-02-26 00:17:32 +03:30
|
|
|
/**
|
|
|
|
|
* Crop Zoning API
|
|
|
|
|
* @see CROP_ZONING_APIS.md
|
|
|
|
|
*/
|
|
|
|
|
|
2026-04-01 17:28:05 +03:30
|
|
|
import type { Feature, FeatureCollection, Polygon } from "geojson";
|
|
|
|
|
import { apiClient } from "../client";
|
|
|
|
|
import type {
|
|
|
|
|
RecommendationTaskInitResponse,
|
|
|
|
|
RecommendationTaskStatus,
|
|
|
|
|
RecommendationTaskStatusResponse,
|
|
|
|
|
} from "./recommendationTask";
|
|
|
|
|
import { normalizeRecommendationTaskStatus } from "./recommendationTask";
|
2026-02-26 00:17:32 +03:30
|
|
|
|
2026-04-01 17:28:05 +03:30
|
|
|
const PREFIX = "/api/crop-zoning";
|
2026-02-26 00:17:32 +03:30
|
|
|
|
|
|
|
|
export interface Product {
|
2026-04-01 17:28:05 +03:30
|
|
|
id: string;
|
|
|
|
|
label: string;
|
|
|
|
|
color: string;
|
2026-02-26 00:17:32 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ZoneInitialData {
|
2026-04-01 17:28:05 +03:30
|
|
|
zoneId: string;
|
|
|
|
|
geometry: Polygon;
|
2026-02-26 00:37:00 +03:30
|
|
|
/** اگر null/خالی/uncultivable باشد، زون غیرقابل کشت و خاکستری نمایش داده میشود */
|
2026-04-01 17:28:05 +03:30
|
|
|
crop?: string | null;
|
|
|
|
|
matchPercent?: number | null;
|
|
|
|
|
waterNeed?: string | null;
|
|
|
|
|
estimatedProfit?: string | null;
|
2026-02-26 00:17:32 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface ZonesInitialResponse {
|
2026-04-01 17:28:05 +03:30
|
|
|
total_area_hectares: number;
|
|
|
|
|
total_area_sqm: number;
|
|
|
|
|
zone_count: number;
|
|
|
|
|
zones: ZoneInitialData[];
|
2026-02-26 00:17:32 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface AreaResponse {
|
2026-04-01 17:28:05 +03:30
|
|
|
area: Feature<Polygon> | null;
|
2026-02-26 00:17:32 +03:30
|
|
|
}
|
|
|
|
|
|
2026-04-01 17:28:05 +03:30
|
|
|
export interface CropZoningAreaTask {
|
|
|
|
|
status?:
|
|
|
|
|
| "IDLE"
|
|
|
|
|
| "PENDING"
|
|
|
|
|
| "PROCESSING"
|
|
|
|
|
| "SUCCESS"
|
|
|
|
|
| "FAILURE"
|
|
|
|
|
| "pending"
|
|
|
|
|
| "processing"
|
|
|
|
|
| "success"
|
|
|
|
|
| "completed"
|
|
|
|
|
| "failure"
|
|
|
|
|
| "failed";
|
|
|
|
|
stage?: string;
|
|
|
|
|
stage_label?: string;
|
|
|
|
|
area_uuid?: string;
|
|
|
|
|
total_zones?: number;
|
|
|
|
|
completed_zones?: number;
|
|
|
|
|
processing_zones?: number;
|
|
|
|
|
pending_zones?: number;
|
|
|
|
|
failed_zones?: number;
|
|
|
|
|
remaining_zones?: number;
|
|
|
|
|
progress_percent?: number;
|
|
|
|
|
message?: string;
|
|
|
|
|
failed_zone_errors?: string[];
|
|
|
|
|
cell_side_km?: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface CropZoningAreaResult extends AreaResponse {
|
|
|
|
|
status?: string;
|
|
|
|
|
task?: CropZoningAreaTask | null;
|
|
|
|
|
zones?: ZoneInitialData[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type CropZoningAreaResponse =
|
|
|
|
|
| CropZoningAreaResult
|
|
|
|
|
| RecommendationTaskInitResponse;
|
|
|
|
|
|
2026-02-26 00:17:32 +03:30
|
|
|
export interface ZoneDetailData {
|
2026-04-01 17:28:05 +03:30
|
|
|
zoneId: string;
|
|
|
|
|
crop: string;
|
|
|
|
|
matchPercent: number;
|
|
|
|
|
waterNeed: string;
|
|
|
|
|
estimatedProfit: string;
|
|
|
|
|
reason: string;
|
|
|
|
|
criteria: { name: string; value: number }[];
|
|
|
|
|
area_hectares?: number;
|
2026-02-26 00:17:32 +03:30
|
|
|
}
|
|
|
|
|
|
2026-02-26 00:37:00 +03:30
|
|
|
/** دیتای نیاز آبی هر زون — لایهٔ نیاز آبی */
|
|
|
|
|
export interface ZoneWaterNeedData {
|
2026-04-01 17:28:05 +03:30
|
|
|
zoneId: string;
|
|
|
|
|
geometry: Polygon;
|
|
|
|
|
level: "low" | "medium" | "high";
|
|
|
|
|
value?: string;
|
|
|
|
|
color: string;
|
2026-02-26 00:37:00 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** دیتای کیفیت خاک هر زون — لایهٔ کیفیت خاک */
|
|
|
|
|
export interface ZoneSoilQualityData {
|
2026-04-01 17:28:05 +03:30
|
|
|
zoneId: string;
|
|
|
|
|
geometry: Polygon;
|
|
|
|
|
level: "low" | "medium" | "high";
|
|
|
|
|
score?: number;
|
|
|
|
|
color: string;
|
2026-02-26 00:37:00 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** دیتای ریسک کشت هر زون — لایهٔ ریسک کشت */
|
|
|
|
|
export interface ZoneCultivationRiskData {
|
2026-04-01 17:28:05 +03:30
|
|
|
zoneId: string;
|
|
|
|
|
geometry: Polygon;
|
|
|
|
|
level: "low" | "medium" | "high";
|
|
|
|
|
color: string;
|
2026-02-26 00:37:00 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** دادهٔ نمایشی هر زون روی نقشه — خروجی تبدیل از تمام لایهها */
|
|
|
|
|
export interface ZoneMapData {
|
2026-04-01 17:28:05 +03:30
|
|
|
zoneId: string;
|
|
|
|
|
geometry: Polygon;
|
|
|
|
|
color: string;
|
|
|
|
|
tooltipContent: string;
|
|
|
|
|
cultivable: boolean;
|
|
|
|
|
zoneInitialData?: ZoneInitialData;
|
2026-02-26 00:37:00 +03:30
|
|
|
}
|
|
|
|
|
|
2026-02-26 00:17:32 +03:30
|
|
|
interface ApiResponse<T> {
|
2026-04-01 17:28:05 +03:30
|
|
|
status: string;
|
|
|
|
|
data: T;
|
2026-02-26 00:17:32 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
|
2026-04-01 17:28:05 +03:30
|
|
|
const res = await promise;
|
|
|
|
|
return res.data;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeTaskInitResponse(
|
|
|
|
|
task: RecommendationTaskInitResponse,
|
|
|
|
|
): RecommendationTaskInitResponse {
|
|
|
|
|
return {
|
|
|
|
|
...task,
|
|
|
|
|
status: normalizeRecommendationTaskStatus(task.status),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeAreaResult(
|
|
|
|
|
result: CropZoningAreaResult,
|
|
|
|
|
): CropZoningAreaResult {
|
|
|
|
|
return {
|
|
|
|
|
...result,
|
|
|
|
|
task: result.task
|
|
|
|
|
? {
|
|
|
|
|
...result.task,
|
|
|
|
|
status: normalizeRecommendationTaskStatus(result.task.status),
|
|
|
|
|
}
|
|
|
|
|
: result.task,
|
|
|
|
|
};
|
2026-02-26 00:17:32 +03:30
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export const cropZoningService = {
|
|
|
|
|
getProducts(): Promise<{ products: Product[] }> {
|
2026-04-01 17:28:05 +03:30
|
|
|
return unwrap(
|
|
|
|
|
apiClient.get<ApiResponse<{ products: Product[] }>>(
|
|
|
|
|
`${PREFIX}/products/`,
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-02-26 00:17:32 +03:30
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getZonesInitial(body: {
|
2026-04-01 17:28:05 +03:30
|
|
|
zones: FeatureCollection<Polygon>;
|
|
|
|
|
products?: string[];
|
2026-02-26 00:17:32 +03:30
|
|
|
}): Promise<ZonesInitialResponse> {
|
2026-04-01 17:28:05 +03:30
|
|
|
return unwrap(
|
|
|
|
|
apiClient.post<ApiResponse<ZonesInitialResponse>>(
|
|
|
|
|
`${PREFIX}/zones/initial/`,
|
|
|
|
|
body,
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-02-26 00:17:32 +03:30
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getZoneDetails(zoneId: string): Promise<ZoneDetailData> {
|
2026-04-01 17:28:05 +03:30
|
|
|
return unwrap(
|
|
|
|
|
apiClient.get<ApiResponse<ZoneDetailData>>(
|
|
|
|
|
`${PREFIX}/zones/${zoneId}/details/`,
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-02-26 00:17:32 +03:30
|
|
|
},
|
|
|
|
|
|
2026-04-01 17:28:05 +03:30
|
|
|
getArea(sensorUuid: string): Promise<CropZoningAreaResponse> {
|
|
|
|
|
return unwrap(
|
|
|
|
|
apiClient.get<ApiResponse<CropZoningAreaResponse>>(`${PREFIX}/area/?sensor_uuid=${sensorUuid}`),
|
|
|
|
|
).then((response) =>
|
|
|
|
|
"task_id" in response
|
|
|
|
|
? normalizeTaskInitResponse(response)
|
|
|
|
|
: normalizeAreaResult(response),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getAreaStatus(
|
|
|
|
|
taskId: string,
|
|
|
|
|
): Promise<RecommendationTaskStatusResponse<CropZoningAreaResult>> {
|
|
|
|
|
return unwrap(
|
|
|
|
|
apiClient.get<
|
|
|
|
|
ApiResponse<RecommendationTaskStatusResponse<CropZoningAreaResult>>
|
|
|
|
|
>(`${PREFIX}/area/status/${taskId}/`),
|
|
|
|
|
).then((response) => ({
|
|
|
|
|
...response,
|
|
|
|
|
status: normalizeRecommendationTaskStatus(response.status),
|
|
|
|
|
result: response.result
|
|
|
|
|
? normalizeAreaResult(response.result)
|
|
|
|
|
: undefined,
|
|
|
|
|
}));
|
2026-02-26 00:37:00 +03:30
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/** نیاز آبی هر منطقه — برای لایهٔ نیاز آبی */
|
2026-04-01 17:28:05 +03:30
|
|
|
getZonesWaterNeed(body: {
|
|
|
|
|
zones: FeatureCollection<Polygon>;
|
|
|
|
|
}): Promise<{ zones: ZoneWaterNeedData[] }> {
|
2026-02-26 00:37:00 +03:30
|
|
|
return unwrap(
|
2026-04-01 17:28:05 +03:30
|
|
|
apiClient.post<ApiResponse<{ zones: ZoneWaterNeedData[] }>>(
|
|
|
|
|
`${PREFIX}/zones/water-need/`,
|
|
|
|
|
body,
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-02-26 00:37:00 +03:30
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/** کیفیت خاک هر منطقه — برای لایهٔ کیفیت خاک */
|
2026-04-01 17:28:05 +03:30
|
|
|
getZonesSoilQuality(body: {
|
|
|
|
|
zones: FeatureCollection<Polygon>;
|
|
|
|
|
}): Promise<{ zones: ZoneSoilQualityData[] }> {
|
2026-02-26 00:37:00 +03:30
|
|
|
return unwrap(
|
2026-04-01 17:28:05 +03:30
|
|
|
apiClient.post<ApiResponse<{ zones: ZoneSoilQualityData[] }>>(
|
|
|
|
|
`${PREFIX}/zones/soil-quality/`,
|
|
|
|
|
body,
|
|
|
|
|
),
|
|
|
|
|
);
|
2026-02-26 00:37:00 +03:30
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/** ریسک کشت هر منطقه — برای لایهٔ ریسک کشت */
|
|
|
|
|
getZonesCultivationRisk(body: {
|
2026-04-01 17:28:05 +03:30
|
|
|
zones: FeatureCollection<Polygon>;
|
2026-02-26 00:37:00 +03:30
|
|
|
}): Promise<{ zones: ZoneCultivationRiskData[] }> {
|
|
|
|
|
return unwrap(
|
|
|
|
|
apiClient.post<ApiResponse<{ zones: ZoneCultivationRiskData[] }>>(
|
|
|
|
|
`${PREFIX}/zones/cultivation-risk/`,
|
2026-04-01 17:28:05 +03:30
|
|
|
body,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
};
|