feat: add scenario analysis workspace

This commit is contained in:
2026-06-24 16:43:27 +08:00
parent b559df50d8
commit f0ac47e040
7 changed files with 986 additions and 20 deletions

View File

@@ -415,6 +415,59 @@ export type SimilarNeighborhoodResponse = {
items: SimilarNeighborhood[];
};
export type ScenarioRun = {
scenario_run_id: number;
name: string;
target_month: string;
area_id: string | null;
interest_rate_delta_bps: number;
policy_signal_delta: number;
supply_delta_pct: number;
rent_delta_pct: number;
notes: string;
created_at: string;
};
export type CreateScenarioRun = {
name: string;
target_month: string;
area_id?: string;
interest_rate_delta_bps: number;
policy_signal_delta: number;
supply_delta_pct: number;
rent_delta_pct: number;
notes?: string;
};
export type ScenarioAreaResult = {
area_id: string;
name: string;
district: string;
baseline_score: number;
projected_score: number;
score_delta: number;
baseline_price_psm: number;
projected_price_psm: number;
price_delta_pct: number;
projected_rent_yield_pct: number;
projected_liquidity_score: number;
projected_supply_risk_score: number;
recommendation: string;
risk_notes: string[];
};
export type ScenarioVariant = {
key: string;
name: string;
assumptions: string[];
items: ScenarioAreaResult[];
};
export type ScenarioRunResponse = {
run: ScenarioRun;
variants: ScenarioVariant[];
};
export type ComparisonMetricValue = {
id: string;
value: number | null;
@@ -663,6 +716,29 @@ export async function fetchAreaScoreLineage(
);
}
export async function fetchScenarioRuns(filters: {
target_month?: string;
area_id?: string;
} = {}): Promise<ScenarioRun[]> {
const params = new URLSearchParams();
for (const [key, value] of Object.entries(filters)) {
if (value) {
params.set(key, value);
}
}
const query = params.toString();
return fetchJson(`/api/v1/scenario-runs${query ? `?${query}` : ""}`);
}
export async function createScenarioRun(
payload: CreateScenarioRun,
): Promise<ScenarioRunResponse> {
return fetchJson("/api/v1/scenario-runs", {
method: "POST",
body: JSON.stringify(payload),
});
}
async function fetchJson<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${API_BASE_URL}${path}`, {
...init,