Preetomsorkar's picture
Ship macro/FRED interpretation feature: per-variable coefficient interpretation fields (unit_meaning/transformation/lag/fred_series/economic_channel/hazard_ratio_per_unit/worked_example) across variable_dictionary, coefficients, macro_glossary, freddie/hazard; UI hazard-ratio column + expandable interpretation panels + How-to-read intro panels.
6675237 verified
Raw
History Blame Contribute Delete
5.58 kB
/**
* 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');