vitals / README.md
s-ttp's picture
Sector-gap sourcing six deep, gap-driven second batch, gap-covering shortlist, per-sector search record, narrator share rules
06f8709 verified
|
Raw History Blame Contribute Delete
28.2 kB
metadata
title: Vitals
emoji: 🩺
colorFrom: blue
colorTo: gray
sdk: docker
app_port: 7860
pinned: false

Vitals β€” Your portfolio's AI health check

Five AI analyst personas examine your stock portfolio in private; you see the verdict and the treatment plan. Key in your holdings (ticker or company name + entry price, quantity optional), and get:

  • Per-holding recommendations β€” Keep / Buy More / Trim / Sell, decided by deterministic rules from a 5-persona AI conviction panel (Value Β· Growth Β· Macro Β· Technical Β· Risk).
  • Intrinsic value β€” a Damodaran-style 2-stage DCF per holding (an LLM sets lifecycle assumptions, auditable code does the math), with value-trap guards.
  • Portfolio-level review β€” sector diversification vs healthy bands, concentration (effective N), correlation clusters, beta, Sharpe/drawdown, strategy coherence, and a deterministic OK/WATCH/ALERT risk scorecard that renders even if every LLM call fails.
  • Alternatives β€” gap-filling equity candidates cued from your existing portfolio, merged from two signals (MarketFM quant factor rank + Slipstream smart-money 13F/insider accumulation), plus an optional context intake that unlocks a recommended target portfolio.
  • Verdict ledger & standing watch β€” save a portfolio under an anonymous code; every verdict is logged with its price and later scored per action vs the S&P 500 (the committee keeps a track record it must answer to). A background sweep plus on-demand checks raise alerts on price shocks, 200-day trend breaks, stale verdicts, and cited adverse news events. Case IDs are remembered per device and shareable via ?p=<code> links. Ledger persists to a private HF dataset (VITALS_DATA_REPO, default s-ttp/vitals-data).
  • A downloadable, typeset PDF report with the full committee detail, plus a JSON audit trail for every run (inputs, data snapshot with sources and as-of dates, every lens output, model and prompt versions, decisions) so any verdict can be reproduced.

Institutional-grade layer

  • Data integrity β€” Alpha Vantage premium is the primary market-data source (adjusted history, quotes, splits, dividends and earnings calendars, ETF constituents, FX) and Sharadar SF1 (licensed, point-in-time) the primary fundamentals source for US equities; scraped feeds are fallback only (indices and exchanges Alpha Vantage does not map); every field passes a plausibility gate (units and feed errors are nulled and flagged, never fed to a model); every number carries an as-of stamp; London pence/pound and D/E/yield unit traps are normalized.
  • Valuation rigor β€” bear/base/bull intrinsic ranges instead of a point estimate, a reverse DCF ("today's price assumes X%/yr growth"), sector-appropriate models (FFO-based DCF for REITs, justified P/B vs ROE for banks and insurers), and peer percentiles vs the US universe.
  • Risk analytics β€” benchmark chosen by domicile (NIFTY, FTSE, Nikkei… not SPY for everyone) with a base-currency view; historical stress replays (COVID, 2022 rate shock, Q4-2018, 2025 tariff shock) of today's weights; 1-day/1-month VaR and expected shortfall; tracking error; currency exposure; real ETF look-through from disclosed constituents; style-factor tilts; days-to-liquidate per position.
  • Reproducibility β€” each analyst lens is sampled LENS_SAMPLES times (default 2) and medianed so a verdict doesn't flip on re-run; strict schema coercion on every LLM output; prompt and methodology versions stamped on every verdict; per-lens calibration (hit rate vs benchmark) accumulates in the ledger.
  • Portfolio-aware recommendations β€” alternatives are not a stock screen: the engine first diagnoses what this book lacks (sector gaps, overweight, effective positions, crash loss vs stated tolerance, income need, valuation tilt), then scores every live-verified candidate on its measured marginal improvement β€” correlation to your holdings, effective-N, beta, worst crash replay, yield, DCF gap β€” and only fit-eligible names reach the CIO and the switch engine. Each pick states the problem it fixes with the number ("moves opposite your holdings (correlation βˆ’0.40) Β· cuts the 2022 replay βˆ’53%β†’βˆ’49%"). Deterministic guards a quant rank never applies (peak-cycle earnings in a cyclical business, an extended price, informed money leaving) surface as cautions and cost fit; then the same five analyst lenses screen every shortlisted candidate as a prospective addition β€” a name the committee would not add (below 52/100) is never recommended or used as a switch destination.
  • Portfolio construction β€” risk decomposition (marginal contribution to risk per holding, Ledoit-Wolf shrinkage covariance), adds chosen jointly (each conditional on the one before it, by variance reduction plus need-fit β€” two redundant names never both win), constrained sizing (SLSQP under name/sector caps, a turnover budget and a tax penalty) in three scenarios β€” full rebalance, tax-light, new-cash-only β€” and the entire risk block re-run on the recommended book (stress replays, VaR, effective positions, health) side by side with today. Instruments (sector ETFs, treasuries, cash, dividend and international ETFs) compete with single stocks as fixes. Guards: earnings-date and ex-dividend checks, client exclusions and a max-positions constraint; every candidate considered but not recommended is listed with the reason.
  • Recommendation completeness β€” every Trim/Sell carries its tax cost (gain and, with an optional purchase date, short- vs long-term treatment); orders carry estimated transaction cost and liquidity notes; investor context now includes loss tolerance and near-term cash need, checked against the stress replays; income/conservative investors get profile-fitting candidates; non-US books get live-verified local-market ideas.
  • Engineering QA β€” pytest regression suite (valuation math, decision validator, schema coercion, sanity gates, a mocked golden end-to-end run) and per-run diagnostics (duration, LLM calls/failures, model versions).

Educational tool β€” not investment advice. No orders are ever placed; output is analysis only.

Token cost strategy

Every run reports tokens and estimated cost by model and by task in the diagnostics (and a "Model usage this run" line in the report). The model plan, chosen so that judgment stays on the strongest model and mechanics do not pay for reasoning they do not need:

Task Model Reasoning Why
CIO synthesis, pre-mortem, alternatives Claude Opus 5 adaptive (default) the one judgment call that shapes the verdict
Portfolio strategist Claude Sonnet 5 adaptive structured read of computed metrics
Value / Risk lenses Claude Sonnet 5 medium effort rubric scoring against a fact sheet; shared fact-sheet block is prompt-cached
Growth / Technical lenses GPT-5.6 Sol provider default model diversity across the committee
Macro lens Gemini 3.7 Flash provider default cheapest lens, macro positioning only
DCF assumptions, plan narrative Claude Sonnet 5 low effort numeric mapping with clip guards; prose
Per-holding rationales GPT-5.6 Luna thinking disabled reason-only sentences with a leading-action guard; split out of the CIO so its response is a third of its former size
Investment memos GPT-5.6 Luna thinking disabled A/B on real payloads: memos as good as Sonnet's at ~1/13 the cost, 10 s vs 17 s
Ticker resolution, listing check, event scan Gemini 3.1 Flash Lite β†’ Haiku thinking disabled 10/10 on renamed tickers at 1.0 s a call for ~nothing; Haiku via the cascade
File import Claude Sonnet 5 thinking disabled once per import; plain tables never reach a model

Tested and rejected: Luna as a lens (growth-lens scores drifted 7 points from GPT-5.6 Sol against 2.4 points of Sol's own repeat noise β€” enough to flip Keep/Buy More bands) and Luna for DCF assumptions (lifecycle classifications diverged from Sonnet's). Gemini 3.7 Flash as a value lens tracked Sonnet at 0.93 correlation but is kept on the macro lens only, so the committee stays diverse.

Claude 5 models think by default; on a 24-name book that was 19k thinking tokens on Sonnet alone. Effort levels and disabled thinking remove most of it without touching the tasks where reasoning matters.

Speed on US books

  • Sharadar price layer (src/sharadar_prices.py): US prices, 52-week range, moving averages, volume, beta, market cap and multiples come from SEP / SFP / DAILY / TICKERS / ACTIONS in batched calls with no meaningful rate limit; Alpha Vantage is kept for what Sharadar lacks (earnings surprises and estimate revisions, the analyst-target overview, news sentiment, ETF constituents, FX) and remains primary for every non-US market. Earnings dates come from one whole-market calendar call a day.
  • Pre-warming: each row resolved at import kicks off the deep fundamentals, eight-year history, street view, news and events in the background, so the analysis finds them cached.
  • Batched import verification: one server round trip resolves every imported row in parallel; the ten-year range and split checks run only when the entry price implies an extreme P/L.
  • Split CIO: investment memos are written by a parallel writer on the standard model in chunks, so the CIO's own response is a quarter of its former size.
  • Time budget: the watchdog scales with book size; past 65% of the budget the candidate committee screen is skipped and past 85% the plan narrative, with the diagnostics saying so.
  • Plain tables never wait for a model: a CSV/XLSX with a symbol column and an average-cost column is read directly; the AI extractor handles transaction exports, PDFs and odd layouts on the standard tier. A pre-split entry price is never rewritten silently: the row asks which is right β€” adjust price and quantity, adjust price only (quantity already post-split), or keep as entered β€” and the analysis refuses to run until the question is answered; the results row shows what was chosen. A 10:1 split can hide behind an ordinary-looking P/L, and only the investor knows whether their share count is pre- or post-split.
  • Pipeline parallelism: the portfolio strategist and the whole candidate pipeline (pool, live verification, fit, screens, committee) run alongside the five-lens panel; books of 30+ names use one sample per lens.
  • US-only fast path: a book with no exchange suffixes skips the European panel, ADR bridge, local-idea generation and multi-venue logic deterministically.
  • The European panel rebuild yields to running analyses and is throttled to a fraction of the Alpha Vantage budget.

Markets covered

Market Yahoo suffix Prices / history / FX Fundamentals
US (NYSE, NASDAQ) β€” Alpha Vantage Sharadar (point-in-time)
UK Β· Germany (Xetra, Frankfurt) Β· France Β· Netherlands Β· Portugal Β· Belgium Β· Canada (TSX, TSX-V) .L .DE .F .PA .AS .LS .BR .TO .V Alpha Vantage Alpha Vantage where available, else yfinance (sanitized)
India (NSE, BSE) .NS .BO Alpha Vantage BSE line (NSE names are dual-listed at near-identical prices; the NSE symbol is kept) Sharadar via the company's NYSE/NASDAQ ADR where one exists (HDFC Bank β†’ HDB, Infosys β†’ INFY: ratio fields are the underlying company's, EPS/BVPS derived currency-consistently from local price Γ— P/E, P/B); else yfinance (sanitized)
China (Shanghai, Shenzhen) .SS .SZ Alpha Vantage Sharadar via ADR where one exists (Alibaba β†’ BABA), else yfinance (sanitized)
Switzerland, Italy, Nordics, Japan, Korea, Hong Kong, Australia, Brazil and other Yahoo-suffixed exchanges e.g. .SW .MI .CO .T .KS .HK .AX .SA yfinance (fallback) Sharadar via ADR where one exists (Vale β†’ VALE, TSMC β†’ TSM, Shell, SAP, Novartis…), else yfinance (sanitized)

Several purchases of one stock may be entered as separate rows (same ticker, each lot's price, quantity and date); they are averaged with the average-cost method (quantity-weighted cost, sales as negative quantities reduce the remaining position at that cost β€” the Section 104 pool logic), the earliest lot dates the position, and US tax notes name the lots still short-term. Without quantities the entry prices are averaged plainly and the row asks for quantities.

Inputs may be tickers, company names (resolved deterministically through Alpha Vantage Symbol Search β€” preferring the home-market listing over a US OTC ADR, and mapping India to the NSE line β€” before any LLM is consulted), Bloomberg-style codes ("VOD LN" β†’ resolved with a listing check) or ISINs (OpenFIGI β†’ primary listing). Renamed or demerged tickers are re-searched by their base symbol ("Tata Motors" β†’ TMCV.NS / TMPV.NS). Benchmarks follow the book's domicile (S&P 500, FTSE 100, STOXX Europe 600, DAX, NIFTY 50, SSE Composite…); European books get UCITS instruments, Indian books get NSE-listed BeES ETFs, and local-market ideas are verified live on the same exchanges. Tax notes apply US short/long-term rules only to US listings.

US books β€” practitioner layer (src/fundamentals.py)

Plain US listings get a deep block computed deterministically from Sharadar history and Alpha Vantage estimates, so the committee argues from numbers rather than adjectives:

  • Forensic quality β€” Piotroski F-score, Altman Z (masked for financials and REITs), Beneish M-score, Sloan accruals, cash conversion (FCF / net income).
  • Buffett / Greenblatt / Lynch economics β€” owner earnings and owner-earnings yield, FCF yield, ROIC and five-year incremental ROIC, EBIT/EV earnings yield, Graham number, PEG on three-year EPS growth, and the Lynch category (fast grower, stalwart, slow grower, cyclical, turnaround, asset play) that decides which metrics matter and which sell triggers apply.
  • Capital allocation β€” buyback yield vs dilution (1y and 5y share count), stock-based compensation as a share of revenue and FCF, FCF payout and the dividend-raise streak.
  • Valuation vs the company's own ten-year history β€” where today's P/E, EV/EBITDA, P/S and P/B sit in the daily distribution (Sharadar DAILY), with the median.
  • Earnings momentum β€” beat count and average surprise over eight quarters, fiscal-year EPS estimate revision over 90 days, up/down revision counts over 30 days.
  • Ownership β€” open-market insider buys and sells over six months (SF2, cash transactions only) and 13F holder / share changes (SF3A, stale or coverage-jump snapshots suppressed).
  • Sell discipline β€” per-holding thesis-break triggers from the same numbers, forensic flags that also screen alternatives (a factor rank never outranks a distress or manipulation flag).

Recommendation discipline for every book with a usable DCF range:

  • Payoff asymmetry β€” bear / base / bull outcomes with probabilities tilted by committee conviction give an expected 12–18 month return and an upside:downside ratio; a "Buy More" must clear 2:1 and +8% or it is downgraded to Keep with the reason shown.
  • Sizing guardrail β€” half-Kelly from expected return and realised volatility, capped at the name cap, shown against the current weight.
  • Investment memo per holding (thesis, the debate, what would change our mind), a pre-mortem for the largest position, an explicit "do nothing" verdict when nothing clears the hurdle, tax-loss harvesting pairs (β‰₯10% loss, β‰₯0.7-correlated non-identical substitute; a broad index fund swaps only into a β‰₯0.9-correlated broad fund on a different index, e.g. VOO β†’ VTI / SCHX, never a sector fund; 31-day wash-sale note) and wash-sale warnings on loss-making switches.
  • Health score = six pillars, shown under the gauge β€” Prospects 25% (committee conviction, DCF-implied payoff where trusted, quant 12-month excess, growth durability, estimate momentum; growth-stage names are never judged on a mature-company DCF gap), Concentration 20% (name vs cap, top three, sector vs band, largest active bet), Diversification 15% (effective positions, correlated pairs, ballast for cautious profiles), Quality 15% (weight in forensic-flagged names, Piotroski), Drawdown 15% (half efficiency vs structural beta, half absolute fall vs the index; 25% for conservative, 8% for aggressive profiles), Valuation 10% (DCF gap where the model applies, blended toward neutral for the share of the book it does not cover, P/E vs own history). Active sector bets fall back to a static S&P 500 mix when the live profile is unavailable, so the charge cannot appear and vanish between runs. One-year performance is a Β±3 tie-breaker only; a breached loss tolerance costs 8; the weakest pillar caps the total (one broken pillar cannot be outvoted by strong prospects).
  • A stock-picker's plan never holds an index fund β€” for a book of 12+ single names with no cautious profile or stated loss tolerance, no ETF enters the candidate pool at all: sector gaps, ballast and diversification are expressed through single names. A conservative profile, a short horizon or a loss tolerance brings defensive instruments back.
  • Stock-picker books get stock ideas β€” a book of 12+ single names is treated as a stock-picker's: the CIO sees single names first, at least two alternatives are single stocks and at most one is an instrument; smaller books or de-risking needs still get instruments.
  • Sizing discipline β€” a new single name enters at a starter size (β‰ˆ1.5Γ— the typical position, at least 3%, never past the cap); an existing name may at most double or gain three points of the book; instruments are bounded in a large single-name book; the target keeps at least 75% of today's market beta unless the investor asked to de-risk (a growth book is never silently converted into utilities); risk aversion scales with the stated profile; a partial sale of a position below the trading band (min(2%, max(1%, 0.5/n)) β€” 1.06% in a 47-name book) is replaced by a clean exit or a hold; a size-driven Trim on a backed winner lands between 80% of the cap and the cap (META 24% β†’ 12–15%), never at 40% of the position; of a correlated pair the plan adds to one name only; a sector ETF is sized like a sector bet (never past the name cap or 15%), and every new name the plan buys is explained in the alternatives list; a conservative / income book never adds to a name with beta above 1.15 and needs a flagged idea (peak-cycle, extended, informed money leaving) at β‰₯66/100.
  • The manager's desk β€” capital follows conviction: fresh money goes only to Buy More verdicts and new ideas (a plain Keep is never topped up; a backed Keep below the trading band is rounded up to a real position; an add is lifted to at most 6% of a broad book, 10% of a small one); proceeds the committee's ideas cannot absorb stay as unallocated cash, presented as dry powder for a watchlist of screened-out names with explicit entry conditions (reclaim the 200-day, pull back to it, wait for revisions to turn). Keeps with weak backing and a negative expected payoff carry a watch tag and are the first source of funds; a sub-band position nobody backs is exited rather than held as dead money. Every sell carries a tax plan (highest-cost lots first, hold a lot that turns long-term within 90 days, split a large long-term gain across two tax years); the plan shows where the risk sits after the trade, a probability-weighted valuation risk/reward (bear/base/bull convergence) for today's and the recommended book, the next 90 days of catalysts (earnings, ex-dividend), and per-holding tripwires with the current number and the date they are next tested. Loss-making growth names are valued on a stated rule-of-40 EV/Sales yardstick instead of a blank. Triage dispositions always agree with the trade list.
  • Sector gaps are sourced, not just named β€” a sector the book lacks is searched six names deep in the quant panel (two for sectors already owned), the top 30 are verified live, the committee screens a second batch whenever a missing sector has no strong candidate, and the shortlist leads with the best strong name from each missing sector so the optimizer can actually move the sector mix. A book with zero Industrials, Materials, Defensive and Utilities once received one Technology idea because its eight gap candidates all failed a screen and nothing replaced them.
  • Valuation guardrails β€” the justified-P/B model is used only for banks, insurers and asset managers with book-value economics (ROE ≀ 35%, book β‰₯ 15% of price, no leverage-inflated ROE); payment networks and fintechs go on the earnings DCF; when forward EPS is β‰₯1.4Γ— depressed trailing EPS the DCF anchors on their average and says so; reinvestment is capped at 80% of earnings so a fast grower on a modest accounting ROIC is not priced as value-destroying; an Altman-Z distress flag disqualifies a candidate outright. The CIO synthesis retries on the standard tier after two unusable top-tier replies, JSON parsing repairs control characters and trailing commas, and every unusable model reply is saved under data/cache for diagnosis.
  • Loss tolerance as a constraint β€” when the investor states a loss tolerance, every crash replay becomes a linear constraint in the optimizer, so the proposed book must stay inside the tolerance in each historical window; the scenario table says whether it was reachable.
  • A model reply is validated, not trusted β€” the CIO's answer must carry its verdict (a truncated reply once parsed as an inner fragment and slipped through as an empty verdict, so the report fell back to the scorecard); the retry lowers reasoning effort so the budget goes to the answer, and any fallback is printed in the report's diagnostics note.
  • Winners are not sold early β€” the validator's two-lens dissent tilt (two analysts at 35 or below overriding a momentum-led median) applies only to cyclicals, judged by sector or by industry (semiconductors, memory, autos, steel, chemicals, oil and gas, mining, shipping…); a secular compounder keeps its Keep or Buy More while its growth and trend lenses back it. Size alone still trims any name past 1.25Γ— its cap unless the committee is strongly behind it.
  • Orders agree with verdicts β€” a Keep the committee only lukewarmly backs (conviction under
    1. or that carries a forensic flag gets no adds; a backed Keep outside the overweight sector is never sold below 80% of its weight to fund other buys; only a new position can fill a sector gap and the plan narrative is told so.
  • Plan coherence β€” switch destinations follow the names the order plan actually buys; a "Buy More" is never issued inside the diagnosed overweight sector; bond and cash funds are ballast and are never ejected by the equity lenses in a conservative or short-horizon book; US-listed ADRs derive per-share values from the ADR price and Sharadar ratios (ordinary-share EPS once made TSM look 4Γ— undervalued).
  • Benchmark-relative risk for US books β€” measured factor betas (momentum, quality, value, low-vol, size ETFs), upside / downside capture, Sortino, Calmar, active sector bets vs the S&P 500.

UK / European books β€” institutional layer

  • European factor panel (src/europanel.py): the MarketFM equivalent for Europe, built on the Space from Alpha Vantage β€” universe = the top ~300 European names by index weight (VGK, SPEU, DBEU, FEZ constituents) mapped to their home listings (LSE, Xetra, Paris, Amsterdam, Lisbon, Brussels; Xetra lines for markets Alpha Vantage does not carry), per-name valuation / quality from the same holding fetch the analysis uses (Alpha Vantage price, Sharadar via ADR, yfinance fill, sanity gates) and momentum / volatility from Alpha Vantage adjusted daily history, cross-sectional percentile ranks and a style-balanced composite (value, quality, momentum, low-vol). Refreshed daily in the background; data/europanel.json ships with the last build. European-heavy books (β‰₯50% weight in European listings) draw quant-ranked candidates from it first, and European holdings get peer percentiles against the European universe, not the US one.
  • European sector UCITS instruments: iShares STOXX Europe 600 sector ETFs (Xetra) and IPRP (European property) fill sector gaps for European books, alongside the broad / gilt / gold / dividend / min-vol UCITS set.
  • Cost & tax realism: the order plan adds UK stamp duty (0.5%), Irish stamp duty, French, Italian and Spanish FTTs and HK stamp duty on home-market share purchases (ETFs exempt); an account wrapper input (taxable / ISA / SIPP / IRA) switches capital-gains notes off inside tax-sheltered wrappers; foreign dividend payers carry a withholding-tax note.

Space secrets

Secret Required Purpose
ANTHROPIC_API_KEY recommended Claude β€” Value & Risk analysts, DCF assumptions, strategist, CIO.
OPENAI_API_KEY recommended GPT β€” Growth & Technical analysts. Different models give independent opinions; with any one key the others cascade onto it.
GEMINI_API_KEY recommended Gemini — Macro analyst + name→ticker fallback.
ALPHAVANTAGE_API_KEY recommended Premium market data (primary): history, quotes, splits, calendars, ETF constituents, FX, news. AV_CALLS_PER_MIN (default 75) sets the limiter.
HF_TOKEN optional Read the private MarketFM panel dataset (quant ranks + alternatives pool).
NASDAQ_DATA_LINK_API_KEY recommended Sharadar SF1 β€” licensed, point-in-time US fundamentals as the primary source (and REIT FFO inputs).

With no LLM key at all the app still runs and shows the quantitative summary.

Models are tier-routed by task demand (all env-overridable): synthesis & valuation judgment on Claude Opus 5 (LLM_MODEL_TOP), Value/Risk lenses + import on Claude Sonnet 5 (LLM_MODEL), Growth/Technical lenses on GPT-5.6 Sol (OPENAI_MODEL), Macro on Gemini 3.7 Flash (GEMINI_MODEL), utilities on Claude Haiku 4.5 (LLM_MODEL_FAST). Other config: RUNS_PER_DAY (default 25), MAX_HOLDINGS (default 25), CACHE_TTL_HOURS (default 6).

Other env vars: LENS_SAMPLES (default 2), MAX_CONCURRENT_RUNS (default 2).

Run locally

pip install -r requirements.txt
uvicorn app:app --port 7860

Policy validation

scripts/backtest_fit_policy.py walks forward quarterly over ~5 years on three seed books and compares the greedy variance-reduction add rule against random adds, momentum adds and no adds (12-month holds); results land in data/policy_backtest.json.

Latest run (2021-Q1 β†’ 2025-Q2, 18 rebalance dates per seed): the greedy rule delivered the lowest volatility and shallowest max drawdown in every seed book (tech-concentrated: 31.6% vol / βˆ’24.0% DD vs 33.7% / βˆ’25.8% random, 35.4% / βˆ’26.7% momentum, 41.4% / βˆ’31.1% with no adds) and the best Sharpe on the tech book (1.34); on the income and mid-cap books momentum adds earned more return. Read it plainly: the construction rule reliably buys diversification, not alpha β€” return is the job of the fit scoring and the analyst committee layered on top.

Tests

pip install -r requirements-dev.txt
pytest

Architecture

FastAPI + SSE progress stream + vanilla JS frontend (no build step). The analysis pipeline (src/orchestrator.py) runs in-process: resolve holdings β†’ fetch market data (yfinance global, Alpha Vantage enrichment) β†’ deterministic portfolio metrics β†’ Damodaran DCF β†’ batched 5-persona conviction panel β†’ portfolio strategist β†’ CIO synthesis. LLM outputs are gated by deterministic validators (actions come from conviction-median rules, never from free-form LLM text); every LLM stage fails open.