Spaces:
Sleeping
Sleeping
File size: 5,583 Bytes
379bf78 8f1c2fc 379bf78 8f1c2fc 379bf78 8f1c2fc 379bf78 8f1c2fc 379bf78 8f1c2fc c37013e 023f23c c37013e 8f1c2fc 379bf78 20abf65 379bf78 8f1c2fc 6675237 8f1c2fc 1da60b4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | /**
* API client — coded ONLY against docs/api_contract.md (the seam contract).
* Every function below returns the exact JSON the contract documents; the
* only "adapters" here reshape a contract payload into a chart-friendly
* array (e.g. waterfall rows -> {start, steps, end}), never invent a field.
* All money fields are raw USD floats; components divide by 1e6 themselves.
*/
const json = async (res) => {
if (!res.ok) {
let detail = res.statusText;
try {
const body = await res.json();
detail = body?.detail ? JSON.stringify(body.detail) : detail;
} catch {
/* non-JSON error body */
}
throw new Error(`HTTP ${res.status}: ${detail}`);
}
return res.json();
};
const get = (url) => fetch(url).then(json);
const post = (url, body) =>
fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}).then(json);
// --------------------------------------------------------------- 1. engine
export const getHealth = () => get('/api/health');
export const getSummary = () => get('/api/ecl/summary');
export const getWaterfall = (t0 = 20, t1 = 40) =>
get(`/api/ecl/waterfall?t0=${t0}&t1=${t1}`);
export const getCreditCycle = () => get('/api/exhibits/credit_cycle');
/** Shared row shape for /api/ecl/waterfall and a tool's waterfall_vs_baseline
* (identical 6-row schema: opening, stage_migration, remeasurement,
* derecognitions, new_loans, closing). */
export const adaptWaterfallRows = (rows, startLabel, endLabel) => {
const level = (name) => rows.find((c) => c.component === name);
return {
start: { label: startLabel, value_m: (level('opening')?.amount ?? 0) / 1e6 },
steps: rows
.filter((c) => c.component !== 'opening' && c.component !== 'closing')
.map((c) => ({
label: c.component.replace(/_/g, ' '),
delta_m: c.amount / 1e6,
})),
end: { label: endLabel, value_m: (level('closing')?.amount ?? 0) / 1e6 },
};
};
// -------------------------------------------------------------- tier-1 tools
export const shockMacro = (v, shock, shape = 'parallel') =>
post('/api/tools/shock_macro', { var: v, shock, shape });
export const reweightScenarios = (w_up, w_base, w_down) =>
post('/api/tools/reweight_scenarios', { w_up, w_base, w_down });
export const rerunEcl = (segment = 'all') =>
post('/api/tools/rerun_ecl', { segment });
export const decomposeWaterfall = (t0, t1) =>
post('/api/tools/decompose_waterfall', { t0, t1 });
export const TOOL_FN = {
shock_macro: shockMacro,
reweight_scenarios: reweightScenarios,
rerun_ecl: rerunEcl,
decompose_waterfall: decomposeWaterfall,
};
// -------------------------------------------------------------------- agent
export const askAgent = (question) => post('/api/agent/ask', { question });
// ---------------------------------------------------- explain-prefix conventions
// docs/api_contract.md §"AI-explain question prefixes" — both conventions
// REUSE POST /api/agent/ask (never a bespoke endpoint) so every explain
// click passes the same tools/Tier-2/Tier-3/refusal governance
// (FINAL_SPEC.md §7.5). Keep these two builders as the single source of the
// exact wire text so the UI and the contract-test stay byte-identical.
/** Panel/tile explain convention (FINAL_SPEC §7.5, graft 4): a bracketed
* routing tag with live params, an Exhibit label, and a CODE-GENERATED recap
* of the exact figures the panel/tile is rendering right now (built from the
* same payload that drew it — never free text). */
export const explainPanelQuestion = ({ panelId, params = {}, exhibitLabel, title, recap }) => {
const paramStr = Object.entries(params)
.map(([k, v]) => `${k}=${v}`)
.join(' ');
const tag = `[explain:${panelId}${paramStr ? ` ${paramStr}` : ''}]`;
const label = exhibitLabel ? `${exhibitLabel} — ${title}` : title;
return `${tag} ${label}: ${recap} What should I take from this?`;
};
/** Selection-explain convention: the user highlighted text anywhere in the
* main area; ground the question in which tab they were reading. */
export const explainSelectionQuestion = (tabLabel, selectedText) => {
const trimmed = String(selectedText || '').trim().slice(0, 300);
return `Explain, in the context of the ${tabLabel} tab: "${trimmed}"`;
};
export const AGENT_STREAM_URL = '/api/agent/stream';
/** Auto-interpretation: {tool, result} -> {interpretation, grounded, mode}. */
export const interpretResult = (tool, result) =>
post('/api/agent/interpret', { tool, result });
// ------------------------------------------------------------ The Model tab
export const getModelCoefficients = () => get('/api/model/coefficients');
export const getVariableDictionary = () => get('/api/model/variable_dictionary');
export const getMacroGlossary = () => get('/api/model/macro_glossary');
export const getLgd = () => get('/api/model/lgd');
// ----------------------------------------------------------- The Policy tab
export const getStagingSensitivity = () => get('/api/policy/staging_sensitivity');
export const getWeightsTable = () => get('/api/policy/weights_table');
export const getExhibitsList = () => get('/api/exhibits/list');
// ------------------------------------------------------ The Real Data tab
export const getFreddieSummary = () => get('/api/freddie/summary');
export const getFreddieHazard = () => get('/api/freddie/hazard');
export const getFreddieBacktest = () => get('/api/freddie/backtest');
export const getFreddieExhibits = () => get('/api/freddie/exhibits');
|