Skip to content

feat(ingest): country + exchange listing metadata (PR-A1, schema 0.10.12)#347

Merged
dackclup merged 1 commit into
mainfrom
claude/sharp-turing-q3A5J
Jun 1, 2026
Merged

feat(ingest): country + exchange listing metadata (PR-A1, schema 0.10.12)#347
dackclup merged 1 commit into
mainfrom
claude/sharp-turing-q3A5J

Conversation

@dackclup
Copy link
Copy Markdown
Owner

@dackclup dackclup commented Jun 1, 2026

What

First of a 2-PR sequence (locked via a /grill-me session) to replace the hero's sector+industry chips (next to the #1 rank) with a country tag (🇺🇸 US) + exchange tag (NYSE/NASDAQ), each with a logo. PR-A1 is the compute + schema half; PR-B does the chips.

The 5 grill-locked decisions

  1. ingest country + exchange for real (not placeholder)
  2. remove both sector + industry chips from the feat(phase-0): project scaffolding #1 row (sector still shows in the SECTOR tile below)
  3. real SVG flag + exchange logo
  4. flag real (public domain) + exchange a generic icon (avoid the NYSE/NASDAQ trademark)
  5. split: PR-A ingest / PR-B chips

PR-A1 — compute + schema

compute/ingest/cross_source.py:

  • exchange_name(code) — maps yfinance fast_info.exchange codes → display names (NMS/NGM/NCM→NASDAQ, NYQ→NYSE, ASE→NYSE American, PCX/NyseArca→NYSE Arca, BATS→Cboe BZX); unknown → passthrough
  • country_for_exchange(code) — "US" for known US venues, else None
  • fetch_yfinance_exchange(ticker) — cache-first via the same yfinance_info/<ticker>.json cache as market-cap (exchange merged in, market_cap preserved); tenacity-retried; QR_SKIP_CROSS_SOURCE honored; graceful-degradation → None

Schema triple (MINOR bump 0.10.11 → 0.10.12-phase4.6): StockDetail.exchange: str | None + StockDetail.country: str | None + Metadata.exchange_coverage_pct: float | None (Rule-18) — lockstep across schemas.py + types.ts + schema-snapshot.json + config + test_config pin + SKILL.md table.

schema-sentinel verified the hand-regenerated snapshot byte-matches the generator contract (value-key order type/required/default, alphabetical position, type strings, +15/−0 diff) → the CI schema guard will PASS.

Tests (test-engineer): tests/test_ingest/test_cross_source_exchange.py — 39 offline (pure helpers + cache merge/backward-compat + QR_SKIP path) + 1 @network AAPL drift.

Observability-before-wiring

The schema fields ship first. main.py field-population is PR-A2 (deferred — the orchestrator is 2000+ lines and the session was long; fresh focus avoids a careless miss). Until PR-A2 the fields are None-default (valid — | None). The hero chips (PR-B) wait for ≥ 1 cron confirming exchange_coverage_pct.

Scope

Compute + schema. DISPLAY-ONLY — no ranking / scoring / valuation / defense-layer change. CLAUDE.md §Phase status + AGENTS.md + SKILL.md schema row + PHASE_STATUS_INFLIGHT.md.

https://claude.ai/code/session_0148EoMmL6zakDWqHXjqQ9yq


Generated by Claude Code

….12)

First of a 2-PR sequence (grill-me spec) to replace the hero's sector+industry
chips next to the #1 rank with a country tag (US) + exchange tag (NYSE/NASDAQ),
each with a logo. PR-A1 is the compute + schema half; PR-B does the chips.

compute/ingest/cross_source.py:
- exchange_name(code): maps yfinance fast_info.exchange codes to display names
  (NMS/NGM/NCM->NASDAQ, NYQ->NYSE, ASE->NYSE American, PCX/NyseArca->NYSE Arca,
  BATS->Cboe BZX); unknown codes pass through verbatim.
- country_for_exchange(code): "US" for all known US venues, else None.
- fetch_yfinance_exchange(ticker): cache-first via the SAME yfinance_info cache
  as market-cap (exchange merged in, market_cap preserved); tenacity-retried
  _yf_fast_exchange; QR_SKIP_CROSS_SOURCE honored; graceful-degradation -> None.

Schema triple (MINOR bump 0.10.11 -> 0.10.12-phase4.6): StockDetail.exchange:
str|None + StockDetail.country: str|None + Metadata.exchange_coverage_pct:
float|None (Rule-18 observability), added in lockstep across schemas.py +
types.ts + schema-snapshot.json. config SCHEMA_VERSION + test_config pin +
SKILL.md schema-table row updated. schema-sentinel verified the hand-regenerated
snapshot byte-matches the generator contract (value-key order, alphabetical
position, type strings, +15/-0 diff) -> CI schema guard will pass.

tests/test_ingest/test_cross_source_exchange.py (test-engineer): 39 offline
(pure helpers + cache merge/backward-compat + QR_SKIP path) + 1 @network drift.

DISPLAY-ONLY: no ranking/scoring/valuation/defense change. Observability-before-
wiring: schema fields ship first; main.py field-population is PR-A2; the hero
chips (PR-B) wait for >=1 cron confirming coverage. Until PR-A2 the fields are
None-default (valid, they are |None).

https://claude.ai/code/session_0148EoMmL6zakDWqHXjqQ9yq
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
quantrank Ready Ready Preview, Comment Jun 1, 2026 5:29am

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Jun 1, 2026

Pre-merge production simulation

Field Value
Duration 383s
Universe size 502
Schema version 0.10.12-phase4.6
Compute commit 2ecb5e2b7057095ef316ca74c7b29a681db78784
PR-branch output pr-347-compute-output (14-day retention)

Diff vs main

Field Main PR Δ
Universe size 502 502 +0
Schema version 0.10.11-phase4.6 0.10.12-phase4.6 ⚠️ bumped

Main baseline: 2026-05-29T23:21:23Z (2.3 days old)

Top-10 movers (sorted by |Δcomposite_score|)

Ticker PR rank main rank Δrank PR score main score Δscore
STE 349 267 -82 45.96 50.20 -4.24
ADSK 179 109 -70 53.93 57.52 -3.59
TYL 417 448 +31 41.48 39.13 +2.35
DELL 281 234 -47 49.36 51.53 -2.17
TGT 41 61 +20 63.08 60.99 +2.09
WMT 210 160 -50 52.65 54.65 -2.00
NXPI 167 211 +44 54.33 52.35 +1.98
BF-B 264 223 -41 50.03 51.98 -1.95
HOOD 280 245 -35 49.40 51.24 -1.84
V 194 235 +41 53.34 51.50 +1.84

@dackclup dackclup marked this pull request as ready for review June 1, 2026 05:40
@dackclup dackclup merged commit 5f39d64 into main Jun 1, 2026
5 checks passed
@dackclup dackclup deleted the claude/sharp-turing-q3A5J branch June 1, 2026 05:40
dackclup added a commit that referenced this pull request Jun 1, 2026
…(PR-A2) (#349)

Populate the StockDetail.exchange / .country fields PR-A1 (#347) declared,
plus the Rule-18 Metadata.exchange_coverage_pct diagnostic, from the Step-8
per-ticker cross_source loop. Display-only -- no ranking / scoring /
valuation / defense-layer change; NO schema change (stays 0.10.12-phase4.6,
the fields already exist from PR-A1).

- Import country_for_exchange / exchange_name / fetch_yfinance_exchange.
- Two {exchange,country}_by_ticker accumulators before the Step-8 loop.
- Per-ticker fetch_yfinance_exchange piggybacks the cross_source market-cap
  block; skip-safe via QR_SKIP_CROSS_SOURCE inside the helper (no main.py
  branch -- confirmed no QR_SKIP_CROSS_SOURCE ref in main.py).
- exchange= / country= kwargs on StockDetail (siblings of industry=).
- exchange_coverage_pct via a pure _exchange_coverage_pct helper, imported by
  the tests (NOT copied) per the CLAUDE.md PR #310 no-verbatim-copy lesson;
  + "Exchange coverage" log + the Metadata kwarg.

PR-B (hero country/exchange chips) waits for >= 1 cron confirming coverage
(observability-before-wiring).

Tests +19 (test_main.py orchestrator wiring + test_output/test_exchange_schema.py
schema round-trip); full offline suite 1460 passed, 0 failures. Pre-push gate:
quantrank-reviewer READY-TO-PUSH, phase-coordinator LOCKSTEP-SATISFIED,
docs-reviewer DOCS-CLEAN.

https://claude.ai/code/session_0148EoMmL6zakDWqHXjqQ9yq

Co-authored-by: Claude <noreply@anthropic.com>
dackclup added a commit that referenced this pull request Jun 1, 2026
Replace the stock-detail hero #rank-row sector + industry chips with a country
chip (country-flag-icons flag + ISO tag) + an exchange chip (lucide Landmark
generic icon + display name), reading detail.country / detail.exchange
(populated by PR-A2, #347 -> #349). Display-only; no compute / schema / scoring
/ valuation change.

- New frontend/components/ListingChips.tsx -- LedgerCraft neutral-steel chips
  (mirrors SectorChip), null-safe (renders nothing until a cron populates the
  fields), FLAG_BY_COUNTRY static per-country lookup (US today).
- New dep country-flag-icons ^1.6.17 (MIT, 0 transitive, 0 install-script --
  dependency-auditor + security-reviewer both SAFE). Per-country STATIC subpath
  import only; barrel/dynamic = 330KB footgun (new CLAUDE.md Gotcha, mirrors
  lucide).
- frontend/app/stock/[ticker]/page.tsx -- swap SectorChip + industry span for
  ListingChips; drop the now-unused SectorChip import. Sector still shows in the
  HeroAttributeTiles Sector tile.

Held Draft -- merge after the next weekly cron populates exchange/country +
confirms exchange_coverage_pct (observability-before-wiring); current data has
the fields null so the chips render nothing on live until then.

frontend-design-reviewer 0 FAIL (D1 font-medium + D2 flag/icon height parity +
F1 title a11y all fixed). tsc --noEmit clean; next build 506/506 static pages;
flag tree-shaken (~2KB, no barrel bloat).

https://claude.ai/code/session_0148EoMmL6zakDWqHXjqQ9yq

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants