diff --git a/project_exercise/ui_sketches/01_portfolio_dashboard.html b/project_exercise/ui_sketches/01_portfolio_dashboard.html new file mode 100644 index 0000000..bbe5344 --- /dev/null +++ b/project_exercise/ui_sketches/01_portfolio_dashboard.html @@ -0,0 +1,55 @@ +Sketch 1 – Portfolio overview +
+
+
Portfolio Intelligence Platform thin dashboard · no business logic · calls the API
+ Market API: live + LLM API: circuit OPEN + +
+ + + +

Portfolio "Student Fund 01" · CHF 100 000 nominal · 6 positions

+
Analysis as of last completed ingestion: 2026-09-07 06:00 (snapshot) · positions from PortfolioService · prices from MarketDataService
+ +
+
Cumulative return (YTD)
+12.4 %
PerformanceService
+
Sharpe ratio (1y)
1.08
PerformanceService
+
Volatility (annualised)
18.2 %
RiskService
+
Value-at-Risk 95 % / 1 day
−2.7 %
RiskService
+
+ +
+
+

Holdings PortfolioService · PerformanceService

+ + + + + + + + +
TickerNameSectorQtyPriceWeight1d
NVDANVIDIASemiconductors120184.1022.1 %+1.9 %
ASMLASML HoldingSemiconductors18742.3013.4 %+3.2 %
NESNNestléConsumer staples21086.4018.1 %−0.4 %
ROGRocheHealthcare60268.9016.1 %+0.6 %
MSFTMicrosoftSoftware45412.7018.6 %+0.8 %
CASHCHF cash———11.7 %0.0 %
+
Sector concentration: Semiconductors 35.5 % (RiskService) · exposure by sector →
+
+
+

Portfolio value, 12 months chart of PerformanceService output

+
+ + + + + + CHF 115kCHF 95kmonths → + +
+
Latest validated insights (3) — see News & Insights: ASML guidance ↑NESN recallROG trial data
+
+
+ +
1Graceful degradation (§4.2): when the LLM or a data API is down, deterministic analysis still works and the UI says so.
+
2Every figure names the deterministic service that computed it (§2.3) — agents never compute numbers.
+
3Freshness stamp: "as of last completed ingestion" — the advisor may never claim fresher data.
+
SKETCH 1/7 · wireframe, not a specification
+
diff --git a/project_exercise/ui_sketches/02_market_data.html b/project_exercise/ui_sketches/02_market_data.html new file mode 100644 index 0000000..01e7665 --- /dev/null +++ b/project_exercise/ui_sketches/02_market_data.html @@ -0,0 +1,63 @@ +Sketch 2 – Market data +
+
+
Portfolio Intelligence Platform thin dashboard · no business logic · calls the API
+ Market API: rate-limited → snapshot + LLM API: closed (healthy) + +
+ + + +
+
+

Tickers configurable set

+
🔍 filter…
+
    +
  • ▸ NVDA NVIDIA
  • +
  • ▸ ASML ASML Holding selected
  • +
  • ▸ NESN Nestlé
  • +
  • ▸ ROG Roche
  • +
  • ▸ MSFT Microsoft
  • +
+
+ add ticker
+
Range: 1M6M1Y5Y
+
+
+

ASML — daily close, 1 year MarketDataService v1 · normalised series

+
+ + + + + + snapshot cut-off + EUR 800EUR 500Sep 2026 → + +
+
Source: live API (e.g. Yahoo Finance) via MarketDataService · contract independent of the provider's format · last live fetch 2026-09-05 18:00 · Refresh
+
+
+

Recent closes from the cache

+ + + + + + + +
DateCloseSrc
2026-09-05742.30cache
2026-09-04719.80cache
2026-09-03724.10cache
2026-09-02711.55cache
2026-09-01703.90cache
+

Feed health System → breakers

+
    +
  • timeout 3 s · retries 3 (backoff) · breaker: half-open
  • +
  • fallback: snapshot file prices_2026-09-05.parquet
  • +
  • demo mode: snapshot pinned for reproducible grading
  • +
+
+
+ +
1Mandatory cache/snapshot fallback when the live API is unavailable or rate-limited (§2.1).
+
2Stable, versioned interface for downstream services — the provider's format never leaks (§2.1).
+
3Resilience on every external call: timeout, retry with backoff, circuit breaker, defined fallback (§4.2).
+
SKETCH 2/7 · wireframe, not a specification
+
diff --git a/project_exercise/ui_sketches/03_news_insights.html b/project_exercise/ui_sketches/03_news_insights.html new file mode 100644 index 0000000..a773816 --- /dev/null +++ b/project_exercise/ui_sketches/03_news_insights.html @@ -0,0 +1,47 @@ +Sketch 3 – News and insights +
+
+
Portfolio Intelligence Platform thin dashboard · no business logic · calls the API
+ Market API: live + LLM API: closed (healthy) + +
+

News & Insights · last ingestion run 2026-09-07 06:00 · 38 items · 31 insights validated · 4 rejected · 3 pending

+
Raw news stored with provenance by NewsIngestionService · structured Insight extracted by ResearchAgent (schema-validated) · every insight checked against the ontology before it enters the system
+ +
+
+

Ingested news filter: validatedrejectedall

+ + + + + + + + +
IDHeadlineSource · timeGuard
N-1023ASML raises full-year 2026 guidance on AI demandReuters · 07:12✓ validated
N-1021Nestlé recalls infant formula batch in three countriesFT · 06:40✓ validated
N-1019Roche phase-III trial meets primary endpointSwissInfo · 06:05✓ validated
N-1017"Acme-X to be acquired", says blog postblog · 05:50✗ rejected
N-1015Fed minutes hint at slower cutsWSJ · 05:20⋯ pending
N-1012Chip export rules: EU opens consultationPolitico · 04:45✓ validated
+
Rejected N-1017: ticker ACMEX not in ontology · event type "rumour" not allowed → insight discarded, raw text kept with provenance for audit.
+
+
+

Insight for N-1023 ResearchAgent → Insight (schema v3)

+ + + + + + + + +
Sentiment +
+0.6 (−1 … +1)
Affected tickersASMLNVDA (resolved against ontology)
Event typeguidance ∈ {earnings, guidance, M&A, regulatory, product, recall, macro}
SummaryASML lifts 2026 revenue guidance, citing AI-related demand for EUV systems; sees continued order strength into 2027.
Provenancenews_id=N-1023 · source=Reuters · fetched 2026-09-07T07:12Z · hash 9f3a…
Extractionmodel via LLM gateway · 1 412 tokens · 2.1 s · schema valid on first attempt
Guard✓ ticker ✓ sector ✓ event type ✓ citation resolvable
+
Open source text Show raw extraction log (audit)
+
The rest of the system never sees raw model text — only this validated object (anti-corruption layer, §4.1).
+
+
+ +
1Ontology as run-time guard: valid ticker? valid sector? plausible event type? (§3.2, §4.2)
+
2Strict, schema-validated Insight — reject or repair non-conforming responses (§4.1).
+
3News text is untrusted input: a prompt-injection vector — treat it as data, never as instructions (§4 pitfalls).
+
SKETCH 3/7 · wireframe, not a specification
+
diff --git a/project_exercise/ui_sketches/04_advisor.html b/project_exercise/ui_sketches/04_advisor.html new file mode 100644 index 0000000..fcc6f0d --- /dev/null +++ b/project_exercise/ui_sketches/04_advisor.html @@ -0,0 +1,55 @@ +Sketch 4 – Advisor +
+
+
Portfolio Intelligence Platform thin dashboard · no business logic · calls the API
+ Market API: live + LLM API: closed (healthy) + +
+ +
+
+
+

Ask the advisor AdvisorAgent (orchestrator) · answers are explained and cited

+
Should I reduce my exposure to semiconductors after this week's news?
Ask +
+
+

Answer request r-4471 · 2026-09-07 09:31 · analysis as of 06:00 ingestion

+

Summary. Your semiconductor exposure is 35.5 % of the portfolio, concentrated in two names. This week's validated news is positive for both, so the case for trimming rests on concentration risk, not on sentiment.

+

Evidence. ASML raised 2026 guidance N-1023; EU consultation on chip export rules is a regulatory watch item N-1012. No validated negative item for NVDA this week.

+

Numbers (computed by deterministic services, not by the agent): portfolio volatility 18.2 %, VaR 95 %/1d −2.7 % (RiskService); semiconductor weight 35.5 %, top-2 concentration 40.7 % (RiskService).

+

Proposal. 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 15.9 % at −0.6 pp expected return (OptimizationService). Open in Optimisation

+

Caveats. Guidance-driven optimism can reverse; data freshness is the 06:00 ingestion; this is analysis, not investment advice or a trade.

+
+
+
+
+

Agent trace contracts only · no shared state

+ + + + + + + + +
StepCallsResult
AdvisorAgentplan: 3 sub-tasks✓
↳ ResearchAgentinsights for NVDA, ASML (7d)3 validated
↳ RiskAgentRiskService.vol, .var, .concentration✓
↳ OptimizationAgentOptimizationService.mean_variance(max_sector=0.25)✓
Ontology guardcitations resolvable, tickers valid✓ pass
AdvisorAgentmerge + explain✓
+
+
+

Cost & latency LLM gateway · per request

+ + + + + +
Tokens6 4204 LLM calls
CostCHF 0.11budget p95 CHF 0.40 ok
Latency8.4 sbudget p95 20 s ok
Modelvia gateway · fallback chain not used
+
Every call logged: prompt version, model, tokens, latency, guard verdict → audit trail.
+
+
+
+ +
1Agents interpret and explain; services compute (§2.5). An agent that "estimates" a volatility is an architecture defect.
+
2Sub-agents cooperate only through service contracts, never by sharing internal state (§2.5).
+
3Every claim carries a citation to a validated Insight; the freshness stamp bounds what may be claimed.
+
SKETCH 4/7 · wireframe, not a specification
+
diff --git a/project_exercise/ui_sketches/05_optimisation.html b/project_exercise/ui_sketches/05_optimisation.html new file mode 100644 index 0000000..af2a8a9 --- /dev/null +++ b/project_exercise/ui_sketches/05_optimisation.html @@ -0,0 +1,57 @@ +Sketch 5 – Risk and optimisation +
+
+
Portfolio Intelligence Platform thin dashboard · no business logic · calls the API
+ Market API: live + LLM API: closed (healthy) + +
+

Risk & optimisation · deterministic: same input → same output · pinned by reference test vectors

+ +
+
+

Parameters OptimizationService

+
Objective
mean–variance (Markowitz)
+
Risk target (annual vol)
16 %
+
Max weight per asset
25 %
+
Max weight per sector
25 %
+
Constraints
long-only · fully invested
+
Price history
1y · as of 2026-09-07 06:00
+
Run optimisation
+
Tests: 42/42 reference vectors pass
+
+
+

Efficient frontier RiskService · OptimizationService

+
+ + + + current (18.2 %, 8.1 %) + proposed (15.9 %, 7.5 %) + + volatility →return ↑ + +
+
Frontier and both points computed by the services; the chart only draws their output.
+
+
+

Allocation: current → proposed OptimizationService

+ + + + + + + + +
AssetNowProposed
NVDA22.1 %15.0 %
ASML13.4 %10.0 %
NESN18.1 %18.0 %
ROG16.1 %18.0 %
MSFT18.6 %21.0 %
Cash11.7 %18.0 %
+

Explanation OptimizationAgent

+

"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."

+
Figures quoted from the service output · no number originates in the agent.
+
+
+ +
1Performance, Risk, Optimization services are pure and deterministic; exact tests against the provided reference vectors (§2.3, §4.3).
+
2Agents explain the numbers; the OptimizationAgent calls OptimizationService and never computes an allocation itself (§2.5).
+
SKETCH 5/7 · wireframe, not a specification
+
diff --git a/project_exercise/ui_sketches/sketch.css b/project_exercise/ui_sketches/sketch.css new file mode 100644 index 0000000..dcfdba5 --- /dev/null +++ b/project_exercise/ui_sketches/sketch.css @@ -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); }