UPDATE
This commit is contained in:
@@ -3,138 +3,250 @@
|
||||
* @see CROP_ZONING_APIS.md
|
||||
*/
|
||||
|
||||
import type { Feature, FeatureCollection, Polygon } from 'geojson'
|
||||
import { apiClient } from '../client'
|
||||
import type { Feature, FeatureCollection, Polygon } from "geojson";
|
||||
import { apiClient } from "../client";
|
||||
import type {
|
||||
RecommendationTaskInitResponse,
|
||||
RecommendationTaskStatus,
|
||||
RecommendationTaskStatusResponse,
|
||||
} from "./recommendationTask";
|
||||
import { normalizeRecommendationTaskStatus } from "./recommendationTask";
|
||||
|
||||
const PREFIX = '/api/crop-zoning'
|
||||
const PREFIX = "/api/crop-zoning";
|
||||
|
||||
export interface Product {
|
||||
id: string
|
||||
label: string
|
||||
color: string
|
||||
id: string;
|
||||
label: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
export interface ZoneInitialData {
|
||||
zoneId: string
|
||||
geometry: Polygon
|
||||
zoneId: string;
|
||||
geometry: Polygon;
|
||||
/** اگر null/خالی/uncultivable باشد، زون غیرقابل کشت و خاکستری نمایش داده میشود */
|
||||
crop?: string | null
|
||||
matchPercent?: number | null
|
||||
waterNeed?: string | null
|
||||
estimatedProfit?: string | null
|
||||
crop?: string | null;
|
||||
matchPercent?: number | null;
|
||||
waterNeed?: string | null;
|
||||
estimatedProfit?: string | null;
|
||||
}
|
||||
|
||||
export interface ZonesInitialResponse {
|
||||
total_area_hectares: number
|
||||
total_area_sqm: number
|
||||
zone_count: number
|
||||
zones: ZoneInitialData[]
|
||||
total_area_hectares: number;
|
||||
total_area_sqm: number;
|
||||
zone_count: number;
|
||||
zones: ZoneInitialData[];
|
||||
}
|
||||
|
||||
export interface AreaResponse {
|
||||
area: Feature<Polygon>
|
||||
area: Feature<Polygon> | null;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
export interface ZoneDetailData {
|
||||
zoneId: string
|
||||
crop: string
|
||||
matchPercent: number
|
||||
waterNeed: string
|
||||
estimatedProfit: string
|
||||
reason: string
|
||||
criteria: { name: string; value: number }[]
|
||||
area_hectares?: number
|
||||
zoneId: string;
|
||||
crop: string;
|
||||
matchPercent: number;
|
||||
waterNeed: string;
|
||||
estimatedProfit: string;
|
||||
reason: string;
|
||||
criteria: { name: string; value: number }[];
|
||||
area_hectares?: number;
|
||||
}
|
||||
|
||||
/** دیتای نیاز آبی هر زون — لایهٔ نیاز آبی */
|
||||
export interface ZoneWaterNeedData {
|
||||
zoneId: string
|
||||
geometry: Polygon
|
||||
level: 'low' | 'medium' | 'high'
|
||||
value?: string
|
||||
color: string
|
||||
zoneId: string;
|
||||
geometry: Polygon;
|
||||
level: "low" | "medium" | "high";
|
||||
value?: string;
|
||||
color: string;
|
||||
}
|
||||
|
||||
/** دیتای کیفیت خاک هر زون — لایهٔ کیفیت خاک */
|
||||
export interface ZoneSoilQualityData {
|
||||
zoneId: string
|
||||
geometry: Polygon
|
||||
level: 'low' | 'medium' | 'high'
|
||||
score?: number
|
||||
color: string
|
||||
zoneId: string;
|
||||
geometry: Polygon;
|
||||
level: "low" | "medium" | "high";
|
||||
score?: number;
|
||||
color: string;
|
||||
}
|
||||
|
||||
/** دیتای ریسک کشت هر زون — لایهٔ ریسک کشت */
|
||||
export interface ZoneCultivationRiskData {
|
||||
zoneId: string
|
||||
geometry: Polygon
|
||||
level: 'low' | 'medium' | 'high'
|
||||
color: string
|
||||
zoneId: string;
|
||||
geometry: Polygon;
|
||||
level: "low" | "medium" | "high";
|
||||
color: string;
|
||||
}
|
||||
|
||||
/** دادهٔ نمایشی هر زون روی نقشه — خروجی تبدیل از تمام لایهها */
|
||||
export interface ZoneMapData {
|
||||
zoneId: string
|
||||
geometry: Polygon
|
||||
color: string
|
||||
tooltipContent: string
|
||||
cultivable: boolean
|
||||
zoneInitialData?: ZoneInitialData
|
||||
zoneId: string;
|
||||
geometry: Polygon;
|
||||
color: string;
|
||||
tooltipContent: string;
|
||||
cultivable: boolean;
|
||||
zoneInitialData?: ZoneInitialData;
|
||||
}
|
||||
|
||||
interface ApiResponse<T> {
|
||||
status: string
|
||||
data: T
|
||||
status: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
|
||||
const res = await promise
|
||||
return res.data
|
||||
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,
|
||||
};
|
||||
}
|
||||
|
||||
export const cropZoningService = {
|
||||
getProducts(): Promise<{ products: Product[] }> {
|
||||
return unwrap(apiClient.get<ApiResponse<{ products: Product[] }>>(`${PREFIX}/products/`))
|
||||
return unwrap(
|
||||
apiClient.get<ApiResponse<{ products: Product[] }>>(
|
||||
`${PREFIX}/products/`,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
getZonesInitial(body: {
|
||||
zones: FeatureCollection<Polygon>
|
||||
products?: string[]
|
||||
zones: FeatureCollection<Polygon>;
|
||||
products?: string[];
|
||||
}): Promise<ZonesInitialResponse> {
|
||||
return unwrap(apiClient.post<ApiResponse<ZonesInitialResponse>>(`${PREFIX}/zones/initial/`, body))
|
||||
return unwrap(
|
||||
apiClient.post<ApiResponse<ZonesInitialResponse>>(
|
||||
`${PREFIX}/zones/initial/`,
|
||||
body,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
getZoneDetails(zoneId: string): Promise<ZoneDetailData> {
|
||||
return unwrap(apiClient.get<ApiResponse<ZoneDetailData>>(`${PREFIX}/zones/${zoneId}/details/`))
|
||||
return unwrap(
|
||||
apiClient.get<ApiResponse<ZoneDetailData>>(
|
||||
`${PREFIX}/zones/${zoneId}/details/`,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
getArea(): Promise<AreaResponse> {
|
||||
return unwrap(apiClient.get<ApiResponse<AreaResponse>>(`${PREFIX}/area/`))
|
||||
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,
|
||||
}));
|
||||
},
|
||||
|
||||
/** نیاز آبی هر منطقه — برای لایهٔ نیاز آبی */
|
||||
getZonesWaterNeed(body: { zones: FeatureCollection<Polygon> }): Promise<{ zones: ZoneWaterNeedData[] }> {
|
||||
getZonesWaterNeed(body: {
|
||||
zones: FeatureCollection<Polygon>;
|
||||
}): Promise<{ zones: ZoneWaterNeedData[] }> {
|
||||
return unwrap(
|
||||
apiClient.post<ApiResponse<{ zones: ZoneWaterNeedData[] }>>(`${PREFIX}/zones/water-need/`, body)
|
||||
)
|
||||
apiClient.post<ApiResponse<{ zones: ZoneWaterNeedData[] }>>(
|
||||
`${PREFIX}/zones/water-need/`,
|
||||
body,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
/** کیفیت خاک هر منطقه — برای لایهٔ کیفیت خاک */
|
||||
getZonesSoilQuality(body: { zones: FeatureCollection<Polygon> }): Promise<{ zones: ZoneSoilQualityData[] }> {
|
||||
getZonesSoilQuality(body: {
|
||||
zones: FeatureCollection<Polygon>;
|
||||
}): Promise<{ zones: ZoneSoilQualityData[] }> {
|
||||
return unwrap(
|
||||
apiClient.post<ApiResponse<{ zones: ZoneSoilQualityData[] }>>(`${PREFIX}/zones/soil-quality/`, body)
|
||||
)
|
||||
apiClient.post<ApiResponse<{ zones: ZoneSoilQualityData[] }>>(
|
||||
`${PREFIX}/zones/soil-quality/`,
|
||||
body,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
/** ریسک کشت هر منطقه — برای لایهٔ ریسک کشت */
|
||||
getZonesCultivationRisk(body: {
|
||||
zones: FeatureCollection<Polygon>
|
||||
zones: FeatureCollection<Polygon>;
|
||||
}): Promise<{ zones: ZoneCultivationRiskData[] }> {
|
||||
return unwrap(
|
||||
apiClient.post<ApiResponse<{ zones: ZoneCultivationRiskData[] }>>(
|
||||
`${PREFIX}/zones/cultivation-risk/`,
|
||||
body
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
body,
|
||||
),
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,10 +1,5 @@
|
||||
/**
|
||||
* Farm AI Assistant API
|
||||
* GET context (farm bar data), POST chat (user message + optional farm_context/images).
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type { FarmContext } from '@views/dashboards/farm/farmAiAssistant/farmAiAssistantTypes'
|
||||
import type { ConversationSummary, FarmContext } from '@views/dashboards/farm/farmAiAssistant/farmAiAssistantTypes'
|
||||
|
||||
const PREFIX = '/api/farm-ai-assistant'
|
||||
|
||||
@@ -29,17 +24,57 @@ export interface ChatSection {
|
||||
}
|
||||
|
||||
export interface ChatPayload {
|
||||
content: string
|
||||
farm_context?: FarmContext
|
||||
content?: string
|
||||
images?: string[]
|
||||
conversation_id?: string
|
||||
title?: string
|
||||
farm_context?: Partial<FarmContext>
|
||||
}
|
||||
|
||||
export interface ChatResponseData {
|
||||
export interface ChatTaskInitResponse {
|
||||
task_id: string
|
||||
status: 'PENDING' | 'STARTED' | 'SUCCESS' | 'FAILURE'
|
||||
status_url?: string
|
||||
conversation_id: string
|
||||
message_id: string
|
||||
}
|
||||
|
||||
export interface ChatTaskStatusResponse {
|
||||
task_id: string
|
||||
status: 'PENDING' | 'STARTED' | 'SUCCESS' | 'FAILURE'
|
||||
conversation_id: string
|
||||
progress?: {
|
||||
message?: string
|
||||
}
|
||||
result?: ChatMessageResponse
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface ChatMessageResponse {
|
||||
message_id: string
|
||||
conversation_id: string
|
||||
role: 'user' | 'assistant'
|
||||
content: string
|
||||
sections: ChatSection[]
|
||||
images?: string[]
|
||||
created_at?: string
|
||||
}
|
||||
|
||||
export interface ConversationMessagesResponse {
|
||||
conversation_id: string
|
||||
messages: ChatMessageResponse[]
|
||||
}
|
||||
|
||||
export interface CreateConversationPayload {
|
||||
title?: string
|
||||
farm_context?: Partial<FarmContext>
|
||||
}
|
||||
|
||||
export interface CreateConversationResponse {
|
||||
id: string
|
||||
message_count: number
|
||||
title?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
interface ApiResponse<T> {
|
||||
@@ -52,21 +87,33 @@ function unwrap<T>(res: ApiResponse<T>): T {
|
||||
}
|
||||
|
||||
export const farmAiAssistantService = {
|
||||
/**
|
||||
* Returns farm context for the context bar (soilType, waterEC, selectedCrop, growthStage, lastIrrigationStatus).
|
||||
*/
|
||||
getContext(): Promise<FarmContextResponse> {
|
||||
return apiClient
|
||||
.get<ApiResponse<FarmContextResponse>>(`${PREFIX}/context/`)
|
||||
.then(unwrap)
|
||||
return apiClient.get<ApiResponse<FarmContextResponse>>(`${PREFIX}/context/`).then(unwrap)
|
||||
},
|
||||
|
||||
/**
|
||||
* Send user message (and optional farm_context, images, conversation_id). Returns message with sections.
|
||||
*/
|
||||
chat(payload: ChatPayload): Promise<ChatResponseData> {
|
||||
createChatTask(payload: ChatPayload): Promise<ChatTaskInitResponse> {
|
||||
return apiClient.post<ApiResponse<ChatTaskInitResponse>>(`${PREFIX}/chat/task/`, payload).then(unwrap)
|
||||
},
|
||||
|
||||
getChatTaskStatus(taskId: string): Promise<ChatTaskStatusResponse> {
|
||||
return apiClient.get<ApiResponse<ChatTaskStatusResponse>>(`${PREFIX}/chat/task/${taskId}/status/`).then(unwrap)
|
||||
},
|
||||
|
||||
getConversations(): Promise<ConversationSummary[]> {
|
||||
return apiClient.get<ApiResponse<ConversationSummary[]>>(`${PREFIX}/chats/`).then(unwrap)
|
||||
},
|
||||
|
||||
createConversation(payload?: CreateConversationPayload): Promise<CreateConversationResponse> {
|
||||
return apiClient.post<ApiResponse<CreateConversationResponse>>(`${PREFIX}/chats/`, payload).then(unwrap)
|
||||
},
|
||||
|
||||
deleteConversation(conversationId: string): Promise<{ conversation_id: string }> {
|
||||
return apiClient.delete<ApiResponse<{ conversation_id: string }>>(`${PREFIX}/chats/${conversationId}/`).then(unwrap)
|
||||
},
|
||||
|
||||
getConversationMessages(conversationId: string): Promise<ConversationMessagesResponse> {
|
||||
return apiClient
|
||||
.post<ApiResponse<ChatResponseData>>(`${PREFIX}/chat/`, payload)
|
||||
.get<ApiResponse<ConversationMessagesResponse>>(`${PREFIX}/chats/${conversationId}/messages/`)
|
||||
.then(unwrap)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,65 +3,135 @@
|
||||
* @see RECOMMENDATION_APIS.md
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import { apiClient } from "../client";
|
||||
import type {
|
||||
RecommendationTaskInitResponse,
|
||||
RecommendationTaskStatusResponse,
|
||||
} from "./recommendationTask";
|
||||
import { normalizeRecommendationTaskStatus } from "./recommendationTask";
|
||||
|
||||
const PREFIX = '/api/fertilization-recommendation'
|
||||
const PREFIX = "/api/fertilization-recommendation";
|
||||
|
||||
export interface FarmData {
|
||||
soilType: string
|
||||
organicMatter: string
|
||||
waterEC: string
|
||||
soilType: string;
|
||||
organicMatter: string;
|
||||
waterEC: string;
|
||||
}
|
||||
|
||||
export interface GrowthStage {
|
||||
id: string
|
||||
icon: string
|
||||
id: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface CropOption {
|
||||
id: string
|
||||
labelKey: string
|
||||
icon: string
|
||||
id: string;
|
||||
labelKey: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface FertilizationConfigResponse {
|
||||
farmData: FarmData
|
||||
growthStages: GrowthStage[]
|
||||
cropOptions: CropOption[]
|
||||
farmData: FarmData;
|
||||
growthStages: GrowthStage[];
|
||||
cropOptions: CropOption[];
|
||||
}
|
||||
|
||||
export interface FertilizationPlan {
|
||||
npkRatio: string
|
||||
amountPerHectare: string
|
||||
applicationMethod: string
|
||||
applicationInterval: string
|
||||
reasoning: string
|
||||
npkRatio: string;
|
||||
amountPerHectare: string;
|
||||
applicationMethod: string;
|
||||
applicationInterval: string;
|
||||
reasoning: string;
|
||||
}
|
||||
|
||||
export interface FertilizationRecommendPayload {
|
||||
crop_id?: string
|
||||
growth_stage?: string
|
||||
soilType?: string
|
||||
organicMatter?: string
|
||||
waterEC?: string
|
||||
crop_id?: string;
|
||||
growth_stage?: string;
|
||||
farm_data?: Partial<FarmData>;
|
||||
soilType?: string;
|
||||
organicMatter?: string;
|
||||
waterEC?: string;
|
||||
}
|
||||
|
||||
export interface FertilizationRecommendationResult {
|
||||
plan: FertilizationPlan;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export type FertilizationRecommendResponse =
|
||||
| FertilizationRecommendationResult
|
||||
| RecommendationTaskInitResponse;
|
||||
|
||||
interface ApiResponse<T> {
|
||||
status: string
|
||||
data: T
|
||||
status: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
|
||||
const res = await promise
|
||||
return res.data
|
||||
const res = await promise;
|
||||
return res.data;
|
||||
}
|
||||
|
||||
function normalizeTaskInitResponse(
|
||||
task: RecommendationTaskInitResponse,
|
||||
): RecommendationTaskInitResponse {
|
||||
return {
|
||||
...task,
|
||||
status: normalizeRecommendationTaskStatus(task.status),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRecommendationResult(
|
||||
result: FertilizationRecommendationResult,
|
||||
): FertilizationRecommendationResult {
|
||||
return result.status
|
||||
? {
|
||||
...result,
|
||||
status: normalizeRecommendationTaskStatus(result.status),
|
||||
}
|
||||
: result;
|
||||
}
|
||||
|
||||
export const fertilizationRecommendationService = {
|
||||
getConfig(): Promise<FertilizationConfigResponse> {
|
||||
return unwrap(apiClient.get<ApiResponse<FertilizationConfigResponse>>(`${PREFIX}/config/`))
|
||||
return unwrap(
|
||||
apiClient.get<ApiResponse<FertilizationConfigResponse>>(
|
||||
`${PREFIX}/config/`,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
recommend(payload?: FertilizationRecommendPayload): Promise<{ plan: FertilizationPlan }> {
|
||||
return unwrap(apiClient.post<ApiResponse<{ plan: FertilizationPlan }>>(`${PREFIX}/recommend/`, payload ?? {}))
|
||||
recommend(
|
||||
payload?: FertilizationRecommendPayload,
|
||||
): Promise<FertilizationRecommendResponse> {
|
||||
return unwrap(
|
||||
apiClient.post<ApiResponse<FertilizationRecommendResponse>>(
|
||||
`${PREFIX}/recommend/`,
|
||||
payload ?? {},
|
||||
),
|
||||
).then((response) =>
|
||||
"task_id" in response
|
||||
? normalizeTaskInitResponse(response)
|
||||
: normalizeRecommendationResult(response),
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getRecommendStatus(
|
||||
taskId: string,
|
||||
): Promise<
|
||||
RecommendationTaskStatusResponse<FertilizationRecommendationResult>
|
||||
> {
|
||||
return unwrap(
|
||||
apiClient.get<
|
||||
ApiResponse<
|
||||
RecommendationTaskStatusResponse<FertilizationRecommendationResult>
|
||||
>
|
||||
>(`${PREFIX}/recommend/status/${taskId}/`),
|
||||
).then((response) => ({
|
||||
...response,
|
||||
status: normalizeRecommendationTaskStatus(response.status),
|
||||
result: response.result
|
||||
? normalizeRecommendationResult(response.result)
|
||||
: undefined,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -3,58 +3,147 @@
|
||||
* @see RECOMMENDATION_APIS.md
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import { apiClient } from "../client";
|
||||
import type {
|
||||
RecommendationTaskInitResponse,
|
||||
RecommendationTaskStatusResponse,
|
||||
} from "./recommendationTask";
|
||||
import { normalizeRecommendationTaskStatus } from "./recommendationTask";
|
||||
|
||||
const PREFIX = '/api/irrigation-recommendation'
|
||||
const PREFIX = "/api/irrigation-recommendation";
|
||||
|
||||
export interface FarmInfo {
|
||||
soilType: string
|
||||
waterQuality: string
|
||||
climateZone: string
|
||||
soilType: string;
|
||||
waterQuality: string;
|
||||
climateZone: string;
|
||||
}
|
||||
|
||||
export interface CropOption {
|
||||
id: string
|
||||
labelKey: string
|
||||
icon: string
|
||||
id: string;
|
||||
labelKey: string;
|
||||
icon: string;
|
||||
}
|
||||
|
||||
export interface IrrigationConfigResponse {
|
||||
farmInfo: FarmInfo
|
||||
cropOptions: CropOption[]
|
||||
farmInfo: FarmInfo;
|
||||
cropOptions: CropOption[];
|
||||
}
|
||||
|
||||
export interface IrrigationPlan {
|
||||
frequencyPerWeek: number
|
||||
durationMinutes: number
|
||||
bestTimeOfDay: string
|
||||
moistureLevel: number
|
||||
warning?: string
|
||||
frequencyPerWeek: number | string;
|
||||
durationMinutes: number | string;
|
||||
bestTimeOfDay: string;
|
||||
moistureLevel: number | string;
|
||||
warning?: string;
|
||||
}
|
||||
|
||||
export interface IrrigationRecommendPayload {
|
||||
crop_id?: string
|
||||
soilType?: string
|
||||
waterQuality?: string
|
||||
climateZone?: string
|
||||
crop_id?: string;
|
||||
farm_data?: Partial<FarmInfo>;
|
||||
soilType?: string;
|
||||
waterQuality?: string;
|
||||
climateZone?: string;
|
||||
}
|
||||
|
||||
export interface WaterBalanceDailyEntry {
|
||||
forecast_date: string;
|
||||
et0_mm: number;
|
||||
etc_mm: number;
|
||||
effective_rainfall_mm: number;
|
||||
gross_irrigation_mm: number;
|
||||
irrigation_timing: string;
|
||||
}
|
||||
|
||||
export interface WaterBalanceCropProfile {
|
||||
kc_initial: number;
|
||||
kc_mid: number;
|
||||
kc_end: number;
|
||||
}
|
||||
|
||||
export interface WaterBalance {
|
||||
daily: WaterBalanceDailyEntry[];
|
||||
crop_profile?: WaterBalanceCropProfile;
|
||||
active_kc?: number;
|
||||
}
|
||||
|
||||
export interface IrrigationRecommendationResult {
|
||||
plan: IrrigationPlan;
|
||||
raw_response?: string;
|
||||
water_balance?: WaterBalance;
|
||||
status?: string;
|
||||
}
|
||||
|
||||
export type IrrigationRecommendResponse =
|
||||
| IrrigationRecommendationResult
|
||||
| RecommendationTaskInitResponse;
|
||||
|
||||
interface ApiResponse<T> {
|
||||
status: string
|
||||
data: T
|
||||
status: string;
|
||||
data: T;
|
||||
}
|
||||
|
||||
async function unwrap<T>(promise: Promise<ApiResponse<T>>): Promise<T> {
|
||||
const res = await promise
|
||||
return res.data
|
||||
const res = await promise;
|
||||
return res.data;
|
||||
}
|
||||
|
||||
function normalizeTaskInitResponse(
|
||||
task: RecommendationTaskInitResponse,
|
||||
): RecommendationTaskInitResponse {
|
||||
return {
|
||||
...task,
|
||||
status: normalizeRecommendationTaskStatus(task.status),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeRecommendationResult(
|
||||
result: IrrigationRecommendationResult,
|
||||
): IrrigationRecommendationResult {
|
||||
return result.status
|
||||
? {
|
||||
...result,
|
||||
status: normalizeRecommendationTaskStatus(result.status),
|
||||
}
|
||||
: result;
|
||||
}
|
||||
|
||||
export const irrigationRecommendationService = {
|
||||
getConfig(): Promise<IrrigationConfigResponse> {
|
||||
return unwrap(apiClient.get<ApiResponse<IrrigationConfigResponse>>(`${PREFIX}/config/`))
|
||||
return unwrap(
|
||||
apiClient.get<ApiResponse<IrrigationConfigResponse>>(`${PREFIX}/config/`),
|
||||
);
|
||||
},
|
||||
|
||||
recommend(payload?: IrrigationRecommendPayload): Promise<{ plan: IrrigationPlan }> {
|
||||
return unwrap(apiClient.post<ApiResponse<{ plan: IrrigationPlan }>>(`${PREFIX}/recommend/`, payload ?? {}))
|
||||
recommend(
|
||||
payload?: IrrigationRecommendPayload,
|
||||
): Promise<IrrigationRecommendResponse> {
|
||||
return unwrap(
|
||||
apiClient.post<ApiResponse<IrrigationRecommendResponse>>(
|
||||
`${PREFIX}/recommend/`,
|
||||
payload ?? {},
|
||||
),
|
||||
).then((response) =>
|
||||
"task_id" in response
|
||||
? normalizeTaskInitResponse(response)
|
||||
: normalizeRecommendationResult(response),
|
||||
);
|
||||
},
|
||||
}
|
||||
|
||||
getRecommendStatus(
|
||||
taskId: string,
|
||||
): Promise<RecommendationTaskStatusResponse<IrrigationRecommendationResult>> {
|
||||
return unwrap(
|
||||
apiClient.get<
|
||||
ApiResponse<
|
||||
RecommendationTaskStatusResponse<IrrigationRecommendationResult>
|
||||
>
|
||||
>(`${PREFIX}/recommend/status/${taskId}/`),
|
||||
).then((response) => ({
|
||||
...response,
|
||||
status: normalizeRecommendationTaskStatus(response.status),
|
||||
result: response.result
|
||||
? normalizeRecommendationResult(response.result)
|
||||
: undefined,
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
export type RecommendationTaskStatus =
|
||||
| "pending"
|
||||
| "processing"
|
||||
| "completed"
|
||||
| "failed";
|
||||
|
||||
export interface RecommendationTaskInitResponse {
|
||||
task_id: string;
|
||||
status: RecommendationTaskStatus;
|
||||
}
|
||||
|
||||
export interface RecommendationTaskProgress {
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface RecommendationTaskStatusResponse<T> {
|
||||
task_id: string;
|
||||
status: RecommendationTaskStatus;
|
||||
progress?: RecommendationTaskProgress;
|
||||
result?: T;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export const normalizeRecommendationTaskStatus = (
|
||||
status?: string,
|
||||
): RecommendationTaskStatus => {
|
||||
switch (status?.toLowerCase()) {
|
||||
case "started":
|
||||
case "processing":
|
||||
return "processing";
|
||||
case "success":
|
||||
case "completed":
|
||||
return "completed";
|
||||
case "failure":
|
||||
case "failed":
|
||||
return "failed";
|
||||
default:
|
||||
return "pending";
|
||||
}
|
||||
};
|
||||
|
||||
export const isRecommendationTaskRunning = (status: RecommendationTaskStatus) =>
|
||||
status === "pending" || status === "processing";
|
||||
Reference in New Issue
Block a user