# Today's Portfolio Impact — Surface Spec (lane 4 design)

**Status:** DESIGN + SCAFFOLD ONLY (no deploy, working tree only).
**Owner:** NEXUS COMMAND THv2 lane 4 (background).
**Created:** 2026-04-28.
**Charter alignment:** `feedback_scientific_rigor_portfolio_wide.md` (calibrated truth-telling), `feedback_ui_bar.md` ("better than Bloomberg's RSK / Schwab's Today's Impact"), `feedback_superior_to_competition.md`, `feedback_thv2_best_practices.md`, `feedback_first_principles_audit.md`, `feedback_silent_failures.md`.
**Touch-discipline:** Lane 1 owns `app.js` + `engine/playbook.js` + `index.html` + `cockpit.html`; lane 2 owns the 19 W1+W2 engine modules + their consumer wires; lane 3 owns `lib/cockpit-preflight.js` / `lib/preflight-banner.js`. **This spec proposes NEW files only.** No edits to in-flight files.

---

## Why this surface

Today (2026-04-28), Kadeem opened his cockpit while down significantly across **MRAL** (-7.78% from cost basis, microcap-distress: down ~87% from highs), **SOXL** (semis-3x exposed to AI-selloff cascade), and **TSMX** (TSM 2x — overlapping AI-semi exposure). The cockpit could not answer the only question that matters in that moment:

> "I'm down today. What should I do, in priority order, with calibrated certainty?"

Five concrete failures of the current cockpit:

1. **Position-blind** — orchestrator + playbook scores stocks abstractly; does not know what Kadeem holds, his cost basis, or how much he's bleeding in dollars right now.
2. **News/event-blind** — no link between sector-selloff catalysts and ticker exposure. The AI-semi selloff is one news event hitting at least three positions; the cockpit has no surface that says so.
3. **Microcap-distress-blind** — MRAL momentum factors light up Strong Buy on what is structurally a value trap (cash-runway risk, dilution risk, -87% from highs).
4. **Wrong-path attempt scrapped** — the brief endpoint (being scrapped this session) was a misguided attempt at this surface.
5. **Redundancy-blind in the cockpit** — `engine/redundancy-detector.js` (W2, 60+ wrapper-ETF mappings) exists and flags GGLL+GOOGL hidden-duplicate-exposure as a regression test inside `api/portfolio-preflight.js`, but the cockpit never surfaces this to the user.

This surface fixes (1)-(5) by composing existing primitives — no new science required for M1.

---

## Section A — User scenarios (canonical golden + 4 diverse)

### A1. Canonical golden: Kadeem 2026-04-28 (AI/semis selloff, microcap distress)

**Inputs (real, from `data/holdings.js` 5:33 AM ET 2026-04-28 snapshot):**
- MRAL 4400 sh @ $5.17 (cost $5.6062, -$1,919.13 unrealized; cls Single-Stock ETFs; ~28% concentration)
- SOXL 46 sh @ $123.39 (Leveraged ETFs 3x; today's intraday selloff)
- TSMX 100 sh @ $80.40 (Single-Stock ETFs 2x TSM)
- TQQQ, GGLL, GOU, ASTX, AAOX, GMEU, BYDDY, COP, DIG (mixed)
- CASH -$635.49 margin debit

**What the surface MUST tell Kadeem (M1 minimum):**

```
TODAY'S PORTFOLIO IMPACT  —  Chase Self-Directed (...8916)
  Today's $ change: -$2,340 (-2.86%)         As-of: 11:42 AM ET 2026-04-28
  Drivers: 3 of 12 positions cover 78% of today's bleed.

ATTENTION-RANKED TOP 3:
  ┌──────────────────────────────────────────────────────────────────┐
  │ #1  MRAL  -$X today  • 28% of book • microcap-distress flag      │
  │     -87% from highs • thesis-status: SCORE 71 BUT DISSONANT      │
  │     "Momentum factors say Strong Buy. Structural factors —       │
  │      cash runway, drawdown, dilution — say Avoid. We do NOT have │
  │      enough confidence to call. Read methodology."               │
  │     SUGGESTED: TRIM (concentration-rule override), then revisit. │
  ├──────────────────────────────────────────────────────────────────┤
  │ #2  SOXL  -$Y today  • 7% of book • leverage-decay extreme       │
  │     +TSMX overlap → 16% of book is AI-semi 2x/3x leveraged       │
  │     drag estimate: ~32% / yr if held through current σ regime    │
  │     thesis-status: SCORE flipped 64→48 since entry (24h ago)     │
  │     SUGGESTED: REDUCE one of {SOXL, TSMX} to break duplication.  │
  ├──────────────────────────────────────────────────────────────────┤
  │ #3  TSMX  -$Z today  • 9% of book • redundant w/ SOXL            │
  │     same catalyst (TSM-led AI-semi compression)                  │
  │     thesis-status: SCORE intact but unhedged exposure to #2      │
  │     SUGGESTED: Pair-trim with SOXL; do not act on TSMX alone.    │
  └──────────────────────────────────────────────────────────────────┘

CATALYSTS LINKED (M2):
  • AI-semi compression (TSM, NVDA, ASML guidance) — hits SOXL, TSMX, AAOX
  • <microcap distress feed pending — M3>

SAFETY FLAGS (M1, from preflight-hygiene):
  • Concentration: MRAL 28% > 25% rule  → hardViolation
  • Leverage stack: 5 leveraged positions (MRAL, GMEU, TSMX, SOXL, GGLL, GOU, TQQQ, AAOX, ASTX, DIG) — leverage-stack-guard threshold likely tripped
  • Hidden duplicates (M1): GGLL + GOU + (no GOOGL common held) → NDX exposure stack via TQQQ unflagged
```

**Why this beats the cockpit today:** the surface refuses to tell Kadeem "MRAL is a Strong Buy" without disclosing that the structural factors disagree. It explicitly enumerates the non-orthogonal exposures (SOXL+TSMX) instead of leaving them in two separate score rows. It dollars-up the bleed instead of percentages. It says "we do not have enough confidence to call" instead of bluffing — per `feedback_scientific_rigor_portfolio_wide.md`.

### A2. Winning streak (positive-day case)

Holdings green across the board. Surface shifts from "what to fix" to **"what to harvest"**: which positions hit a Score-tier downgrade (forecast deteriorated below an Add-tier floor) while up — i.e. let the winners run vs trim into strength. Default ranking: highest $ unrealized × concentration × score-decay flag. Prevents "everything's fine" complacency.

### A3. Mixed regime (some green, some red)

Surface partitions into two stacks: top bleeders ranked by attention score (A1-style) and top harvesters ranked by trim-into-strength score (A2-style). User can drill down into either. Default landing shows 3 of each.

### A4. Dividend day (COP, ATOM yield)

Income tag highlighted on COP ($585.85 est. annual income on book per holdings.js comment) and ATOM (Coinbase Earn / staking yield). Today's dividend-credit shown on positions where ex-div fell today; methodology note clarifies that today's price impact equals approximately the dividend amount and is not "loss." This guards against Kadeem confusing a cleanly-earned cash-distribution drop for a real signal.

### A5. Earnings day (any held name)

Earnings-stop-tightener (W2, already wired into v94 sizer per lane-2 delta) decorates the position with "earnings in N days; ATR stop tightened to k×". Surface adds a banner: "Earnings imminent — reduce sizing or pre-define exit." Pulls from existing engine; no new fetch required.

---

## Section B — Data inputs

| Input | Source | Status | Notes |
|---|---|---|---|
| Positions (qty, costBasis, lastPrice, venue, account, cls) | `data/holdings.js` (`THV2Holdings.load()` + `.rollup()`) | EXISTS | Real chase + coinbase, decimalized. |
| Today's intraday $ P&L per position | needs **today's open** price + **lastPrice**; `data/seed.js` carries px0 | PARTIAL | M1: degrade gracefully if open unavailable — fall back to "session change unknown" with explicit confidence note. M2: `api/intraday-bars.js` (new). |
| Score (per position, current) | `engine/orchestrator.js` `runForTicker(ticker)` | EXISTS | Returns `{score, tier, factors, …}`. Composed engine-side. |
| Forecast (per position) | `engine/prescience.js` `forecast(s, bars, opts)` | EXISTS | Score-vs-forecast methodology page already published. |
| Tier (per position) | derived from score in `engine/_schema.js` / `engine/playbook.js` | EXISTS | Reuse — do not re-implement. |
| Leverage-decay headwind | `engine/leverage-decay.js` `scorePosition({leverage, dailyVol, marketValue})` | EXISTS | M1 wire. |
| Redundancy / duplicates | `engine/redundancy-detector.js` `duplicates(positions)` | EXISTS | M1 wire. 60+ wrapper-ETF mappings. |
| Concentration | `engine/concentration-guard.js` (via `preflight-hygiene.js`) | EXISTS | M1 wire. 25%/50%/65% rules. |
| Leverage stack | `engine/leverage-stack-guard.js` (via `preflight-hygiene.js`) | EXISTS | M1 wire. |
| Drawdown velocity | `engine/drawdown-velocity.js` (W2, wired into alerts-pipe per lane-2 delta) | EXISTS | M1 read-only consume. |
| News headlines per ticker | `api/news.js` / `api/news/headlines.js` / `api/news-aggregator.js` | EXISTS (raw) | M2 needs an "impact-tagged" derivative endpoint — see Section G. |
| Microcap-distress flags (cash runway, 52w drawdown, dilution proxy) | does NOT exist | NEEDED (M3) | Spec'd in Section G (`api/microcap-distress.js`). |
| Real-time bars | `api/feeds/probe.js` + downstream feeds | PARTIAL | Sufficient for M1 if degraded gracefully. |
| Cohort comparison (similar books) | does NOT exist | NEEDED (M4) | Future surface. |
| Thesis log (why user bought, when) | does NOT exist as a structured store | NEEDED (M4) | Currently lives in `data/holdings.js` `note` strings — promote to first-class. |
| User score-history (for thesis-drift / score-flip detection) | `scoreHistoryByTicker` — store schema gap (lane-2 delta calls this out as deferred dep for score-velocity / score-volatility / score-stability) | BLOCKED | M4 unblock. |

**M1 input compatibility:** M1 ranks using only EXISTS rows. M2-M4 progressively unlock attention-ranking dimensions as inputs land.

---

## Section C — UI layout

**New page:** `pages/portfolio-impact.html` (NEW FILE, leaves room for surface area without bloating cockpit). Renderer lives in `app.js` only as a nav link wire-up — no logic in `app.js`. Page-local script `portfolio-impact-render.js` reads `window.THV2.PortfolioImpact.compute(...)` (the new pure-fn engine in this spec) and renders.

### C1. Default landing (desktop ≥ 1024 px)

```
┌───────────────────────────────────────────────────────────────────────┐
│ [nav] Today's Portfolio Impact                  As-of 11:42 AM ET      │
│                                                                       │
│ ┌─SUMMARY───────────────────────────────────────────────────────┐     │
│ │ $80,973  total   (-$2,340 today, -2.86%)                      │     │
│ │ 3 of 12 positions = 78% of today's bleed                      │     │
│ │ 2 hardViolation flags • 1 concentration • 1 redundancy        │     │
│ └───────────────────────────────────────────────────────────────┘     │
│                                                                       │
│ FILTERS: [All] [Bleeders] [Harvesters] [Flagged] [Cash-only]          │
│ SORT BY: [Attention ▼] [$ today] [Concentration] [Score-flip] [Decay] │
│                                                                       │
│ ┌─RANKED LIST──────────────────────────────────────────────────┐      │
│ │ #1  MRAL    -$X    28%   ⚠ microcap   ⚠ concentration   →    │      │
│ │ #2  SOXL    -$Y     7%   ⚠ leverage-decay extreme       →    │      │
│ │ #3  TSMX    -$Z     9%   ⚠ redundant w/ SOXL            →    │      │
│ │ #4  GGLL    +$W     8%   • OK                           →    │      │
│ │ … (collapsed; expand)                                        │      │
│ └──────────────────────────────────────────────────────────────┘      │
│                                                                       │
│ ┌─DRILL-DOWN PANE  (selected: MRAL)──────────────────────────────┐    │
│ │ Score 71 (Add tier)  ◇  Forecast +1.4σ over 1 session         │    │
│ │ DISSONANCE: structural factors (cash, drawdown, dilution)     │    │
│ │   disagree with momentum factors. We do not have enough       │    │
│ │   confidence to call.  [Read methodology]                     │    │
│ │                                                               │    │
│ │ TODAY:  open $5.41 → last $5.17 (-4.4%)  → -$X  (-Y bps)      │    │
│ │ THIS WK:  -3.6%   THIS MO:  -8.1%   FROM HIGH:  -87%          │    │
│ │                                                               │    │
│ │ FLAGS:                                                        │    │
│ │   • Concentration 28% > 25% rule (hardViolation)              │    │
│ │   • Microcap distress: pending (M3 surface)                   │    │
│ │                                                               │    │
│ │ CATALYSTS (today):                                            │    │
│ │   • [M2 surface — pending]                                    │    │
│ │                                                               │    │
│ │ SUGGESTED:                                                    │    │
│ │   • TRIM to ≤ 15%   (rule: concentration-guard >25%)          │    │
│ │     Estimated proceeds: $$  Margin debit unchanged.           │    │
│ │   [methodology]  [counter-thesis]  [run /api/portfolio-       │    │
│ │    preflight after edit]                                      │    │
│ └───────────────────────────────────────────────────────────────┘    │
│                                                                       │
│ METHODOLOGY FOOTER (every number is a tooltip):                       │
│  • Attention score = w1·|$today| + w2·conc + w3·|score-flip| +        │
│    w4·catalyst-presence + w5·decay-headwind  …  [See /methodology]    │
└───────────────────────────────────────────────────────────────────────┘
```

### C2. Mobile (≤ 640 px)

- Summary card stays full-width.
- Filters collapse to a single overflow-tab (`More`).
- Ranked list becomes a stack of cards; tapping a card slides up the drill-down as a sheet (not a side pane).
- Filter+sort lock to the header on scroll.
- Methodology footer collapses to a single chip "How are these numbers computed?" → opens /methodology.

### C3. Drill-down patterns

- Click row → drill-down pane (desktop) / bottom sheet (mobile).
- "[methodology]" inline-link on every numerical claim → `/methodology#<anchor>`.
- "[counter-thesis]" → reuses existing `api/counter-thesis.js` to surface the bear case.
- "[run /api/portfolio-preflight after edit]" — an inline planner: user can preview a hypothetical TRIM + see the resulting preflight pass/fail (uses the lane-2 endpoint without modifying it).

---

## Section D — Ranking algorithm

**Goal:** produce one ordered list answering "what's most worth my attention right now" that **cannot be gamed by single high-vol noise blip**.

### D1. Attention score formula (M1)

For each position `p`:

```
attention(p) =
    w_loss        · norm(|today_$loss|)              // dollar bleed today
  + w_conc        · norm(concentration)              // size of bet
  + w_redundant   · I(redundancy_flag)               // 0/1
  + w_decay       · norm(leverage_decay_headwind)    // 0..100
  + w_thesisdrift · norm(|score_today − score_atEntry|)  // M4
  + w_catalyst    · I(catalyst_today)                // 0/1; M2
  + w_distress    · I(microcap_distress)             // 0/1; M3
```

with default weights (M1, calibrated against scenario A1 to produce the ordering MRAL > SOXL > TSMX):

```
w_loss = 0.30
w_conc = 0.20
w_redundant = 0.15
w_decay = 0.10
w_thesisdrift = 0.10  (M4 only; otherwise rolled into w_loss)
w_catalyst = 0.10     (M2 only)
w_distress = 0.05     (M3 only)
```

`norm(x)` = robust-scale to `[0,1]` using **today's portfolio-wide median + MAD** (not z-score) — that's the anti-gaming property: a single absurd outlier moves only its own row, not the entire ranking. Floor `norm(x) = 0` if `x ≤ portfolio-median`.

### D2. Anti-gaming property (single-vol-blip suppression)

If only ONE ticker has a single-blip-driven `|today_$loss|` (e.g. a fat-finger print, a halt-and-resume gap), the MAD-based norm caps its contribution at `1.0`. Combined with five other ranking dims, the maximum displaceable rank is bounded. We do **not** allow `attention(p) > 1.0` even if one term saturates — every dim is bounded.

### D3. Confidence band on ranking

Per `feedback_scientific_rigor_portfolio_wide.md`: the surface MUST show how confident we are in a ranking. Heuristic v1: if the gap between rank #N and rank #N+1 is < 5% of the ranking range, render the boundary as **dotted** instead of solid — UX signal that the ordering is uncertain. M1 ships dotted/solid only; M4+ ships a 95% bootstrap CI interval.

### D4. "We do not have enough confidence" gate

If a position has score-momentum-factors and structural-factors disagree (disagreement-detector — already in W2 family, deferred per lane-2 delta), the SUGGESTED action is **not** rendered. Instead the row says: "Dissonance: factors disagree. Read methodology." This is the first-principles version of `feedback_scientific_rigor_portfolio_wide.md` applied to the per-position recommendation.

---

## Section E — Methodology footer (transparency)

Every number on the surface MUST link to a methodology entry. New section in `/methodology` (page already exists per project file map):

- `#today-dollar-pnl` — "Today's $ change = qty × (lastPrice − todayOpen). If todayOpen is unavailable, the row degrades to '— ' with a confidence note."
- `#attention-score` — "Weighted composite of 4 (M1) → 7 (M3) signals. Scaled by portfolio-median / MAD; bounded to [0,1] per dim. Anti-gaming property: bounded contribution per dim."
- `#leverage-decay-headwind` — links to existing `engine/leverage-decay.js` documentation (already in `ENGINE-VERSIONS.md`).
- `#redundancy` — links to `engine/redundancy-detector.js` registry (60+ wrappers, conservative bias toward false-negative).
- `#concentration` — links to `engine/concentration-guard.js` 25/50/65% rules.
- `#thesis-drift` — pending M4. M1 footer says: "(Coming M4)."
- `#catalyst-link` — pending M2.
- `#microcap-distress` — pending M3.

Methodology page integration: each anchor lives in `/methodology` with a "Last updated" date and a link to the engine source (read-only).

---

## Section F — Scientific rigor (null hypothesis + A/B logging)

### F1. Null hypothesis

**H0:** Showing this surface to Kadeem does NOT change his portfolio's risk-adjusted outcome relative to the cockpit-only baseline.

**H1:** Showing this surface produces ≥ 0.05 Sharpe-ratio improvement over a 90-day window (rolling), measured against a frozen cockpit-only counterfactual.

This is **not** an A/B test of users (n=1). It is a single-user time-series eval. We need:

1. A **frozen counterfactual decision rule** ("what would cockpit-only have suggested?") logged at every action moment.
2. An **actual decision log** of what Kadeem did (already partially captured in `api/decision-replay.js`).
3. A 90-day eval window that compares H0 vs H1 outcomes.

We are NOT able to credibly run this with n=1; it can only **falsify** the H1 if outcomes are flat or worse. This is per `feedback_first_principles_audit.md` — set up the eval honestly even if statistical power is weak; do not pretend.

### F2. Passive A/B logging (M1 ships)

Every render of the surface emits an analytics event (CSP-compliant, local-only):

```
{
  type: "todays_portfolio_impact_render",
  ts: <ISO>,
  attention_top3: ["MRAL", "SOXL", "TSMX"],
  flags_present: ["concentration", "redundancy", "leverage-decay-extreme"],
  cockpit_top3_at_same_ts: <pulled from current cockpit logic>,
  agreement_score: <jaccard(attention_top3, cockpit_top3)>
}
```

Stored in `THV2Store.appendEvent('thv2.portfolio-impact.events', ...)`. Replayable via `api/decision-replay.js`. **No outbound network.** No PII-grade leakage; tickers only.

### F3. Falsifiable claim

If after 90 days of rendering, the user's risk-adjusted return does NOT exceed the cockpit-only counterfactual, this surface is **honestly retired or rebuilt**. Per `feedback_nexus_kill_failing_experiments.md` — but with double-confirm gate.

---

## Section G — Data gaps & new APIs needed

### G1. `POST /api/portfolio-impact` (NEW, M1-eligible)

**Request:**
```json
{
  "positions": [/* THV2Holdings.rows */],
  "scoresByTicker": { "MRAL": { "score": 71, "tier": "ADD" }, … },
  "forecastsByTicker": { "MRAL": { "expected": 1.4, "horizon": 390 }, … },
  "todaysBars": { "MRAL": { "open": 5.41, "last": 5.17, "high": 5.43, "low": 5.05 }, … },
  "newsByTicker": { "MRAL": [/* M2 */] },
  "venue": "jpmorgan",
  "now": "<ISO>"
}
```
**Response:**
```json
{
  "ok": true,
  "summary": {
    "totalValue": 80973.41,
    "todaysDollarChange": -2340.11,
    "todaysPercentChange": -0.0286,
    "topNCoverPct": 0.78
  },
  "rankings": [
    { "ticker": "MRAL", "attention": 0.91, "rankBand": "solid", "flags": ["concentration","microcap-distress"], "todays_dollar": -…, "concentration": 0.28, "decay_headwind": 0, "redundant_with": [] },
    { "ticker": "SOXL", "attention": 0.74, "rankBand": "solid", "flags": ["leverage-decay-extreme"], "todays_dollar": -…, "concentration": 0.07, "decay_headwind": 87, "redundant_with": ["TSMX","TSM-fam"] },
    …
  ],
  "flags": [
    { "kind": "concentration", "ticker": "MRAL", "weight": 0.28, "rule": ">25%", "severity": "hardViolation" },
    { "kind": "redundancy", "underlying": "TSM", "tickers": ["SOXL","TSMX"], "totalWeight": 0.16 },
    …
  ],
  "openQuestions": [
    "MRAL microcap-distress not yet computable — pending M3",
    "Catalysts for SOXL today not yet linked — pending M2"
  ],
  "methodology": "/methodology#todays-portfolio-impact"
}
```
Pure-fn over inputs, mirrors the lane-2 `api/portfolio-preflight.js` envelope shape. Reuses redundancy-detector + leverage-decay + preflight-hygiene + concentration-guard. **M1-shippable today.**

### G2. `POST /api/news-impact` (NEW, M2)

Takes `{tickers[], from, to}` and returns per-ticker tagged catalysts. Internally:
- Calls `api/news/headlines.js` for raw heads.
- Tags by ticker via existing classifier (or M2-shipped tagger).
- Returns `[{ticker, headline, source, ts, catalystType, sectorImpact}]`.

Pre-existing endpoints (`api/news.js`, `api/news-aggregator.js`, `api/news-flow.js`) handle the raw fetch; this is a tagging/clustering layer on top.

### G3. `POST /api/microcap-distress` (NEW, M3)

Takes `{ticker}` and returns:
```json
{
  "ticker": "MRAL",
  "asOf": "<ISO>",
  "drawdown_from_high": -0.87,
  "cash_runway_quarters": 3.2,
  "dilution_event_count_ttm": 2,
  "going_concern_flag_in_10k": false,
  "distress_score": 78,
  "severity": "high",
  "methodology": "/methodology#microcap-distress",
  "data_freshness": "2025-Q4 10-K + 2026-Q1 10-Q",
  "data_source": "SEC EDGAR (api/sec-fundamentals existing endpoint)"
}
```
Reuses existing SEC fundamentals endpoint. Composes drawdown + cash runway + dilution into a single distress score. Conservative bias toward false-negative (do NOT cry wolf).

### G4. `POST /api/intraday-bars` (NEW or extension, M1-blocking)

Today's open/high/low/last for each held ticker. Sources: existing finnhub/polygon endpoints. Composer endpoint, not a new data source. M1 cannot ship $-today without this OR a graceful "session change unknown" degradation.

### G5. Store schema extension (M4 unblock)

Persistent `scoreHistoryByTicker` to support score-velocity / volatility / stability / thesis-drift. Already on lane-2's deferred list. M4 of this surface depends on this landing.

---

## Section H — Phased ship plan

| Milestone | Scope | Dependencies | Effort | LoC | Risk |
|---|---|---|---|---|---|
| **M1** | Positions + today P&L + redundancy + leverage-decay flags + concentration + leverage-stack flags. No news. No microcap. No thesis-drift. Default landing + filters + ranked list + drill-down + methodology footer. | `engine/portfolio-impact.js` (NEW; this spec scaffolds), `pages/portfolio-impact.html` (NEW), `api/portfolio-impact.js` (NEW, mirrors lane-2 envelope), `api/intraday-bars.js` (NEW or extension). All other deps EXIST. Lane-1 patches `app.js` nav-link wire-up only. | 1.0–1.5 days | ~600 LOC + ~200 LOC tests | LOW — entirely composes existing primitives |
| **M2** | News-event tagging. Catalysts per ticker per session. Catalyst-presence dim added to attention score. | `api/news-impact.js` (NEW). Tagger heuristic (rule-based start; ML later). Methodology entry `#catalyst-link`. | 2.0 days | ~400 LOC + ~150 LOC tests | MEDIUM — tagging quality risk |
| **M3** | Microcap-distress flag. Cash runway + 52w drawdown + dilution proxy. | `api/microcap-distress.js` (NEW). SEC EDGAR fundamentals composer. Methodology entry `#microcap-distress`. | 2.0 days | ~500 LOC + ~200 LOC tests | MEDIUM — data-freshness risk; conservative bias mandatory |
| **M4** | Cohort comparison + thesis-drift detector. Score-flip detection vs entry. "Was your reason for buying still valid?" surface. | `scoreHistoryByTicker` store extension, `api/cohort-compare.js` (NEW), thesis-log promotion to first-class. Unblocks score-velocity/volatility/stability deferred from lane-2. | 3.0–4.0 days | ~700 LOC + ~250 LOC tests | HIGH — store schema migration; cohort comparison may not be viable for n=1 user — may instead surface "this position vs analogous tickers" |

**Total M1-M4:** ~8.0–9.5 days, ~2200 LOC + ~800 LOC tests.

**M1 ships first** because it covers the canonical golden-test scenario (Kadeem 2026-04-28) using only existing primitives. M2-M4 add progressively more inputs but do not change the surface's core promise.

---

## Section I — Directives this surface closes (charter alignment)

| Directive | How it closes |
|---|---|
| `feedback_scientific_rigor_portfolio_wide.md` | "We do not have enough confidence to call" gate (D4). Methodology footer on every number (E). Falsifiable H1 (F). |
| `feedback_ui_bar.md` | Beats Bloomberg RSK on per-position dissonance disclosure; beats Schwab Today's Impact on factor-level explanation. |
| `feedback_superior_to_competition.md` | Named dimension: **factor-dissonance disclosure**. No competitor surfaces "score 71 BUT structural factors disagree." |
| `feedback_silent_failures.md` | Every input degraded gracefully (Section B "Status" column). Surface tells user what it COULDN'T compute (`openQuestions[]`). |
| `feedback_first_principles_audit.md` | The brief endpoint was the wrong-path attempt; this redesign re-derives from "what does the user need at 11:42 AM ET when down -$2,340?" |
| `feedback_thv2_best_practices.md` | New page over cockpit bloat. Pure-fn engine. Composes existing primitives. CSP-safe. |
| `feedback_nexus_audit_trail_by_default.md` | F2 passive A/B logging into THV2Store events; replayable via decision-replay. |
| `feedback_aggregator_display.md` | Ranking + drill-down with inline drill-out into per-position factor view. |
| `feedback_actionable_first.md` | Top-3 attention-ranked first; everything else collapsed by default. |
| `feedback_no_defaults.md` | Surface refuses to render hardcoded suggestions when computed values are missing. |
| `feedback_progressive_disclosure.md` | Summary → ranking → drill-down → methodology, four levels deep. |

---

## Section J — Out of scope (explicitly)

- **Trade execution.** This surface RECOMMENDS; the broker integration handles execution. No edits to `api/broker/*`.
- **News fetching.** `api/news/*` exists; M2 adds a tagging layer, not a new fetch.
- **Cockpit modification.** Lane 1's territory. We add a nav link only — and we propose that as a comment, not a patch (lane 1 owns the patch).
- **Pillar-0 territory.** No edits to `app.js`, `engine/playbook.js`, `index.html`, `cockpit.html`.
- **Lane-2's 19 W1+W2 engine modules.** We CONSUME; we never modify.
- **Production deploy.** Working tree only.
- **Scorecard upgrade.** Will earn `intelligence.position-aware-views +0.5` after M1 ships and is wired by lane 1; not claimed in this spec.

---

## Section K — Open questions for next planning pass

1. Should "Today's Portfolio Impact" replace the cockpit's holdings panel, or be a peer page accessed via nav? **Recommendation:** peer page (M1), with cockpit holdings panel showing a single summary line "3 positions need attention — open Today's Impact".
2. How should the surface treat coinbase positions? **Recommendation:** M1 ships chase-only; M2 expands to coinbase with a venue filter.
3. Does the dollar-loss attention dim need a sign? Should "+$X today on a position you wanted to trim" rank as high as "-$X on a position you wanted to hold"? **Recommendation:** v1 uses absolute value; v2 (post-M4 thesis-drift) uses signed-vs-thesis.
4. Should the surface auto-refresh? **Recommendation:** poll every 60s when tab focused; freeze when blurred. Do NOT poll when market closed.
5. Mobile-first vs desktop-first build order? **Recommendation:** desktop-first M1 (Kadeem trades from desktop); mobile-responsive M2.

---

## Appendix — File manifest

**Created in this scaffold (working tree only, no deploy):**
- `trading-hub-v2/docs/todays-portfolio-impact-spec-2026-04-28.md` (this file)
- `trading-hub-v2/engine/portfolio-impact.js` (pure-fn engine, M1 stub)
- `trading-hub-v2/tests/portfolio-impact.test.js` (vitest cases, fail-on-TODO)
- `trading-hub-v2/pages/portfolio-impact.html` (skeleton; renderer throws "M1 not yet implemented" pre-build)
- `nexus-command-build/.locks/agent-thv2-portfolio-impact-spec.delta.json`

**Decision log entry:** appended to `nexus-command-build/NEXUS_COMMAND_DECISION_LOG.md`.

**Not touched:** `app.js`, `engine/playbook.js`, `index.html`, `cockpit.html`, `lib/cockpit-preflight.js`, `lib/preflight-banner.js`, `engine/preflight-pipeline.js`, all 19 W1+W2 engine modules, `package.json`, `vercel.json`, `sw.js`.
