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');