import { useQuery } from '@tanstack/react-query' // Local fixtures — no backend wiring. Every React Query hook in this file // resolves against these fixtures via a small simulated network delay so the // UI renders with populated content. import { mandisResponse, marketPricesResponse, priceForecastsResponse, sellRecommendationsResponse, priceConflictsResponse, modelInfoResponse, deliveryLogsResponse, pipelineRunsResponse, pipelineStatsResponse, } from './mockData' const BASE_URL = import.meta.env.VITE_API_URL ?? '' async function fetchJson(path: string): Promise { const res = await fetch(`${BASE_URL}${path}`) if (!res.ok) throw new Error(`API error: ${res.status} ${res.statusText}`) return res.json() as Promise } function mock(value: T, delayMs = 260): Promise { return new Promise((resolve) => setTimeout(() => resolve(value), delayMs)) } // ── Types ──────────────────────────────────────────────────────────────────── export interface Mandi { mandi_id: string name: string district: string latitude: number longitude: number market_type: string enam_integrated: boolean reporting_quality: string commodities_traded: string[] // Real backend fields (present in static api/mandis.ts and HF Space `src/api.py`): state?: string avg_daily_arrivals_tonnes?: number // Only populated by the offline mock fixture — real backend does not return this. last_updated?: string } export interface MandisResponse { mandis: Mandi[] total: number source?: string } export interface MarketPrice { mandi_id: string mandi_name: string commodity_id: string commodity_name: string price_rs: number agmarknet_price_rs: number | null enam_price_rs: number | null reconciled_price_rs: number confidence: number price_trend: string date: string // Present when backend spreads `full_data` JSONB (pipeline-authored rows); // absent from the older-row fallback in api/market-prices.ts. category?: string source_used?: string reasoning?: string } export interface MarketPricesResponse { market_prices: MarketPrice[] total: number source?: string } export interface PriceForecast { mandi_id: string mandi_name: string commodity_id: string commodity_name: string // Backend api/price-forecast.ts initialises these to `null` and only fills // 7d/14d/30d when the corresponding horizon row exists in Neon. current_price_rs: number | null price_7d: number | null price_14d: number | null price_30d: number | null ci_lower_7d: number | null ci_upper_7d: number | null // Backend api/price-forecast.ts only produces 7d CI bands. 14d/30d bands // exist in the Python `PriceForecast` dataclass but are not surfaced through // the `price_forecasts` SELECT. Mock fixture provides them; real API will // leave them undefined. ci_lower_14d?: number ci_upper_14d?: number ci_lower_30d?: number ci_upper_30d?: number direction: string confidence: number // Mock-only; not returned by backend. seasonal_index?: number } export interface PriceForecastsResponse { price_forecasts: PriceForecast[] total: number source?: string } export interface SellOption { mandi_id: string mandi_name: string sell_timing: string market_price_rs: number transport_cost_rs: number storage_loss_rs: number mandi_fee_rs: number net_price_rs: number distance_km: number // Python `recommendation_to_dict` emits `confidence` and `price_source` // but does NOT emit `drive_time_min`. Older fallback rows in // api/sell-recommendations.ts also drop `confidence`. drive_time_min?: number confidence?: number price_source?: string } export interface CreditReadiness { readiness: 'strong' | 'moderate' | 'not_yet' expected_revenue_rs: number min_revenue_rs: number max_advisable_input_loan_rs: number revenue_confidence: number loan_to_revenue_pct: number strengths: string[] risks: string[] advice_en: string advice_ta: string // KCC / DPI extras from Python `credit_readiness_to_dict` — absent in mock. kcc_limit_rs?: number | null kcc_outstanding_rs?: number | null kcc_headroom_rs?: number | null kcc_repayment_status?: string | null dpi_checked?: boolean } export interface SellRecommendation { // Fallback branch of api/sell-recommendations.ts emits `farmer_id`; newer // `full_data` rows include it too. farmer_id?: string farmer_name: string commodity_id: string commodity_name: string // Python layer emits a `quantity_quintals` key regardless of region; under // Kenya the value is bags. Pages use `RegionCopy.quantityNoun` for the label. quantity_quintals: number farmer_lat: number farmer_lon: number best_option: SellOption all_options: SellOption[] potential_gain_rs: number recommendation_text: string // Phase 1.4 rename: the broker agent emits `recommendation_local` as the // local-language translation, paired with `local_language_code` (ISO 639-1, // "ta" for Tamil / India, "sw" for Swahili / Kenya). Use // `LANGUAGE_NAMES[code]` from lib/region.ts to render a display name // rather than hardcoding the language. recommendation_local?: string local_language_code?: string // Backend explicitly returns `null` (not undefined) from the fallback path. credit_readiness?: CreditReadiness | null } export interface SellRecommendationsResponse { sell_recommendations: SellRecommendation[] total: number source?: string } export interface InvestigationStep { tool: string finding: string } export interface PriceConflict { mandi_id: string mandi_name: string commodity_id: string commodity_name: string agmarknet_price: number enam_price: number delta_pct: number resolution: string reconciled_price: number reasoning: string // api/price-conflicts.ts adds this alongside `investigation_steps` when // enriching raw JSONB conflicts from pipeline_runs. confidence?: number investigation_steps?: InvestigationStep[] | null } export interface PriceConflictsResponse { price_conflicts: PriceConflict[] total: number source?: string } // ── Raw / Extracted / Reconciled responses ──────────────────────────────────── export interface RawInputsResponse { raw_inputs: Record sources: string[] } export interface ExtractedDataResponse { extracted_data: Record total_mandis: number } export interface ReconciledDataResponse { reconciled_data: Record total_mandis: number total_conflicts: number } // ── Model info ─────────────────────────────────────────────────────────────── export interface ModelInfoResponse { model_metrics: { model_type: string // Backend api/model-info.ts returns these as literal `null` when the // static stub has no values. Once the port wires real metrics from Neon // they'll be numbers, but the type must tolerate `null` today. rmse?: number | null mae?: number | null r2?: number | null directional_accuracy?: number | null train_samples?: number | null test_samples?: number | null features?: string[] feature_importances?: Record } // Mock-only; backend api/model-info.ts does not emit this block. Kept as // optional so pages can render a Claude/Chronos stack summary when present. ml_stack?: { primary_model: { type: string; features: number; metrics: Record } agents: Record [key: string]: unknown } source?: string } // ── Delivery log types ────────────────────────────────────────────────────── export interface DeliveryLog { farmer_id: string farmer_name: string phone: string channel: string sms_text: string sms_text_local: string status: string error: string | null created_at: string } export interface DeliveryLogsResponse { delivery_logs: DeliveryLog[] total: number } // ── Pipeline types ─────────────────────────────────────────────────────────── export interface PipelineStepDetails { data_source_mode?: 'live' | 'demo' [key: string]: unknown } export interface PipelineStep { step: string status: string duration_s: number details?: PipelineStepDetails } export interface PipelineRun { run_id: string started_at: string ended_at: string status: string duration_s: number steps: PipelineStep[] total_cost_usd: number // Extra columns surfaced by api/pipeline-runs.ts; mock fixture omits them. mandis_processed?: number commodities_tracked?: number } export interface PipelineRunsResponse { runs: PipelineRun[] // Backend api/pipeline-runs.ts does NOT return `total` — it only emits // `{ runs, source }`. Kept optional so the mock can still populate it. total?: number source?: string } export interface PipelineStats { total_runs: number success_rate: number mandis_monitored: number commodities_tracked: number price_conflicts_found: number total_cost_usd: number last_run: string | null data_sources: string[] // Additional fields returned by api/pipeline-stats.ts. successful_runs?: number avg_cost_per_run_usd?: number source?: string } // ── Query hooks ────────────────────────────────────────────────────────────── const STALE_5MIN = 5 * 60 * 1000 // ── Health / region endpoint ──────────────────────────────────────────────── export interface HealthResponse { status: string // Present when backend exposes region (added in Phase 1.5 — both the Vercel // `/api/health` and the HF Space `/health` emit it). region?: 'kenya' | 'india' pipeline_data?: boolean source?: string error?: string } export function useHealth() { return useQuery({ queryKey: ['health'], queryFn: () => fetchJson('/api/health'), staleTime: STALE_5MIN, }) } export function useMandis() { return useQuery({ queryKey: ['mandis'], queryFn: () => fetchJson('/api/mandis'), staleTime: STALE_5MIN, }) } export function useMarketPrices() { return useQuery({ queryKey: ['market-prices'], queryFn: () => fetchJson('/api/market-prices'), staleTime: STALE_5MIN, }) } export function usePriceForecasts() { return useQuery({ queryKey: ['price-forecast'], queryFn: () => fetchJson('/api/price-forecast'), staleTime: STALE_5MIN, }) } export function useSellRecommendations() { return useQuery({ queryKey: ['sell-recommendations'], queryFn: () => fetchJson('/api/sell-recommendations'), staleTime: STALE_5MIN, }) } export function usePriceConflicts() { return useQuery({ queryKey: ['price-conflicts'], queryFn: () => fetchJson('/api/price-conflicts'), staleTime: STALE_5MIN, }) } export function useModelInfo() { return useQuery({ queryKey: ['model-info'], queryFn: () => fetchJson('/api/model-info'), staleTime: STALE_5MIN, }) } export function useDeliveryLogs() { return useQuery({ queryKey: ['delivery-logs'], queryFn: () => fetchJson('/api/delivery-logs'), staleTime: STALE_5MIN, }) } export function usePipelineRuns() { return useQuery({ queryKey: ['pipeline-runs'], queryFn: () => fetchJson('/api/pipeline-runs'), staleTime: STALE_5MIN, }) } export function usePipelineStats() { return useQuery({ queryKey: ['pipeline-stats'], queryFn: () => fetchJson('/api/pipeline-stats'), staleTime: STALE_5MIN, }) }