---
phase: 07-heartbeat-unified-per-symbol-view
verified: 2026-03-25T20:00:00Z
status: passed
score: 4/4 must-haves verified
re_verification: false
---

# Phase 7: Heartbeat Unified Per-Symbol View Verification Report

**Phase Goal:** Merge Yesterday + Junction sections into a single exception-only display covering all registry symbols. Yesterday expands from BTC/ETH to all enabled Binance spot symbols (from symbols.toml). Junction replaces raw TID dumps with delta-only (0=clean). When all symbols are clean, show one summary line. Only expand individual symbols when issues exist.
**Verified:** 2026-03-25T20:00:00Z
**Status:** passed
**Re-verification:** No — initial verification

## Goal Achievement

### Observable Truths

| #   | Truth                                                                                                                                           | Status   | Evidence                                                                                                                                                                                       |
| --- | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 1   | When all symbols are clean (yesterday=no gaps, junction delta=0), the Telegram message shows one summary line instead of expanding every symbol | VERIFIED | `formatter.py:406-409`: `if not issues: lines.append(f"  {symbols_checked}/{symbols_checked} symbols clean{junc_note}")`                                                                       |
| 2   | When issues exist, only problematic symbols are expanded with detail; clean symbols are not listed individually                                 | VERIFIED | `formatter.py:410-417`: issue loop iterates only `(sym, desc)` tuples collected for problem symbols; no per-symbol listing for clean entries                                                   |
| 3   | Junction section shows TID delta per symbol, not raw TID dumps                                                                                  | VERIFIED | `junction.py:67-78`: `delta = int(w_tid) - int(r_tid) - 1`; stores `junction_delta_{sym}` (int) and `junction_detail_{sym}` (only when delta != 0); old aggregated string keys entirely absent |
| 4   | Yesterday + Junction are merged into a single unified section in the formatter                                                                  | VERIFIED | `formatter.py:372-417`: single `# -- Per-Symbol Health (unified Yesterday + Junction) --` block; no separate `Yesterday</b>` or `Junction</b>` section headers in any `append()` call          |

**Score:** 4/4 truths verified

### Required Artifacts

| Artifact                               | Expected                          | Status   | Details                                                                                                                                                                   |
| -------------------------------------- | --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `scripts/heartbeat/checks/junction.py` | Per-symbol TID delta computation  | VERIFIED | Contains `junction_delta_{sym}` at lines 68 and 75; delta formula `int(w_tid) - int(r_tid) - 1` at line 67; `junction_available` replaces deprecated `junction_telemetry` |
| `scripts/heartbeat/formatter.py`       | Unified per-symbol health section | VERIFIED | Contains `Per-Symbol Health` at lines 372 and 404; imports `get_heartbeat_symbols` from `heartbeat.config` at line 11; no `HEARTBEAT_PRIORITY_SYMBOLS` import             |

### Key Link Verification

| From                                    | To                               | Via                                         | Status | Details                                                                                                                         |
| --------------------------------------- | -------------------------------- | ------------------------------------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------- |
| `scripts/heartbeat/checks/junction.py`  | `scripts/heartbeat/formatter.py` | `results dict keys junction_delta_{symbol}` | WIRED  | `junction.py:68,75` sets `results[f"junction_delta_{sym}"]`; `formatter.py:390` reads `results.get(f"junction_delta_{symbol}")` |
| `scripts/heartbeat/checks/yesterday.py` | `scripts/heartbeat/formatter.py` | `results dict keys yesterday_{symbol}`      | WIRED  | `yesterday.py:68,71` sets `results[f"yesterday_{symbol}"]`; `formatter.py:380` reads `results.get(f"yesterday_{symbol}")`       |

### Data-Flow Trace (Level 4)

Not applicable — both files are monitoring/formatting utilities, not data-rendering components with their own DB fetch. The formatter reads from the shared `results` dict populated by check modules; junction.py reads from the live sidecar HTTP endpoint. Data flow is correct by structural inspection.

### Behavioral Spot-Checks

Step 7b: SKIPPED — both modified files are not runnable in isolation (they require a live sidecar HTTP endpoint and ClickHouse connection). Syntax validation substitutes as the closest automated check.

| Behavior                                                     | Command                                  | Result                    | Status |
| ------------------------------------------------------------ | ---------------------------------------- | ------------------------- | ------ |
| junction.py parses cleanly                                   | `python -c "import ast; ast.parse(...)"` | `junction.py: syntax ok`  | PASS   |
| formatter.py parses cleanly                                  | `python -c "import ast; ast.parse(...)"` | `formatter.py: syntax ok` | PASS   |
| `junction_delta_` keys present in junction.py                | grep                                     | Lines 68, 75              | PASS   |
| `Per-Symbol Health` present in formatter.py                  | grep                                     | Line 404                  | PASS   |
| No standalone `Yesterday</b>` or `Junction</b>` append calls | grep                                     | 0 matches                 | PASS   |
| Documented commits exist in git                              | `git log --oneline 0fcf4f1 b35c03b`      | Both commits found        | PASS   |

### Requirements Coverage

HB-VIEW-01 and HB-VIEW-02 are defined only within the PLAN frontmatter and ROADMAP — they are not listed as rows in REQUIREMENTS.md. The ROADMAP entry at line 151 references both IDs but does not expand their definitions. The PLAN `acceptance_criteria` and `success_criteria` serve as the effective requirement specification.

| Requirement | Source        | Description (derived from plan)                                                                      | Status    | Evidence               |
| ----------- | ------------- | ---------------------------------------------------------------------------------------------------- | --------- | ---------------------- |
| HB-VIEW-01  | 07-01-PLAN.md | Exception-only unified Yesterday + Junction display; one summary line when all clean                 | SATISFIED | `formatter.py:372-417` |
| HB-VIEW-02  | 07-01-PLAN.md | Per-symbol TID delta computation (`delta = ws_first_tid - rest_last_tid - 1`); replace raw TID dumps | SATISFIED | `junction.py:67-78`    |

### Anti-Patterns Found

| File | Line | Pattern    | Severity | Impact |
| ---- | ---- | ---------- | -------- | ------ |
| —    | —    | None found | —        | —      |

No TODOs, FIXMEs, placeholder returns, or stub implementations found in either modified file.

### Human Verification Required

#### 1. Live Telegram Message Format

**Test:** Trigger a heartbeat run on bigblack when all symbols are healthy. Observe the Telegram message.
**Expected:** Single `Per-Symbol Health` section with one line: `✅ 17/17 symbols clean` (no per-symbol expansion). No separate Yesterday or Junction sections.
**Why human:** Requires live sidecar + ClickHouse + Telegram bot; cannot be verified by static analysis.

#### 2. Issue Expansion When a Symbol Has a Gap

**Test:** Simulate or observe a real yesterday mismatch or junction delta != 0 for one symbol.
**Expected:** Message shows summary line like `16/17 clean, 1 issue(s):` followed by `❌ XRPUSDT: yest mismatch: expected X, got Y`.
**Why human:** Cannot inject fault conditions without live infrastructure.

#### 3. Junction Sidecar Unavailable Path

**Test:** Stop the sidecar, then trigger heartbeat.
**Expected:** Summary line includes `(junction: sidecar unavailable)` suffix; no crash.
**Why human:** Requires deliberately stopping a production service.

### Gaps Summary

No gaps. All four observable truths are verified against the codebase. Both artifacts exist, are substantive, and are correctly wired through the shared results dict. Old aggregated junction keys (`junction_rest_last_tid`, `junction_ws_first_tid`, `junction_forming_bar`, `junction_committed_floors`, `junction_telemetry`) have been fully removed from both files. The `HEARTBEAT_PRIORITY_SYMBOLS` constant is no longer imported in formatter.py. yesterday.py already uses `get_heartbeat_symbols()` (confirmed at line 33), satisfying the all-symbols expansion requirement. The phase goal is achieved.

---

_Verified: 2026-03-25T20:00:00Z_
_Verifier: Claude (gsd-verifier)_
