/* global React, DATA, Panel, mdToHtml, fmtSAR, fmtN */
// Static metadata so the page header + ease/impact render instantly (pre-fetch),
// tying each agent to the implementation roadmap scoring.
const AGENT_META = {
A1: { fam: 'Family A · Forecasting & Demand Sensing', title: 'Event-Impact Forecasting Agent', sub: 'Reads the KSA event calendar and writes structured demand adjustments', ease: 5, rev: 5 },
A2: { fam: 'Family A · Forecasting & Demand Sensing', title: 'Multi-Signal Demand Sensing', sub: 'Fuses booking pace, search/shop, competitor, weather & macro into T-14→T-0 corrections', ease: 3, rev: 4 },
A3: { fam: 'Family A · Forecasting & Demand Sensing', title: 'Forecast Anomaly Auto-Explanation', sub: 'Flags unusual booking curves / forecast drifts and writes the likely cause', ease: 5, rev: 3 },
B1: { fam: 'Family B · Pricing & Optimization Agents', title: 'Autonomous Bid-Price Agent (HITL)', sub: 'Proposes bid-price overrides by O&D from the updated forecast + competitor position', ease: 3, rev: 5 },
B2: { fam: 'Family B · Pricing & Optimization Agents', title: 'Competitive Response Agent', sub: 'Classifies competitor fare moves and recommends a response', ease: 3, rev: 4 },
B3: { fam: 'Family B · Pricing & Optimization Agents', title: 'Overbooking & No-Show Agent', sub: 'Recommends overbooking authorisation net of denied-boarding cost', ease: 4, rev: 5 },
B4: { fam: 'Family B · Pricing & Optimization Agents', title: 'Class Closure & Inventory Agent', sub: 'Recommends opening / closing booking classes by flight', ease: 4, rev: 4 },
D1: { fam: 'Family D · Commercial Workflows', title: 'Group & Charter Quote Agent', sub: 'Drafts group / charter quotes from RFP + displacement cost', ease: 4, rev: 5 },
D2: { fam: 'Family D · Commercial Workflows', title: 'Ancillary & Bundle Personalization', sub: 'Selects the right ancillary offer & bundle per passenger cohort', ease: 3, rev: 5 },
D3: { fam: 'Family D · Commercial Workflows', title: 'IROPS & Disruption Recovery', sub: 'Proposes re-accommodation, compensation & rebooking on disruption', ease: 2, rev: 5 },
E1: { fam: 'Family E · Explainability & Governance', title: 'Decision Explainability Layer', sub: 'Plain-language, audit-ready explanations for RM decisions', ease: 4, rev: 3 },
E2: { fam: 'Family E · Explainability & Governance', title: 'Pricing Audit & Compliance Agent', sub: 'Audits filed fares against policy, fare-rule & competition-law guardrails', ease: 3, rev: 3 },
};
const Dots = ({ n, label }) => React.createElement('div', { className: 'row between', style: { alignItems: 'center', gap: 14 } },
React.createElement('span', { className: 'label' }, label),
React.createElement('span', { style: { whiteSpace: 'nowrap', flexShrink: 0 } },
[1, 2, 3, 4, 5].map(i => React.createElement('span', { key: i, style: {
width: 8, height: 8, borderRadius: '50%', display: 'inline-block', marginLeft: 5,
background: i <= n ? 'var(--accent)' : 'var(--paper-edge)', border: '1px solid var(--rule-soft)' } }))));
const prioColor = p => p === 'High' ? 'var(--neg)' : p === 'Medium' ? 'var(--amber)' : 'var(--accent)';
const RecCard = ({ item, acted, onAct }) => {
const p = item.priority || 'Medium';
return React.createElement('div', { className: 'panel', style: { borderLeft: `4px solid ${prioColor(p)}`, opacity: acted === 'rejected' ? 0.5 : 1 } },
React.createElement('div', { className: 'panel-body', style: { padding: '14px 16px' } },
React.createElement('div', { className: 'row between', style: { marginBottom: 6, gap: 8 } },
React.createElement('div', { className: 'row gap-1', style: { flexWrap: 'wrap' } },
item.tag ? React.createElement('span', { className: 'pill pill-brand' }, item.tag) : null,
React.createElement('span', { className: 'pill pill-outline' }, p + ' priority'),
item.confidence ? React.createElement('span', { className: 'pill pill-info' }, item.confidence + ' confidence') : null),
item.expected_impact_sar != null ? React.createElement('span', {
className: 'mono', style: { fontWeight: 700, fontSize: 15, color: item.expected_impact_sar >= 0 ? 'var(--green)' : 'var(--neg)' } },
(item.expected_impact_sar >= 0 ? '+' : '') + fmtSAR(item.expected_impact_sar)) : null),
React.createElement('div', { style: { fontFamily: 'var(--serif)', fontSize: 22, fontWeight: 600, color: 'var(--ink)', lineHeight: 1.2 } }, item.title),
item.target && item.target !== item.title ? React.createElement('div', { className: 'mono', style: { fontSize: 12.5, color: 'var(--text-muted)', marginBottom: 4 } }, item.target) : null,
React.createElement('div', { style: { fontWeight: 700, fontSize: 15.5, margin: '8px 0 5px', lineHeight: 1.4, color: 'var(--accent-bright, var(--accent))' } }, '▸ ' + item.action),
React.createElement('div', { style: { fontSize: 15, color: 'var(--ink-2)', lineHeight: 1.55 } }, item.rationale),
item.metric ? React.createElement('div', { className: 'mono', style: { fontSize: 12.5, color: 'var(--text-dim)', marginTop: 6 } }, item.metric) : null,
React.createElement('div', { className: 'row gap-1', style: { marginTop: 10 } },
acted ? React.createElement('span', { className: 'pill ' + (acted === 'accepted' ? 'pill-pos' : acted === 'edited' ? 'pill-warn' : 'pill-neg') },
acted === 'accepted' ? '✓ Approved' : acted === 'edited' ? '✎ Edited' : '✕ Rejected')
: React.createElement(React.Fragment, null,
React.createElement('button', { className: 'btn btn-sm btn-buy', onClick: () => onAct('accepted') }, 'Approve'),
React.createElement('button', { className: 'btn btn-sm', onClick: () => onAct('edited') }, 'Edit'),
React.createElement('button', { className: 'btn btn-sm btn-sell', onClick: () => onAct('rejected') }, 'Reject')))));
};
// auto-tabulate the largest array of objects found in the agent's context fact-pack
const EvidenceTable = ({ context }) => {
if (!context) return null;
let best = null, bestKey = null;
for (const [k, v] of Object.entries(context)) {
if (Array.isArray(v) && v.length && typeof v[0] === 'object' && (!best || v.length > best.length)) { best = v; bestKey = k; }
}
if (!best) return null;
const cols = Object.keys(best[0]).slice(0, 8);
const fmtCell = c => typeof c === 'number' ? (Number.isInteger(c) ? fmtN(c) : c.toFixed(1)) : String(c == null ? '—' : c);
return React.createElement(Panel, { title: 'Evidence', sub: `Deterministic fact-pack · ${bestKey.replace(/_/g, ' ')}`, flush: true },
React.createElement('div', { style: { maxHeight: 360, overflowY: 'auto' } },
React.createElement('table', { className: 'tbl tbl-zebra' },
React.createElement('thead', null, React.createElement('tr', null,
cols.map((c, i) => React.createElement('th', { key: i, className: typeof best[0][c] === 'number' ? 'num' : '' }, c.replace(/_/g, ' '))))),
React.createElement('tbody', null,
best.slice(0, 60).map((r, i) => React.createElement('tr', { key: i },
cols.map((c, j) => React.createElement('td', { key: j, className: typeof r[c] === 'number' ? 'num mono' : '' }, fmtCell(r[c])))))))));
};
const AgentView = ({ code }) => {
const meta = AGENT_META[code] || { fam: '', title: code, sub: '', ease: 3, rev: 3 };
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [err, setErr] = useState(null);
const [acted, setActed] = useState({});
useEffect(() => {
let live = true; setLoading(true); setErr(null); setData(null); setActed({});
fetch((window.API_BASE_URL || '') + '/api/agent/' + code, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ params: {} }) })
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(d => { if (live) { setData(d); setLoading(false); } })
.catch(e => { if (live) { setErr(e.message); setLoading(false); } });
return () => { live = false; };
}, [code]);
const items = (data && data.items) || [];
const accepted = Object.values(acted).filter(a => a === 'accepted').length;
const totalImpact = items.reduce((s, it, i) => s + (acted[i] === 'accepted' ? (it.expected_impact_sar || 0) : 0), 0);
return React.createElement('div', { className: 'page' },
React.createElement('div', { className: 'page-head' },
React.createElement('div', { className: 'row between', style: { alignItems: 'flex-end', flexWrap: 'wrap', gap: 12 } },
React.createElement('div', null,
React.createElement('div', { className: 'eyebrow', style: { marginBottom: 8 } }, meta.fam, ' · ', code),
React.createElement('h1', { className: 'headline', style: { fontSize: 42 } }, meta.title),
React.createElement('div', { className: 'subhead' }, meta.sub)),
React.createElement('div', { className: 'boxed', style: { minWidth: 190 } },
React.createElement(Dots, { n: meta.ease, label: 'Ease' }),
React.createElement('div', { style: { height: 6 } }),
React.createElement(Dots, { n: meta.rev, label: 'Rev. impact' })))),
React.createElement('div', { className: 'banner', style: { marginBottom: 20 } },
React.createElement('span', { className: 'banner-glyph' }, '◆'),
React.createElement('div', { style: { flex: 1 } },
React.createElement('div', { className: 'banner-title', style: { fontSize: 15 } }, 'Agent proposes · analyst decides'),
React.createElement('div', { className: 'banner-body' },
'Every recommendation below is generated live by Claude over the warehouse and routed for human approval — nothing is actioned autonomously in this MVP. ',
React.createElement('strong', null, '⏳ Output is generated on the fly each time you open this page, so please allow ~10–30 seconds for it to load.'))),
accepted ? React.createElement('span', { className: 'pill pill-pos' }, `${accepted} approved · ${fmtSAR(totalImpact)} impact`) : null),
loading ? React.createElement('div', { style: { padding: 40, textAlign: 'center', color: 'var(--text-muted)', fontStyle: 'italic', fontSize: 15 } },
'· · · the agent is reading the warehouse and reasoning over the fact-pack — this usually takes ~10–30 seconds, please hold on')
: err ? React.createElement('div', { className: 'text-neg', style: { padding: 16 } }, 'Agent failed: ' + err)
: React.createElement('div', null,
data.summary ? React.createElement(Panel, { className: '', }, React.createElement('div', { dangerouslySetInnerHTML: { __html: mdToHtml(data.summary) }, style: { fontSize: 14.5, lineHeight: 1.55 } })) : null,
React.createElement('div', { className: 'section-rule' }),
React.createElement('div', { className: 'row between', style: { marginBottom: 12 } },
React.createElement('div', { className: 'section-h' }, 'Recommendations'),
React.createElement('span', { className: 'text-muted', style: { fontSize: 12 } }, `${items.length} proposals`)),
React.createElement('div', { className: 'grid grid-2 gap-3', style: { marginBottom: 24 } },
items.map((it, i) => React.createElement(RecCard, { key: i, item: it, acted: acted[i], onAct: (a) => setActed(s => ({ ...s, [i]: a })) }))),
React.createElement(EvidenceTable, { context: data.context })));
};
window.AgentView = AgentView;