/* 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;