| import { useQuery } from '@tanstack/react-query' |
| import type { DisbursementsResponse } from '../types' |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
|
|
| const BASE_URL = import.meta.env.VITE_API_URL ?? '' |
|
|
| async function fetchJson<T>(path: string): Promise<T> { |
| const res = await fetch(`${BASE_URL}${path}`) |
| if (!res.ok) throw new Error(`API error: ${res.status} ${res.statusText}`) |
| return res.json() as Promise<T> |
| } |
|
|
| function mock<T>(value: T, delayMs = 180): Promise<T> { |
| |
| return new Promise((resolve) => setTimeout(() => resolve(value), delayMs)) |
| } |
|
|
| |
| |
| |
| |
| |
| |
|
|
| export interface Zone { |
| zone_id: string |
| name: string |
| city: string |
| country: string |
| latitude: number |
| longitude: number |
| elevation_m: number |
| settlement_type: string |
| worker_population_est: number |
| outdoor_exposure_pct: number |
| heat_vulnerability: string |
| risk_level: string |
| corrected_temp_c: number |
| current_wbgt_c: number |
| current_heat_index_c: number |
| max_temp_c: number |
| max_wbgt_c: number |
| consecutive_hot_days: number |
| trigger_probability_7d: number |
| prediction_confidence: number |
| model_tier: string |
| |
| |
| |
| alert_threshold_c?: number | null |
| payout_threshold_c?: number | null |
| threshold_uhi_model?: string | null |
| threshold_mode?: string | null |
| } |
|
|
| export interface ZonesResponse { |
| zones: Zone[] |
| total: number |
| cities: string[] |
| } |
|
|
| export interface DailyHeat { |
| date: string |
| temp_c: number |
| grid_temp_c: number |
| wbgt_c: number |
| } |
|
|
| export interface IndexData { |
| zone_id: string |
| zone_name: string |
| city: string |
| temp_current: number |
| wbgt_current: number |
| risk_level: string |
| daily_history: DailyHeat[] |
| } |
|
|
| export interface IndicesResponse { |
| indices: IndexData[] |
| total: number |
| } |
|
|
| export interface Trigger { |
| zone_id: string |
| zone_name: string |
| city: string |
| trigger_level: string |
| triggered_at: string |
| heat_risk_score: number |
| max_temp_c: number |
| max_wbgt_c: number |
| consecutive_days: number |
| settlement_type: string |
| payout_per_worker_usd: number |
| } |
|
|
| export interface TriggersResponse { |
| triggers: Trigger[] |
| total: number |
| } |
|
|
| export interface BasisRisk { |
| zone_id: string |
| zone_name: string |
| city: string |
| settlement_type: string |
| false_positive_rate: number |
| false_negative_rate: number |
| correlation: number |
| mae: number |
| accuracy_by_tier: Record<string, number> |
| recommendations: unknown |
| } |
|
|
| export interface BasisRiskResponse { |
| basis_risk: BasisRisk[] |
| total: number |
| } |
|
|
| export interface Notification { |
| id: string |
| zone_id: string |
| zone_name: string |
| city: string |
| trigger_level: string |
| channel: string |
| language: string |
| recipient_count: number |
| message_preview: string |
| status: string |
| delivered_at: string |
| } |
|
|
| export interface NotificationsResponse { |
| notifications: Notification[] |
| by_language: Record<string, number> |
| } |
|
|
| export interface PipelineStep { |
| step: string |
| status: string |
| duration_s: number |
| } |
|
|
| export interface PipelineRun { |
| run_id: string |
| started_at: string |
| ended_at: string |
| status: string |
| duration_s: number |
| zones_processed: number |
| triggers_found: number |
| notifications_sent: number |
| total_cost_usd: number |
| steps: PipelineStep[] |
| } |
|
|
| export interface PipelineRunsResponse { |
| runs: PipelineRun[] |
| total: number |
| } |
|
|
| export interface PipelineStats { |
| total_runs: number |
| successful_runs: number |
| success_rate: number |
| zones_monitored: number |
| cities: number |
| active_triggers: number |
| total_enrolled: number |
| total_cost_usd: number |
| avg_cost_per_run_usd: number |
| last_run: string | null |
| data_sources: string[] |
| } |
|
|
| export interface EnrolledResponse { |
| by_zone: { zone_id: string; enrolled: number; worker_population: number }[] |
| total_enrolled: number |
| } |
|
|
| export interface CalibrateParams { |
| temp_threshold: number |
| consecutive_days: number |
| wbgt_threshold: number |
| payout_usd: number |
| budget_usd: number |
| worker_contribution_usd: number |
| } |
|
|
| export interface CalibrateZoneResult { |
| zone_id: string |
| zone_name: string |
| city: string |
| settlement_type: string |
| heat_vulnerability: string |
| enrolled_workers: number |
| days_above_temp: number |
| days_above_wbgt: number |
| consecutive_days_temp: number |
| consecutive_days_wbgt: number |
| trigger_events: number |
| events_per_year: number |
| annual_payout_per_worker: number |
| annual_payout_total: number |
| basis_risk_score: number |
| triggered: boolean |
| actuarial_cost_per_worker: number |
| cost_breakdown: Record<string, any> |
| allocated_budget: number |
| workers_covered: number |
| coverage_pct: number |
| priority_rank: number |
| } |
|
|
| export interface CalibrateSummary { |
| total_zones: number |
| zones_triggered: number |
| total_trigger_days: number |
| avg_events_per_year: number |
| total_annual_cost: number |
| avg_cost_per_worker: number |
| total_enrolled: number |
| avg_basis_risk: number |
| } |
|
|
| export interface CalibrateAllocation { |
| budget_usd: number |
| worker_contribution_usd: number |
| workers_covered: number |
| overall_coverage_pct: number |
| zones_fully_funded: number |
| zones_partially_funded: number |
| zones_unfunded: number |
| stretch_analysis: Record<string, any> |
| } |
|
|
| export interface CalibrateResponse { |
| zones: CalibrateZoneResult[] |
| summary: CalibrateSummary |
| allocation: CalibrateAllocation |
| thresholds: CalibrateParams |
| } |
|
|
| |
|
|
| import { |
| ZONES_RESPONSE, |
| INDICES_RESPONSE, |
| TRIGGERS_RESPONSE, |
| BASIS_RISK_RESPONSE, |
| NOTIFICATIONS_RESPONSE, |
| DISBURSEMENTS_RESPONSE, |
| PIPELINE_RUNS_RESPONSE, |
| PIPELINE_STATS_RESPONSE, |
| ENROLLED_RESPONSE, |
| buildCoverage, |
| } from './mockData' |
|
|
| const STALE_5MIN = 5 * 60 * 1000 |
|
|
| export function useZones() { |
| return useQuery<ZonesResponse>({ |
| queryKey: ['zones'], |
| queryFn: () => fetchJson<ZonesResponse>('/api/zones'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| export function useIndices() { |
| return useQuery<IndicesResponse>({ |
| queryKey: ['indices'], |
| queryFn: () => fetchJson<IndicesResponse>('/api/indices'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| export function useTriggers() { |
| return useQuery<TriggersResponse>({ |
| queryKey: ['triggers'], |
| queryFn: () => fetchJson<TriggersResponse>('/api/triggers'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| export function useBasisRisk() { |
| return useQuery<BasisRiskResponse>({ |
| queryKey: ['basis-risk'], |
| queryFn: () => fetchJson<BasisRiskResponse>('/api/basis-risk'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| export function useNotifications() { |
| return useQuery<NotificationsResponse>({ |
| queryKey: ['notifications'], |
| queryFn: () => fetchJson<NotificationsResponse>('/api/notifications'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| export function useDisbursements() { |
| return useQuery<DisbursementsResponse>({ |
| queryKey: ['disbursements'], |
| queryFn: () => fetchJson<DisbursementsResponse>('/api/disbursements'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| export function usePipelineRuns() { |
| return useQuery<PipelineRunsResponse>({ |
| queryKey: ['pipeline-runs'], |
| queryFn: () => fetchJson<PipelineRunsResponse>('/api/pipeline/runs'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| export function usePipelineStats() { |
| return useQuery<PipelineStats>({ |
| queryKey: ['pipeline-stats'], |
| queryFn: () => fetchJson<PipelineStats>('/api/pipeline/stats'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| export function useEnrolled() { |
| return useQuery<EnrolledResponse>({ |
| queryKey: ['enrolled'], |
| queryFn: () => fetchJson<EnrolledResponse>('/api/enrolled-workers'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| |
|
|
| export interface CoverageZone { |
| zone_id: string |
| zone_name: string |
| city: string |
| settlement_type: string |
| heat_vulnerability: string |
| urgency: string |
| current_temp_c: number |
| current_wbgt_c: number |
| trigger_probability_7d: number |
| triggers_this_week: boolean |
| risk_level: string |
| outdoor_exposure_pct: number |
| enrolled_workers: number |
| |
| weekly_cost_per_worker: number |
| alert_payout: number |
| insurance_payout: number |
| total_payout_this_week: number |
| worker_contribution: number |
| philanthropy_share: number |
| insurer_premium: number |
| neural_model: boolean |
| learned_frequency: number | null |
| payout_factor: number | null |
| |
| annual_cost_per_worker: number |
| annual_worker_share: number |
| annual_philanthropy_share: number |
| annual_insurer_share: number |
| annual_trigger_days: number |
| } |
|
|
| export interface CoverageRecommendation { |
| weekly_cost_per_worker: number |
| weekly_cost_total: number |
| annual_cost_total: number |
| annual_cost_per_worker: number |
| total_workers: number |
| zones_triggering: number |
| zones_total: number |
| model_type: string |
| scenario: string |
| payout_threshold_wbgt: number |
| } |
|
|
| export interface CoverageCostSummary { |
| total_to_workers_pct: number |
| total_admin_pct: number |
| total_basis_risk_pct: number |
| worker_contribution_pct: number |
| philanthropy_pct: number |
| insurer_pct: number |
| } |
|
|
| export interface CoverageResponse { |
| recommendation: CoverageRecommendation |
| zones: CoverageZone[] |
| cost_summary: CoverageCostSummary |
| } |
|
|
| export function useCoverageRecommendation(payoutUsd: number = 10, filters: string = 'all|all') { |
| const [gender, settlement] = filters.split('|') |
| return useQuery<CoverageResponse>({ |
| queryKey: ['coverage-recommendation', payoutUsd, gender, settlement], |
| queryFn: () => |
| fetchJson<CoverageResponse>( |
| `/api/coverage-recommendation?payout_usd=${payoutUsd}&gender=${gender || 'all'}&settlement=${settlement || 'all'}` |
| ), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|
| |
|
|
| export interface Worker { |
| worker_id: string |
| name: string |
| name_swahili: string | null |
| nida_id: string | null |
| phone: string |
| zone_id: string |
| zone_name: string |
| settlement_type: string |
| occupation: string |
| age: number | null |
| years_outdoor: number | null |
| household_size: number | null |
| mobile_money: string | null |
| tasaf_enrolled: boolean |
| enrolled_at: string |
| } |
|
|
| export interface WorkersResponse { |
| workers: Worker[] |
| total: number |
| } |
|
|
| export function useWorkers() { |
| return useQuery<WorkersResponse>({ |
| queryKey: ['workers'], |
| queryFn: () => fetchJson<WorkersResponse>('/api/workers'), |
| staleTime: STALE_5MIN, |
| }) |
| } |
|
|