Auto-commit 2026-09-07 16:09: 6 files changed, 347 insertions(+)

This commit is contained in:
herzogflorian 2026-09-07 16:09:36 +02:00
parent d33357fd47
commit e9602a7aa4
6 changed files with 347 additions and 0 deletions

View File

@ -0,0 +1,55 @@
<!doctype html><html><head><meta charset="utf-8"><title>Sketch 1 – Portfolio overview</title><link rel="stylesheet" href="sketch.css"></head><body>
<div class="app">
<div class="topbar sk">
<div class="brand">Portfolio Intelligence Platform <small>thin dashboard · no business logic · calls the API</small></div>
<span class="pill ok">Market API: live</span>
<span class="pill err">LLM API: circuit OPEN</span>
<div class="nav"><span class="active">Portfolio</span><span>Market data</span><span>News &amp; Insights</span><span>Advisor</span><span>Optimisation</span><span>System</span></div>
</div>
<div class="banner err">&#9888; News insights and the advisor are <b>paused</b>: the LLM API circuit breaker opened at 09:12 (3 timeouts). All figures below are computed by deterministic services and are <b>unaffected</b>.</div>
<h1 class="page">Portfolio "Student Fund 01" <span class="small">· CHF 100 000 nominal · 6 positions</span></h1>
<div class="sub">Analysis as of last completed ingestion: <b>2026-09-07 06:00</b> (snapshot) · positions from <b>PortfolioService</b> · prices from <b>MarketDataService</b></div>
<div class="kpis">
<div class="kpi sk"><div class="l">Cumulative return (YTD)</div><div class="v">+12.4 %</div><div class="s">PerformanceService</div></div>
<div class="kpi sk"><div class="l">Sharpe ratio (1y)</div><div class="v">1.08</div><div class="s">PerformanceService</div></div>
<div class="kpi sk"><div class="l">Volatility (annualised)</div><div class="v">18.2 %</div><div class="s">RiskService</div></div>
<div class="kpi sk"><div class="l">Value-at-Risk 95 % / 1 day</div><div class="v">&minus;2.7 %</div><div class="s">RiskService</div></div>
</div>
<div class="grid" style="grid-template-columns: 1.55fr 1fr; margin-top: 12px;">
<div class="card sk">
<h3>Holdings <span class="src">PortfolioService · PerformanceService</span></h3>
<table class="sk-table">
<tr><th>Ticker</th><th>Name</th><th>Sector</th><th class="num">Qty</th><th class="num">Price</th><th class="num">Weight</th><th class="num">1d</th></tr>
<tr><td>NVDA</td><td>NVIDIA</td><td>Semiconductors</td><td class="num">120</td><td class="num">184.10</td><td class="num">22.1 %</td><td class="num">+1.9 %</td></tr>
<tr><td>ASML</td><td>ASML Holding</td><td>Semiconductors</td><td class="num">18</td><td class="num">742.30</td><td class="num">13.4 %</td><td class="num">+3.2 %</td></tr>
<tr><td>NESN</td><td>Nestlé</td><td>Consumer staples</td><td class="num">210</td><td class="num">86.40</td><td class="num">18.1 %</td><td class="num">&minus;0.4 %</td></tr>
<tr><td>ROG</td><td>Roche</td><td>Healthcare</td><td class="num">60</td><td class="num">268.90</td><td class="num">16.1 %</td><td class="num">+0.6 %</td></tr>
<tr><td>MSFT</td><td>Microsoft</td><td>Software</td><td class="num">45</td><td class="num">412.70</td><td class="num">18.6 %</td><td class="num">+0.8 %</td></tr>
<tr><td>CASH</td><td>CHF cash</td><td>—</td><td class="num">—</td><td class="num">—</td><td class="num">11.7 %</td><td class="num">0.0 %</td></tr>
</table>
<div class="small" style="margin-top:6px">Sector concentration: Semiconductors <b>35.5 %</b> (RiskService) · <span class="tag neu">exposure by sector →</span></div>
</div>
<div class="card sk">
<h3>Portfolio value, 12 months <span class="src">chart of PerformanceService output</span></h3>
<div style="height:170px">
<svg class="chart" viewBox="0 0 420 170">
<line class="axis" x1="30" y1="150" x2="410" y2="150"/><line class="axis" x1="30" y1="10" x2="30" y2="150"/>
<line class="grid" x1="30" y1="100" x2="410" y2="100"/><line class="grid" x1="30" y1="50" x2="410" y2="50"/>
<path class="fill" d="M30 120 L70 118 L110 126 L150 110 L190 112 L230 96 L270 102 L310 84 L350 90 L390 70 L405 66 L405 150 L30 150 Z"/>
<path d="M30 120 L70 118 L110 126 L150 110 L190 112 L230 96 L270 102 L310 84 L350 90 L390 70 L405 66"/>
<text x="34" y="20">CHF 115k</text><text x="34" y="146">CHF 95k</text><text x="300" y="165">months →</text>
</svg>
</div>
<div class="small">Latest validated insights (3) — see News &amp; Insights: <span class="tag pos">ASML guidance ↑</span><span class="tag neg">NESN recall</span><span class="tag neu">ROG trial data</span></div>
</div>
</div>
<div class="note" style="left: 560px; top: 118px;"><b>1</b>Graceful degradation (§4.2): when the LLM or a data API is down, deterministic analysis still works and the UI says so.</div>
<div class="note r" style="left: 1060px; top: 250px;"><b>2</b>Every figure names the deterministic service that computed it (§2.3) — agents never compute numbers.</div>
<div class="note" style="left: 40px; top: 690px;"><b>3</b>Freshness stamp: "as of last completed ingestion" — the advisor may never claim fresher data.</div>
<div class="watermark">SKETCH 1/7 · wireframe, not a specification</div>
</div></body></html>

View File

@ -0,0 +1,63 @@
<!doctype html><html><head><meta charset="utf-8"><title>Sketch 2 – Market data</title><link rel="stylesheet" href="sketch.css"></head><body>
<div class="app">
<div class="topbar sk">
<div class="brand">Portfolio Intelligence Platform <small>thin dashboard · no business logic · calls the API</small></div>
<span class="pill warn">Market API: rate-limited → snapshot</span>
<span class="pill ok">LLM API: closed (healthy)</span>
<div class="nav"><span>Portfolio</span><span class="active">Market data</span><span>News &amp; Insights</span><span>Advisor</span><span>Optimisation</span><span>System</span></div>
</div>
<div class="banner">&#9888; Live price API is <b>rate-limited</b> (HTTP 429, retry with backoff exhausted) — showing the <b>cached snapshot from 2026-09-05 18:00</b>. Figures are marked <span class="pill warn">snapshot</span> until the live feed returns.</div>
<div class="grid" style="grid-template-columns: 1fr 2.2fr 1fr;">
<div class="card sk">
<h3>Tickers <span class="src">configurable set</span></h3>
<div class="input" style="width:100%">🔍 filter…</div>
<ul class="list" style="list-style:none; padding-left:4px; margin-top:8px;">
<li>▸ <b>NVDA</b> <span class="small">NVIDIA</span></li>
<li>▸ <b>ASML</b> <span class="small">ASML Holding</span> <span class="tag">selected</span></li>
<li>▸ <b>NESN</b> <span class="small">Nestlé</span></li>
<li>▸ <b>ROG</b> <span class="small">Roche</span></li>
<li>▸ <b>MSFT</b> <span class="small">Microsoft</span></li>
</ul>
<div style="margin-top:12px"><span class="btn">+ add ticker</span></div>
<div class="small" style="margin-top:14px">Range: <span class="tag">1M</span><span class="tag">6M</span><span class="tag pos">1Y</span><span class="tag">5Y</span></div>
</div>
<div class="card sk">
<h3>ASML — daily close, 1 year <span class="src">MarketDataService v1 · normalised series</span></h3>
<div style="height:330px">
<svg class="chart" viewBox="0 0 640 330">
<line class="axis" x1="40" y1="300" x2="630" y2="300"/><line class="axis" x1="40" y1="15" x2="40" y2="300"/>
<line class="grid" x1="40" y1="230" x2="630" y2="230"/><line class="grid" x1="40" y1="160" x2="630" y2="160"/><line class="grid" x1="40" y1="90" x2="630" y2="90"/>
<path d="M40 240 L70 232 L100 250 L130 228 L160 236 L190 210 L220 224 L250 196 L280 204 L310 178 L340 188 L370 160 L400 172 L430 150 L460 158 L490 128 L520 140 L550 110 L580 118 L610 96"/>
<line x1="560" y1="15" x2="560" y2="300" stroke="#b8860b" stroke-width="2" stroke-dasharray="6 5"/>
<text x="566" y="30" style="fill:#b8860b">snapshot cut-off</text>
<text x="44" y="28">EUR 800</text><text x="44" y="296">EUR 500</text><text x="520" y="318">Sep 2026 →</text>
</svg>
</div>
<div class="small">Source: live API (e.g. Yahoo Finance) via <b>MarketDataService</b> · contract independent of the provider's format · last live fetch 2026-09-05 18:00 · <span class="btn">Refresh</span></div>
</div>
<div class="card sk">
<h3>Recent closes <span class="src">from the cache</span></h3>
<table class="sk-table">
<tr><th>Date</th><th class="num">Close</th><th>Src</th></tr>
<tr><td>2026-09-05</td><td class="num">742.30</td><td><span class="tag">cache</span></td></tr>
<tr><td>2026-09-04</td><td class="num">719.80</td><td><span class="tag">cache</span></td></tr>
<tr><td>2026-09-03</td><td class="num">724.10</td><td><span class="tag">cache</span></td></tr>
<tr><td>2026-09-02</td><td class="num">711.55</td><td><span class="tag">cache</span></td></tr>
<tr><td>2026-09-01</td><td class="num">703.90</td><td><span class="tag">cache</span></td></tr>
</table>
<h3 style="margin-top:14px">Feed health <span class="src">System → breakers</span></h3>
<ul class="list small">
<li>timeout 3 s · retries 3 (backoff) · breaker: <b>half-open</b></li>
<li>fallback: snapshot file <span class="mono">prices_2026-09-05.parquet</span></li>
<li>demo mode: <b>snapshot pinned</b> for reproducible grading</li>
</ul>
</div>
</div>
<div class="note" style="left: 700px; top: 100px;"><b>1</b>Mandatory cache/snapshot fallback when the live API is unavailable or rate-limited (§2.1).</div>
<div class="note r" style="left: 400px; top: 590px;"><b>2</b>Stable, versioned interface for downstream services — the provider's format never leaks (§2.1).</div>
<div class="note" style="left: 1040px; top: 640px;"><b>3</b>Resilience on every external call: timeout, retry with backoff, circuit breaker, defined fallback (§4.2).</div>
<div class="watermark">SKETCH 2/7 · wireframe, not a specification</div>
</div></body></html>

View File

@ -0,0 +1,47 @@
<!doctype html><html><head><meta charset="utf-8"><title>Sketch 3 – News and insights</title><link rel="stylesheet" href="sketch.css"></head><body>
<div class="app">
<div class="topbar sk">
<div class="brand">Portfolio Intelligence Platform <small>thin dashboard · no business logic · calls the API</small></div>
<span class="pill ok">Market API: live</span>
<span class="pill ok">LLM API: closed (healthy)</span>
<div class="nav"><span>Portfolio</span><span>Market data</span><span class="active">News &amp; Insights</span><span>Advisor</span><span>Optimisation</span><span>System</span></div>
</div>
<h1 class="page">News &amp; Insights <span class="small">· last ingestion run 2026-09-07 06:00 · 38 items · 31 insights validated · 4 rejected · 3 pending</span></h1>
<div class="sub">Raw news stored with provenance by <b>NewsIngestionService</b> · structured <b>Insight</b> extracted by <b>ResearchAgent</b> (schema-validated) · every insight checked against the <b>ontology</b> before it enters the system</div>
<div class="grid" style="grid-template-columns: 1.15fr 1fr;">
<div class="card sk">
<h3>Ingested news <span class="src">filter: <span class="tag pos">validated</span><span class="tag neg">rejected</span><span class="tag">all</span></span></h3>
<table class="sk-table">
<tr><th>ID</th><th>Headline</th><th>Source · time</th><th>Guard</th></tr>
<tr style="background:#eef3f7"><td class="mono">N-1023</td><td><b>ASML raises full-year 2026 guidance on AI demand</b></td><td class="small">Reuters · 07:12</td><td><span class="pill ok">✓ validated</span></td></tr>
<tr><td class="mono">N-1021</td><td>Nestlé recalls infant formula batch in three countries</td><td class="small">FT · 06:40</td><td><span class="pill ok">✓ validated</span></td></tr>
<tr><td class="mono">N-1019</td><td>Roche phase-III trial meets primary endpoint</td><td class="small">SwissInfo · 06:05</td><td><span class="pill ok">✓ validated</span></td></tr>
<tr><td class="mono">N-1017</td><td>"Acme-X to be acquired", says blog post</td><td class="small">blog · 05:50</td><td><span class="pill err">✗ rejected</span></td></tr>
<tr><td class="mono">N-1015</td><td>Fed minutes hint at slower cuts</td><td class="small">WSJ · 05:20</td><td><span class="pill neutral">⋯ pending</span></td></tr>
<tr><td class="mono">N-1012</td><td>Chip export rules: EU opens consultation</td><td class="small">Politico · 04:45</td><td><span class="pill ok">✓ validated</span></td></tr>
</table>
<div class="small" style="margin-top:8px">Rejected N-1017: ticker <span class="mono">ACMEX</span> not in ontology · event type "rumour" not allowed → insight <b>discarded</b>, raw text kept with provenance for audit.</div>
</div>
<div class="card sk">
<h3>Insight for N-1023 <span class="src">ResearchAgent → Insight (schema v3)</span></h3>
<table class="sk-table">
<tr><td style="width:120px"><b>Sentiment</b></td><td>
<div class="bar" style="width:220px; display:inline-block; vertical-align:middle"><i style="width:80%"></i></div> <b>+0.6</b> <span class="small">(−1 … +1)</span></td></tr>
<tr><td><b>Affected tickers</b></td><td><span class="tag">ASML</span><span class="tag">NVDA</span> <span class="small">(resolved against ontology)</span></td></tr>
<tr><td><b>Event type</b></td><td><span class="tag">guidance</span> <span class="small">∈ {earnings, guidance, M&amp;A, regulatory, product, recall, macro}</span></td></tr>
<tr><td><b>Summary</b></td><td>ASML lifts 2026 revenue guidance, citing AI-related demand for EUV systems; sees continued order strength into 2027.</td></tr>
<tr><td><b>Provenance</b></td><td class="mono">news_id=N-1023 · source=Reuters · fetched 2026-09-07T07:12Z · hash 9f3a…</td></tr>
<tr><td><b>Extraction</b></td><td class="small">model via LLM gateway · 1 412 tokens · 2.1 s · schema valid on first attempt</td></tr>
<tr><td><b>Guard</b></td><td><span class="pill ok">✓ ticker</span> <span class="pill ok">✓ sector</span> <span class="pill ok">✓ event type</span> <span class="pill ok">✓ citation resolvable</span></td></tr>
</table>
<div style="margin-top:10px"><span class="btn">Open source text</span> <span class="btn">Show raw extraction log (audit)</span></div>
<div class="small" style="margin-top:8px">The rest of the system never sees raw model text — only this validated object (anti-corruption layer, §4.1).</div>
</div>
</div>
<div class="note" style="left: 470px; top: 96px;"><b>1</b>Ontology as run-time guard: valid ticker? valid sector? plausible event type? (§3.2, §4.2)</div>
<div class="note r" style="left: 1060px; top: 560px;"><b>2</b>Strict, schema-validated Insight — reject or repair non-conforming responses (§4.1).</div>
<div class="note" style="left: 40px; top: 640px;"><b>3</b>News text is untrusted input: a prompt-injection vector — treat it as data, never as instructions (§4 pitfalls).</div>
<div class="watermark">SKETCH 3/7 · wireframe, not a specification</div>
</div></body></html>

View File

@ -0,0 +1,55 @@
<!doctype html><html><head><meta charset="utf-8"><title>Sketch 4 – Advisor</title><link rel="stylesheet" href="sketch.css"></head><body>
<div class="app">
<div class="topbar sk">
<div class="brand">Portfolio Intelligence Platform <small>thin dashboard · no business logic · calls the API</small></div>
<span class="pill ok">Market API: live</span>
<span class="pill ok">LLM API: closed (healthy)</span>
<div class="nav"><span>Portfolio</span><span>Market data</span><span>News &amp; Insights</span><span class="active">Advisor</span><span>Optimisation</span><span>System</span></div>
</div>
<div class="grid" style="grid-template-columns: 1.6fr 1fr;">
<div>
<div class="card sk" style="margin-bottom:10px">
<h3>Ask the advisor <span class="src">AdvisorAgent (orchestrator) · answers are explained and cited</span></h3>
<div class="input" style="width: 78%">Should I reduce my exposure to semiconductors after this week's news?</div> <span class="btn primary">Ask</span>
</div>
<div class="card sk">
<h3>Answer <span class="src">request r-4471 · 2026-09-07 09:31 · analysis as of 06:00 ingestion</span></h3>
<p style="margin:4px 0"><b>Summary.</b> Your semiconductor exposure is <b>35.5 %</b> of the portfolio, concentrated in two names. This week's validated news is <i>positive</i> for both, so the case for trimming rests on <i>concentration risk</i>, not on sentiment.</p>
<p style="margin:4px 0"><b>Evidence.</b> ASML raised 2026 guidance <span class="tag">N-1023</span>; EU consultation on chip export rules is a regulatory watch item <span class="tag">N-1012</span>. No validated negative item for NVDA this week.</p>
<p style="margin:4px 0"><b>Numbers</b> <span class="small">(computed by deterministic services, not by the agent)</span>: portfolio volatility <b>18.2 %</b>, VaR 95 %/1d <b>−2.7 %</b> (RiskService); semiconductor weight 35.5 %, top-2 concentration 40.7 % (RiskService).</p>
<p style="margin:4px 0"><b>Proposal.</b> A mean–variance run with max 25 % per sector suggests NVDA 15 % · ASML 10 % · NESN 18 % · ROG 18 % · MSFT 21 % · cash 18 % — volatility falls to <b>15.9 %</b> at −0.6 pp expected return (OptimizationService). <span class="btn">Open in Optimisation</span></p>
<p style="margin:4px 0"><b>Caveats.</b> Guidance-driven optimism can reverse; data freshness is the 06:00 ingestion; this is analysis, not investment advice or a trade.</p>
</div>
</div>
<div>
<div class="card sk" style="margin-bottom:10px">
<h3>Agent trace <span class="src">contracts only · no shared state</span></h3>
<table class="sk-table">
<tr><th>Step</th><th>Calls</th><th>Result</th></tr>
<tr><td>AdvisorAgent</td><td class="small">plan: 3 sub-tasks</td><td><span class="pill ok">✓</span></td></tr>
<tr><td>↳ ResearchAgent</td><td class="small">insights for NVDA, ASML (7d)</td><td><span class="pill ok">3 validated</span></td></tr>
<tr><td>↳ RiskAgent</td><td class="small">RiskService.vol, .var, .concentration</td><td><span class="pill ok">✓</span></td></tr>
<tr><td>↳ OptimizationAgent</td><td class="small">OptimizationService.mean_variance(max_sector=0.25)</td><td><span class="pill ok">✓</span></td></tr>
<tr><td>Ontology guard</td><td class="small">citations resolvable, tickers valid</td><td><span class="pill ok">✓ pass</span></td></tr>
<tr><td>AdvisorAgent</td><td class="small">merge + explain</td><td><span class="pill ok">✓</span></td></tr>
</table>
</div>
<div class="card sk">
<h3>Cost &amp; latency <span class="src">LLM gateway · per request</span></h3>
<table class="sk-table">
<tr><td>Tokens</td><td class="num">6 420</td><td class="small">4 LLM calls</td></tr>
<tr><td>Cost</td><td class="num">CHF 0.11</td><td class="small">budget p95 CHF 0.40 <span class="pill ok">ok</span></td></tr>
<tr><td>Latency</td><td class="num">8.4 s</td><td class="small">budget p95 20 s <span class="pill ok">ok</span></td></tr>
<tr><td>Model</td><td colspan="2" class="small">via gateway · fallback chain not used</td></tr>
</table>
<div class="small" style="margin-top:6px">Every call logged: prompt version, model, tokens, latency, guard verdict → audit trail.</div>
</div>
</div>
</div>
<div class="note" style="left: 560px; top: 150px;"><b>1</b>Agents interpret and explain; services compute (§2.5). An agent that "estimates" a volatility is an architecture defect.</div>
<div class="note r" style="left: 1070px; top: 330px;"><b>2</b>Sub-agents cooperate only through service contracts, never by sharing internal state (§2.5).</div>
<div class="note" style="left: 40px; top: 690px;"><b>3</b>Every claim carries a citation to a validated Insight; the freshness stamp bounds what may be claimed.</div>
<div class="watermark">SKETCH 4/7 · wireframe, not a specification</div>
</div></body></html>

View File

@ -0,0 +1,57 @@
<!doctype html><html><head><meta charset="utf-8"><title>Sketch 5 – Risk and optimisation</title><link rel="stylesheet" href="sketch.css"></head><body>
<div class="app">
<div class="topbar sk">
<div class="brand">Portfolio Intelligence Platform <small>thin dashboard · no business logic · calls the API</small></div>
<span class="pill ok">Market API: live</span>
<span class="pill ok">LLM API: closed (healthy)</span>
<div class="nav"><span>Portfolio</span><span>Market data</span><span>News &amp; Insights</span><span>Advisor</span><span class="active">Optimisation</span><span>System</span></div>
</div>
<h1 class="page">Risk &amp; optimisation <span class="small">· deterministic: same input → same output · pinned by reference test vectors</span></h1>
<div class="grid" style="grid-template-columns: 0.9fr 1.3fr 1fr;">
<div class="card sk">
<h3>Parameters <span class="src">OptimizationService</span></h3>
<div class="small">Objective</div><div class="input" style="width:100%">mean–variance (Markowitz)</div>
<div class="small" style="margin-top:6px">Risk target (annual vol)</div><div class="input" style="width:100%">16 %</div>
<div class="small" style="margin-top:6px">Max weight per asset</div><div class="input" style="width:100%">25 %</div>
<div class="small" style="margin-top:6px">Max weight per sector</div><div class="input" style="width:100%">25 %</div>
<div class="small" style="margin-top:6px">Constraints</div><div class="input" style="width:100%">long-only · fully invested</div>
<div class="small" style="margin-top:6px">Price history</div><div class="input" style="width:100%">1y · as of 2026-09-07 06:00</div>
<div style="margin-top:12px"><span class="btn primary">Run optimisation</span></div>
<div class="small" style="margin-top:10px">Tests: <span class="pill ok">42/42 reference vectors pass</span></div>
</div>
<div class="card sk">
<h3>Efficient frontier <span class="src">RiskService · OptimizationService</span></h3>
<div style="height:300px">
<svg class="chart" viewBox="0 0 520 300">
<line class="axis" x1="45" y1="270" x2="505" y2="270"/><line class="axis" x1="45" y1="15" x2="45" y2="270"/>
<path d="M120 240 C 150 150, 220 100, 330 70 S 470 40, 500 38"/>
<circle cx="330" cy="150" r="6" fill="#a83a2e"/><text x="340" y="154" style="fill:#a83a2e">current (18.2 %, 8.1 %)</text>
<circle cx="255" cy="104" r="6" fill="#3f7d4e"/><text x="120" y="98" style="fill:#3f7d4e">proposed (15.9 %, 7.5 %)</text>
<line x1="255" y1="104" x2="330" y2="150" stroke="#7a7a7a" stroke-dasharray="4 4"/>
<text x="400" y="290">volatility →</text><text x="50" y="28">return ↑</text>
</svg>
</div>
<div class="small">Frontier and both points computed by the services; the chart only draws their output.</div>
</div>
<div class="card sk">
<h3>Allocation: current → proposed <span class="src">OptimizationService</span></h3>
<table class="sk-table">
<tr><th>Asset</th><th class="num">Now</th><th class="num">Proposed</th><th></th></tr>
<tr><td>NVDA</td><td class="num">22.1 %</td><td class="num">15.0 %</td><td><div class="bar warn" style="width:70px"><i style="width:60%"></i></div></td></tr>
<tr><td>ASML</td><td class="num">13.4 %</td><td class="num">10.0 %</td><td><div class="bar warn" style="width:70px"><i style="width:40%"></i></div></td></tr>
<tr><td>NESN</td><td class="num">18.1 %</td><td class="num">18.0 %</td><td><div class="bar" style="width:70px"><i style="width:72%"></i></div></td></tr>
<tr><td>ROG</td><td class="num">16.1 %</td><td class="num">18.0 %</td><td><div class="bar" style="width:70px"><i style="width:72%"></i></div></td></tr>
<tr><td>MSFT</td><td class="num">18.6 %</td><td class="num">21.0 %</td><td><div class="bar" style="width:70px"><i style="width:84%"></i></div></td></tr>
<tr><td>Cash</td><td class="num">11.7 %</td><td class="num">18.0 %</td><td><div class="bar" style="width:70px"><i style="width:72%"></i></div></td></tr>
</table>
<h3 style="margin-top:12px">Explanation <span class="src">OptimizationAgent</span></h3>
<p class="small" style="margin:4px 0">"The proposal trims the two semiconductor names to respect the 25 % sector cap, which removes most of the concentration risk; expected return falls by 0.6 pp while volatility falls by 2.3 pp. The cash weight is a by-product of the caps, not a market view."</p>
<div class="small">Figures quoted from the service output · no number originates in the agent.</div>
</div>
</div>
<div class="note" style="left: 330px; top: 96px;"><b>1</b>Performance, Risk, Optimization services are pure and deterministic; exact tests against the provided reference vectors (§2.3, §4.3).</div>
<div class="note r" style="left: 1040px; top: 600px;"><b>2</b>Agents explain the numbers; the OptimizationAgent calls OptimizationService and never computes an allocation itself (§2.5).</div>
<div class="watermark">SKETCH 5/7 · wireframe, not a specification</div>
</div></body></html>

View File

@ -0,0 +1,70 @@
/* Low-fidelity wireframe style for the Portfolio Intelligence Platform sketches.
Rendered at 1280 x 800 (16:10) for the project presentation. */
:root {
--ink: #3a3a3a; --ink-light: #7a7a7a; --paper: #f6f4ee; --card: #fffdf7;
--line: #555; --accent: #2f5d7c; --ok: #3f7d4e; --warn: #b8860b; --err: #a83a2e;
--note: #fff6a8; --grey: #e9e6df;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; background: var(--paper); color: var(--ink);
font-family: "Chalkboard SE", "Comic Sans MS", "Marker Felt", cursive; font-size: 15px; }
.app { width: 1280px; height: 800px; position: relative; overflow: hidden; padding: 14px 18px; }
.sk { border: 2.2px solid var(--line); background: var(--card);
border-radius: 255px 15px 225px 15px / 15px 225px 15px 255px; }
.sk.grey { background: var(--grey); }
.topbar { display: flex; align-items: center; gap: 18px; padding: 8px 16px; margin-bottom: 12px; }
.brand { font-weight: bold; font-size: 20px; }
.brand small { font-weight: normal; color: var(--ink-light); font-size: 12px; margin-left: 8px; }
.nav { display: flex; gap: 6px; margin-left: auto; }
.nav span { padding: 4px 10px; border: 1.8px solid var(--line); border-radius: 12px 4px 12px 4px; background: #fff; }
.nav span.active { background: #dfe8ef; font-weight: bold; }
.pill { display: inline-block; padding: 2px 9px; border: 1.8px solid; border-radius: 14px; font-size: 12.5px; white-space: nowrap; }
.pill.ok { border-color: var(--ok); color: var(--ok); }
.pill.warn { border-color: var(--warn); color: var(--warn); }
.pill.err { border-color: var(--err); color: var(--err); }
.pill.neutral { border-color: var(--ink-light); color: var(--ink-light); }
h1.page { font-size: 24px; margin: 0 0 4px 0; }
.sub { color: var(--ink-light); font-size: 13px; margin-bottom: 10px; }
.grid { display: grid; gap: 12px; }
.card { padding: 10px 14px; }
.card h3 { margin: 0 0 6px 0; font-size: 15px; border-bottom: 1.5px dashed var(--line); padding-bottom: 3px; }
.card h3 .src { float: right; font-size: 11px; color: var(--ink-light); font-weight: normal; }
table.sk-table { width: 100%; border-collapse: collapse; font-size: 13px; }
table.sk-table th { text-align: left; border-bottom: 2px solid var(--line); padding: 3px 6px; }
table.sk-table td { border-bottom: 1px dashed #aaa; padding: 3px 6px; }
table.sk-table td.num, table.sk-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
.kpi { padding: 8px 12px; }
.kpi .v { font-size: 26px; font-weight: bold; }
.kpi .l { font-size: 12px; color: var(--ink-light); }
.kpi .s { font-size: 11px; color: var(--accent); }
.btn { display: inline-block; padding: 5px 12px; border: 2px solid var(--line); border-radius: 9px 3px 9px 3px; background: #fff; font-weight: bold; }
.btn.primary { background: #dfe8ef; }
.input { display: inline-block; padding: 5px 10px; border: 1.8px solid var(--line); border-radius: 4px; background: #fff; color: var(--ink-light); min-width: 160px; }
.banner { padding: 8px 14px; border: 2.2px solid var(--warn); background: #fff3d6; border-radius: 12px 4px 12px 4px; margin-bottom: 10px; font-size: 14px; }
.banner.err { border-color: var(--err); background: #fbe3df; }
.banner.ok { border-color: var(--ok); background: #e5f1e6; }
.chart { width: 100%; height: 100%; }
.chart path, .chart polyline { fill: none; stroke: var(--ink); stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.chart .axis { stroke: var(--ink-light); stroke-width: 1.5; }
.chart .grid { stroke: #ccc; stroke-width: 1; stroke-dasharray: 4 4; }
.chart text { font-family: inherit; font-size: 11px; fill: var(--ink-light); }
.chart .fill { fill: #dfe8ef; stroke: none; }
.note { position: absolute; width: 190px; padding: 8px 10px; background: var(--note); border: 1.5px solid #c9b45a;
box-shadow: 2px 3px 0 #d8cf9a; font-size: 12px; line-height: 1.25; transform: rotate(-2deg); z-index: 5; }
.note b { display: inline-block; width: 18px; height: 18px; border-radius: 50%; background: var(--accent); color: #fff;
text-align: center; line-height: 18px; font-size: 12px; margin-right: 4px; }
.note.r { transform: rotate(1.5deg); }
.watermark { position: absolute; right: 22px; bottom: 10px; font-size: 12px; color: var(--ink-light); letter-spacing: 1px; }
.tag { display: inline-block; padding: 1px 7px; border: 1.5px solid var(--line); border-radius: 8px; font-size: 11.5px; background: #fff; margin-right: 3px; }
.tag.pos { border-color: var(--ok); color: var(--ok); }
.tag.neg { border-color: var(--err); color: var(--err); }
.tag.neu { border-color: var(--ink-light); color: var(--ink-light); }
.small { font-size: 12px; color: var(--ink-light); }
.mono { font-family: "Menlo", monospace; font-size: 11.5px; }
.list { margin: 4px 0 0 0; padding-left: 18px; }
.list li { margin: 2px 0; }
.bar { height: 14px; background: #dfe8ef; border: 1.5px solid var(--line); border-radius: 3px; position: relative; }
.bar i { display: block; height: 100%; background: #7fa3bd; }
.bar.warn i { background: #e0c068; }
.arrow { font-size: 26px; color: var(--ink-light); }