export type AreaScore = { area_id: string; name: string; district: string; segment: string; month: string; investment_score: number; recommendation: string; liquidity_score: number; momentum_score: number; rent_support_score: number; safety_margin_score: number; credit_support_score: number; supply_risk_score: number; valuation_pressure_score: number; transaction_count: number; transaction_price_psm: number; listing_count: number; listing_price_psm: number; rent_price_psm: number; median_days_on_market: number; annual_rent_yield_pct: number; listing_pressure_ratio: number; discount_pct: number; price_momentum_pct: number; volume_momentum_pct: number; }; export type AreaScoreSummary = { area_id: string; name: string; district: string; investment_score: number; recommendation: string; }; export type MarketOverview = { month: string; area_count: number; average_investment_score: number; average_rent_yield_pct: number; top_area: AreaScoreSummary | null; weakest_area: AreaScoreSummary | null; highest_supply_risk_area: AreaScoreSummary | null; }; export type NeighborhoodScore = { neighborhood_id: string; area_id: string; name: string; area_name: string; district: string; month: string; investment_score: number; recommendation: string; liquidity_score: number; rent_support_score: number; price_safety_score: number; location_score: number; building_age_score: number; transaction_count: number; transaction_price_psm: number; listing_count: number; listing_price_psm: number; rent_price_psm: number; median_days_on_market: number; annual_rent_yield_pct: number; discount_pct: number; available_units: number; }; export type WatchlistItem = { watchlist_item_id: number; neighborhood_id: string | null; area_id: string | null; target_price_psm: number | null; status: string; notes: string; }; export type CreateWatchlistItem = { area_id?: string; neighborhood_id?: string; target_price_psm?: number; notes?: string; }; export type DataSource = { source_id: number; name: string; source_type: string; url: string | null; cadence: string | null; reliability: string; notes: string; created_at: string; }; export type CreateDataSource = { name: string; source_type: string; url?: string; cadence?: string; reliability?: string; notes?: string; }; export type IngestionRun = { run_id: number; source_id: number | null; source_name: string | null; raw_artifact_id: number | null; status: string; started_at: string; finished_at: string | null; raw_uri: string | null; row_count: number | null; error_message: string | null; }; export type CreateIngestionRun = { source_id?: number; status?: string; raw_uri?: string; raw_artifact_id?: number; row_count?: number; error_message?: string; }; export type FinishIngestionRun = { status: "succeeded" | "failed"; row_count?: number; error_message?: string; }; export type RawArtifact = { artifact_id: number; run_id: number | null; source_id: number | null; source_name: string | null; original_filename: string; raw_uri: string; sha256: string; size_bytes: number; mime_type: string | null; uploaded_by: string; notes: string; created_at: string; }; export type CreateRawArtifact = { run_id?: number; source_id?: number; original_filename: string; raw_uri: string; sha256: string; size_bytes: number; mime_type?: string; uploaded_by?: string; notes?: string; }; export type ImportExecutionRequest = { template_name: string; run_id: number; raw_artifact_id: number; rows: Record[]; allow_reimport?: boolean; }; export type ImportExecutionResponse = { template_name: string; target_table: string; run_id: number; raw_artifact_id: number; row_count: number; raw_row_count: number; duplicate_artifact: boolean; previous_run_ids: number[]; status: string; }; export type ModelRun = { model_run_id: number; model_name: string; model_version: string; target_month: string; status: string; parameters: Record; started_at: string; finished_at: string | null; row_count: number | null; error_message: string | null; }; export type CreateAreaScoreModelRun = { month: string; model_version?: string; }; export type AreaScoreModelRunResponse = { model_run: ModelRun; scores: AreaScore[]; }; export type AreaScoreLineage = { area_id: string; area_name: string; month: string; investment_score: number; model_run_id: number | null; model_version: string; ingestion_run_id: number | null; raw_artifact_id: number | null; raw_uri: string | null; sha256: string | null; source_name: string | null; metric_updated_at: string; score_computed_at: string; }; const API_BASE_URL = process.env.NEXT_PUBLIC_API_BASE_URL?.replace(/\/$/, "") ?? "http://127.0.0.1:8080"; export async function fetchMarketOverview(month: string): Promise { return fetchJson(`/api/v1/market/overview?month=${encodeURIComponent(month)}`); } export async function fetchAreaScores(month: string): Promise { return fetchJson(`/api/v1/areas/scores?month=${encodeURIComponent(month)}`); } export async function fetchNeighborhoodScores(month: string): Promise { return fetchJson(`/api/v1/neighborhoods/scores?month=${encodeURIComponent(month)}`); } export async function fetchWatchlistItems(): Promise { return fetchJson("/api/v1/watchlist"); } export async function createWatchlistItem( payload: CreateWatchlistItem, ): Promise { return fetchJson("/api/v1/watchlist", { method: "POST", body: JSON.stringify(payload), }); } export async function archiveWatchlistItem(id: number): Promise { return fetchJson(`/api/v1/watchlist/${id}`, { method: "DELETE", }); } export async function fetchDataSources(): Promise { return fetchJson("/api/v1/data-sources"); } export async function createDataSource(payload: CreateDataSource): Promise { return fetchJson("/api/v1/data-sources", { method: "POST", body: JSON.stringify(payload), }); } export async function fetchIngestionRuns(): Promise { return fetchJson("/api/v1/ingestion-runs"); } export async function createIngestionRun(payload: CreateIngestionRun): Promise { return fetchJson("/api/v1/ingestion-runs", { method: "POST", body: JSON.stringify(payload), }); } export async function finishIngestionRun( id: number, payload: FinishIngestionRun, ): Promise { return fetchJson(`/api/v1/ingestion-runs/${id}/finish`, { method: "PATCH", body: JSON.stringify(payload), }); } export async function fetchRawArtifacts(): Promise { return fetchJson("/api/v1/raw-artifacts"); } export async function createRawArtifact(payload: CreateRawArtifact): Promise { return fetchJson("/api/v1/raw-artifacts", { method: "POST", body: JSON.stringify(payload), }); } export async function executeImport( payload: ImportExecutionRequest, ): Promise { return fetchJson("/api/v1/imports/execute", { method: "POST", body: JSON.stringify(payload), }); } export async function createAreaScoreModelRun( payload: CreateAreaScoreModelRun, ): Promise { return fetchJson("/api/v1/model-runs/area-scores", { method: "POST", body: JSON.stringify(payload), }); } export async function fetchAreaScoreLineage( areaId: string, month: string, ): Promise { return fetchJson( `/api/v1/areas/${encodeURIComponent(areaId)}/score-lineage?month=${encodeURIComponent(month)}`, ); } async function fetchJson(path: string, init?: RequestInit): Promise { const response = await fetch(`${API_BASE_URL}${path}`, { ...init, headers: { "Content-Type": "application/json", ...init?.headers, }, }); if (!response.ok) { throw new Error(`API request failed with ${response.status}`); } return response.json() as Promise; }