Backlog
Ranked list of work, parked items, and open methodology questions. The parked queue is not ordered; items graduate to the next cycle plan when ready.
This page is the session-start reference for "what's next." Pair
with the latest handoff in #esg-screening for current cycle state.
Last refreshed: 2026-05-26 (fourteenth session) — Companies rename, Held pill, full-width explainer, filter-pill fix all logged and immediately closed (876e6a4). Thirteenth session: Watchlist → "Additional Findings" tidy (fb49123). Twelfth session: Detail-page display tidy (4b53dae): dark-rule suppression + Major Shareholders panel removal. ADR-0031 post-ship carry-forward queue fully clear. Tenth session (2026-05-26): ADR-0031 post-ship carry-forward queue cleared: RAG recalibration assessed no-change; display tidy (#4b) fully closed (rounding standard + phantom-row verification); fossil/E7 weighting assessed no-change. rag.js unchanged. Ninth session (2026-05-26): #4b weapons-floor NULL-pillar display defect fixed (approach A: engine.js:61 NULL→0; composites unchanged; all three surfaces corrected — list/holdings/detail now show 0.0 not 100/—/'No adverse findings'). Parked item logged and immediately closed. Eighth session (2026-05-25): ADR-0031 SHIPPED (origin/main 99d9645, run_id=53): symmetric baseline-100 scoring model; agency-weighted positives (migrations 064/065/066, self k=22.5 / external k=45 / neutral k=0); weapons→S=0 hard floor. Distribution: Green 46 / Amber 25 / Red 8. RAG recalibration, ADR-0031 doc-tidy, page-wording updates, display tidy (score-rounding + obs-3 verification), and fossil/E7 weighting review added to parked queue. Seventh session: BHRRC→NF-S4 projection-vs-actual W reconciliation DONE (scripts/bhrrc-w-report.js, commit cc9fde7); divergence root cause: watchlist_entry stale/incomplete vs. live API; fix reads W from signal.raw_value (scorer output). Sixth session: UN-G1/G4 retired as CDP-non-viable (migration 063, ADR-0030, score-neutral, liveRuleCount 22→20); scraper error-path crash fixed (run_id=49, 063 commit). Fifth session: Display bug fixed: detail-page dropped-source rows (4 commits incl. migration 060); busy_timeout cron-killer fixed (7446bda, run_id=48 full 79 clean — scheduler de-risked); de-scoped scraper Pass A complete (08ad24d + migration 061); doc dedup (CLAUDE.md 320e09a). RAG Green-threshold raise CLOSED — ASSESSED, NO CHANGE. Pass B (9 dead rules) DONE (migration 062). Earlier same day (fourth session): NF-S4.2 (GOOGLE-NEWS-RSS) dead-wiring CLOSED — ASSESSED AND DECLINED (run_id=46 throwaway probe). Third session: BHRRC→NF-S4 projection-vs-actual W reconciliation logged to parked queue (run_id=46, ADR-0029). Second session: BHRRC→NF-S4 wiring gap DONE (ADR-0029, migration 059, severity-modelled, 8 band changes); NF-S4.2 (GOOGLE-NEWS-RSS) logged as separate gap. First session: obs-1 weighting review CLOSED MIXED (ADR-0026 updated); baseline re-anchor DONE (ADR-0026); CORP-WEBSITE permanently dropped (ADR-0027); Lever B DONE (ADR-0028); Lever C DONE; scheduler enabled; pillar-card surface honesty fix (c) DONE; TNFD-ADOPTERS/IFRS-S2-MANDATE/TPI-SECTOR built (migrations 053–057). Refresh discipline: update at the end of any session that completes or reorders an item.
Phase 1 completion plan
Established 2026-05-21 following v1.5 close (cycle 7 landed). Replaces the prior v2 work plan. Phase 2 (financials triangulation with credit and returns) de-scoped — see "Future ideas" section below.
Thesis: complete the source catalogue, lift coverage to the point where the screen produces a defensible read on every institution in the universe, and decide the scoring methodology against real coverage data.
Phase 1 complete when: (a) source catalogue built out per the cycle plan below (b) median financial coverage exceeds a threshold to be set after the first post-build scoring run (c) scoring methodology implemented (neutral-prior, baseline 50/100; recorded in ADR-0012) and verified against full coverage data (d) v1.5 tail items closed (PRB seed completeness, RAG green band calibration, BankTrack recovery)
Cycle plan (sequential; small items may land opportunistically between):
| Cycle | Scope | Type | Status |
|---|---|---|---|
| 8 | BankTrack recovery monitor (passive); UN-G4/G7 overlap audit | Small / methodology | Done 2026-05-21 |
| 9a | UN-G4 → NF-G4 rename + rule_type structural audit (migrations 026/027/028) | Small / methodology | Done 2026-05-21 |
| 9b.5 | Current holdings feature (is_held flag; /holdings page; held column on /; admin toggle) | Small / operational | Done 2026-05-21 |
| 9b | Google News RSS scraper build — inherits corrected NF-G4 foundation (no rule_type issues, no naming ambiguity) | Build (large) | Done 2026-05-21 |
| 10 | NGO-TARGETS scraper build — seed-backed (FoE / RAN / ShareAction); feeds E7.3 + S6.2; financials-only; migration 031 (placeholder UPDATE not INSERT — PAX/NZBA activation pattern). | Build (large) | Done 2026-05-21 |
| 10.5 | NGO-TARGETS seed activation — 16-entry seed compiled in chat from FoE / RAN / ShareAction current campaign material (BoCC excluded; universe-only). Migration 032 added ngo_targets_match_name to 8 institutions' scraper_config_json (preflight caught short-form vs legal_name mismatch). Source pending → healthy. 9 matched financials, 16 watchlist entries. HSBC HK non-inheritance confirmed (parent LEI not consulted by scraper). |
Operational | Done 2026-05-21 |
| 11 | Seed-backed scrapers batch 1 — CHRB, FOREST500 | Build | Done 2026-05-21 |
| 11.5a | CHRB seed compiled (9 entries, 8 institutions, 2022+2023 combined per ADR-0014). VW chrb_match_name wired (migration 035). Scraper field names aligned to seed schema. | Operational | Done 2026-05-21 |
| 11.5b | FOREST500 seed compiled both sides (5 company 2025 + 24 finance 2024). Migration 038 wired 9 institutions; Amazon/JPMorgan name-corrected; VW removed (absent from F500 universe). ADR-0015. Dry-run: 17 matched (14 fin E7.4 / 3 NF NF-E6-STAP.2), 5 correctly not-found. esg-screening cdee163 / esg-screen-ops 62cb175. | Operational | Done 2026-05-22 |
| 12 | INFLUENCEMAP + CA100PLUS seeds compiled + scrapers wired + ADR-0016 accepted. 12a (chat): CA100PLUS flags + qualitative notes (6 names). 12b (Claude-in-Chrome): Measure A (LobbyMap Org Score + Band) captured for all 6 names, cross-checked against LobbyMapScores DOM table (1400 rows, no export — browser-read). Scoping: INFLUENCEMAP = scored seed (Measure A, lobbymap_organization_score); CA100PLUS = flag layer (no scoring rules; watchlist-only — focus-company + disclosure indicator Measure B). Seeds: data/seed/influencemap-seed.json + data/seed/ca100plus-flag-seed.json (6 entries each). Scrapers: src/scrapers/influencemap.js (G6.1/G6.2 signals), ca100plus.js (watchlist only). Migration 039: wired influencemap_match_name + fixed ca100plus_match_name (Caterpillar→Caterpillar Inc.) on INST-0007; promoted both sources candidate→live. Dry-run INST-0007: INFLUENCEMAP G6.1 ok=1 bool=1 conf=1.0 (D+/58), G6.2 bool=0 (58≤75); CA100PLUS watchlist entry ok (disclosure=Partial). Matched 1/6 in-universe (5 pre-compiled for c15b/c/d). ADR-0016. | Build | Done 2026-05-22 |
| 13 | Done 2026-05-22. Full scoring run run_id=29 (320 ok / 64 failed, all BankTrack, expected). Coverage assessed: financial median 46.2% engine / 53.8% source view; NF 37.5%. ADR-0012 contingency confirmed (both below ~60% → neutral-prior holds, no flag). Decision doc committed b22a588. |
Verification | Done 2026-05-22 |
| 14 | COMPLETE 2026-05-22. D1–D3 decision set + surface work all landed. D1 (5d041e6): is_aggregate flag; G3 excluded from Research-depth denominator. D2 (ad8a76e): no-evidence sentinel omitted from score aggregation. D3 (c62376e): E7/E8 honest label + scoring fix. Surface (5d1141e): (a) "Coverage" → "Research depth" on all user-facing surfaces — templates, nav, page headings, /methodology prose and RAG band descriptions; URL /coverage, internal identifiers, coverage-weighted method name all untouched. (b) Pink-box explainer rewritten: neutral-prior copy ("Every institution starts at 50/100…"); old coverage-as-confidence text retired. (c) Null-pillar rendering: pillars with score=NULL now show "—" + "No evidence yet" caption, no RAG colour; confirmed against run_id=29 (S-pillar null for 10 institutions); composite unaffected. Scores byte-identical to post-D1 baseline throughout. |
Build | Done 2026-05-22 |
| 15 | Holdings universe alignment — add ~50 institutions to bring screen in line with actual portfolio (15 asset managers + ~35 direct equity issuers). Internal sub-cycle structure: 15a asset managers; 15b large screen-ready non-financials; 15c mid-cap non-financials; 15d edge cases. See cycle 15 reference notes below. | Build (very large) | COMPLETE 2026-05-22 |
| 15a | COMPLETE 2026-05-22. 14 asset-manager institutions ingested (INST-0023–0036). Migration 044 (d72aa93). GICS code 40203010 (Asset Management & Custody Banks) added to classification table. All 14 scored in run_id=34 (36 total institutions, 537 ok, 64 BankTrack failed — pre-existing). Score range: Crédit Agricole/Nordea 22.7, Schroders 22.4, RBC 18.4, BlackRock/Invesco 6.5, remaining 0.0 (all red/amber; confidence < 0.5 universe threshold, expected at first run). Seed overrides set at ingest: NZBA (Crédit Agricole, Nordea), PRB (Crédit Agricole name-match, Nordea LEI-match), FOREST500 (BlackRock/Invesco/Schroders name-match, Nordea/RBC LEI-match). Operating-arm LEI (d6f2636, migration 045): 7 cycle-2B institutions populated (AIB, Bank of Ireland, ING, JPMorgan Chase, Citigroup, Goldman Sachs, MUFG); all verified GLEIF ACTIVE/ISSUED 2026-05-22. NGO-TARGETS seed (d6f2636): 16 → 19 entries; 3 added (Nordea E7.3, BlackRock E7.3+S6.2). run_id=35 confirms all 3 signals conf=1.0. Full ShareAction broad-roster pass: CLOSED — ASSESSED AND DECLINED (ADR-0019, 2026-05-23; deduction-source no-op skew + no-defer + open provenance — seed complete at 15a state). |
Build | Done 2026-05-22 |
| 16 | COMPLETE 2026-05-22. RAG score-bands recalibrated: green≥30 / amber 15–29 / red<15 (operator-confirmed; commit 6f8a3a7). Band classification over run_id=29: 4 red / 15 amber / 3 green — confirmed spread across all bands. PRB seed: no completeness gaps in current 22-institution universe; deferred to cycle 15a. v1.5 tail item (Phase-1-close criterion (d)) closed. Phase 1 close criteria all met for current 22-institution universe: (a) catalogue built; (b) coverage threshold set (C13); (c) methodology implemented + verified (C14); (d) v1.5 tail closed (C16). Remaining to formal Phase 1 close: cycle 15 (universe expansion) → cycle 17 (close pass). |
Small | Done 2026-05-22 |
| 17 | Phase 1 close pass (walk-through analog to cycle 7). COMPLETE 2026-05-22. run_id=40: 934 ok · 64 BankTrack-failed · 76 institutions. All four criteria met (a) catalogue MET + SFDR gap documented; (b) financial median 37.0% engine / 53.8% source — below ADR-0012 70% contingency threshold; (c) methodology verified incl. BRK/ORIT edge cases; (d) v1.5 tail closed. Surfaces consistent at 76. No defects. Close doc: docs/design/phase-1-close-pass.md. Phase 1 CLOSED. Post-close loose ends both resolved 2026-05-23: (1) [backup-stale] 2026-05-22 alert investigated — confirmed a test artefact from S2 Layer-B verification (scratch path /tmp/tmp.qsMxWHGXku; the "14:17 BST" in the prior handoff was a BST/UTC mix-up = S2 commit 0969bd2 authored 14:17 UTC). Backup pipeline healthy throughout: 03:00 UTC cron ran clean, 76 institutions, well within 36h. DRY_RUN guard added (S3, 10594db). (2) CC auto-updater fixed permanently — npm global prefix moved to user-owned ~/.npm-global, claude bumped 2.1.148→2.1.150, auto-update now self-heals (no recurring sudo). [Operator-environment housekeeping, not ESG code — noted here only because prior handoffs carried it as a recurring line.] |
Close | Done 2026-05-22 |
| S1 | ~~Migration runner BEGIN/COMMIT defence~~ — Done 2026-05-22, cce47bc. migrate.js findTransactionControl() scans pending files, strips comments, rejects BEGIN (all forms)/COMMIT/END TRANSACTION/ROLLBACK before execution. Triggered by migration 037 incident. Refined 2026-05-24, ccc080b: matcher now keys on the TRANSACTION keyword — bare BEGIN (trigger bodies) passes; BEGIN TRANSACTION, COMMIT, END TRANSACTION, ROLLBACK are still rejected. Root cause: migration 056's trigger pairs use bare BEGIN…END blocks, which tripped the original guard. |
Small / CC | Done 2026-05-22 |
| S2 | COMPLETE 2026-05-22. Two-layer backup-failure alerting via Slack webhook (0969bd2). Layer A — backup-db.sh: ERR trap + die() + EXIT handler posts [backup-failed] to #esg-screening on any non-zero exit (mid-script or preflight), including exit code and last 10 log lines. Layer B — scripts/check-backup-freshness.sh at 05:00 UTC: independent cron posts [backup-stale] if /var/backups/esg/ has no file newer than 36h (one miss = noise, two = signal). Webhook: plain incoming webhook bound to #esg-screening, read at runtime from /etc/esg/slack-webhook (azureuser:azureuser 0600); never hardcoded. Architecture note: plain incoming webhook, not a Vextor-style bridge — two-script alerting doesn't justify a bridge service; revisit if ESG grows ongoing autonomous posting. Webhook now also exists for the scheduler digest step (CLAUDE.md §Scheduler step 7) when that's built. Layer A verified: test copy with forced failure → [backup-failed] posted, ok from API; success path silent. Layer B verified: empty dir → [backup-stale]; 48h-old file → [backup-stale]; real fresh dir → silent. Leakage check: zero hooks.slack.com occurrences in repo, scripts, or logs. Crontab: 0 3 backup + 0 5 freshness check both in azureuser crontab (crontab -l confirmed). |
Small / CC | Done 2026-05-22 |
| S3 | check-backup-freshness.sh DRY_RUN guard. Done 2026-05-23, 10594db (esg-screening, confirmed tip of origin/main). Added DRY_RUN=1 env flag: when set, the Layer-B alert function logs the would-be Slack message and returns without POSTing. Prevents proof-of-alert test runs from reaching #esg-screening (root cause of the 2026-05-22 [backup-stale] noise). Verified before commit: DRY_RUN=1 run against a scratch dir logged the message with zero webhook calls. Production cron path (no env override) unchanged. |
Small / CC | Done 2026-05-23 |
| S4 | FI Supplement / Stage 2 removed from app surface (3b003ba, de-scoped 2026-05-21). "AWAITING DATA" panel removed from institution detail + /methodology. Inert DB columns and DESIGN_NOTE_v2_two_stage.md retained. Stage 2 remains a parked Phase-2 idea (see Future ideas). |
Small / CC | Done 2026-05-23 |
| S5 | A(ii) — scraped-negative positive rules score neutral, not zero (aa16106, ADR-0022). Positive-type rules scraped (extraction_ok=1) returning boolean=0 now OMIT from the weighted mean (neutral prior) instead of contributing 0. Fixed 24 institutions (M&G / cycle-15a asset-manager cohort) wrongly at 0.0 → now 50.0. Deduction rules unchanged. |
Small / CC | Done 2026-05-23 |
| S6 | Deduction baseline re-anchor (99fa2ea, ADR-0023). Deduction rules were anchored at 100 (rawScore×20, base_score=5 → 1 violation = 80 points lost) — ceiling-and-subtract, contrary to neutral-prior. Re-anchored to 50: Math.max(0, rawScore×20 − 50). 25 violation-bearing institutions now below 50 (max single-violation ≈ 39.0); 24 clean stay 50. The ×20/−50 coupling is load-bearing — documented in ADR-0023. |
Small / CC | Done 2026-05-23 |
| S7 | Headline composite decoupled from coverage-weighting; list-view blank-score fixed (f4f6fd3). Institutions list + detail hero now read score_value (neutral-prior) directly as the headline; score_coverage_weighted no longer a headline input on either surface. Hero label updated to "ESG Composite" (was "ESG Composite · coverage-weighted"). Fixes list-view blank/dash for confidence=0 institutions (coverage_weighted=null, score_value=50) — they now show 50. Note: score_value = score_coverage_weighted for all currently-scored institutions; the decoupling is architectural (prevents future coverage-crush of the headline display). |
Small / CC | Done 2026-05-23 |
| S8 | G5/G7 +5 documentary sub-rules removed (93d6e3b, migration 052, ADR-0024). G5.sub_rule_id=3 and G7.sub_rule_id=4 (points=+5, inside base_score=5/ceiling=5 deduction rules) were pre-neutral-prior documentary artefacts with no scraper feeding them; clamp(baseScore+pointsSum, floor, ceiling) let a hypothetical +5 mask real violations on the same rule. Confirmed latent-only (no signals, no FK refs, no code reads them by sub_rule_id; zero live triggers in run_id=41). Deleted via Option A. G5 now 2 deduction sub-rules, G7 now 3. Stage 1 hash byte-identical (43a13b7...). One expected correction: G7 confidence 0.75→1.00 (dead G7.4 row had been counting as a missing signal, understating coverage). Closes the parked "G5/G7 points=+5 oddity" item. |
Small / CC | Done 2026-05-23 |
| S9 | /holdings headline composite decoupled from coverage-weighting (fbbd03d). The /holdings page (cycle 9b.5) read score_coverage_weighted (null for the confidence=0 neutral-prior cohort), showing "—" for M&G, Man Group, TIAA, Ninety One, Colchester, Heptagon, Trium, etc. Same root cause and fix as S7/f4f6fd3 — reads score_value directly (e.score_value AS score_coverage_weighted, src/routes/holdings.js:61); worst-first sort repointed to score_value. /holdings was the third surface S7 missed (S7 fixed institutions list + detail hero only). Verified: neutral cohort now 50.0, violation-bearing sort above them, scoring untouched, score hash unchanged. |
Small / CC | Done 2026-05-23 |
| S10 | Watchlist count column removed from institutions list and /holdings (f986c74). Operator decision: the per-institution active-entry count conflated with the ESG score on the list views. Removed from both list surfaces (views/index.ejs colspan 10→9; views/holdings.ejs). Route SQL: index.js sort=watchlist branch removed but watchlist JOIN KEPT (still feeds the "N with watchlist" filter pill + stat); holdings.js sort=watchlist branch + entire watchlist_entry JOIN + 3 SELECT columns removed (no other consumer). KEPT untouched: per-institution detail-page watchlist panel, /watchlist index, /watchlist/:id detail. Display-only; score hash unchanged. |
Small / CC | Done 2026-05-23 |
| S11 | Detail-page dropped-source rows — display bug fixed. Four commits: 40fcfcb (OMIT-sentinel positive labels), 3c15797 (source attribution COALESCE), migration 060 (CORP-WEBSITE source_status 'deferred'→'dropped', constraint widened to add 'dropped'), 3afe764 (deduction-rule clean-OMIT labels). Display-only; score hashes byte-identical. |
Display fix | Done 2026-05-24 |
| S12 | busy_timeout cron-killer bug fixed (7446bda). busy_timeout=5000ms added to both DB connection paths (app + scheduler). Latent defect: would have crashed the 02:00 UTC Sunday cron. Found via manual run; run_id=47 partial crash cleaned up (marked failed). Proven on run_id=48 — first clean full-79 scheduled-path run (Green=23/Amber=30/Red=26, byte-identical to run_id=46). Scheduler de-risked. |
Bug fix | Done 2026-05-24 |
| S13 | De-scoped scraper Pass A — display decommissioning (08ad24d + migration 061). Suppressed CORP-WEBSITE / EP-MEMBERS / TCFD-SUPPORTERS source names from all UI surfaces: detail-page SOURCE column (3-arm COALESCE skips de-scoped sources), methodology effectiveStatus (CORP-WEBSITE now reads 'dropped'), methodology theme labels (dead-source phrases removed), TCFD feeds_rules corrected (migration 061). Score-neutral; no rule retired. |
Display / CC | Done 2026-05-24 |
| S14 | Doc dedup (320e09a esg-screening, f4b21d5 ops). CLAUDE.md Vextor section trimmed to one-liner + §Push-and-deploy added; ops claudemd re-synced. Proposed primer edits pending Rob's manual application in project knowledge. |
Docs | Done 2026-05-24 |
| S15 | Scraper error-path crash fixed (migration 063 commit). Pre-existing dormant crash in run.js error-path SELECT: applicable_sectors column missing from the query's column list. Surfaced on run_id=49 when a scraper threw; fixed in same commit as UN-G1/G4 retirement. Orthogonal to rule retirement. |
Bug fix / CC | Done 2026-05-24 |
| S16 | ADR-0031 — symmetric baseline-100 scoring model + agency-weighted positives (commits f387307 ADR doc, 8661cf7 engine, 99d9645 migrations 064–066; origin/main 99d9645, run_id=53). Supersedes ADR-0026 (neutral-prior/deduction-led); reverses ADR-0007 (flag independence). Pillars start 100; deductions full k=45 (M1±45 / M2±90 / M≥3±100); positives additive but agency-weighted — self k=22.5 (org-instigated: memberships, pledges, disclosure commitments, AND mandatory disclosures UK-MSA/UK-GPG/IFRS-S2-MANDATE), external k=45 (independent third-party assessment or absence from negative list: TPI, InfluenceMap, BoCC, BankTrack, CHRB, Bloomberg GEI/Stonewall S4.5), neutral k=0 (mere list presence, no positive judgement: Forest500). Deductions unaffected. Weapons flag → S=0 hard floor (definitional; applied after S aggregation; no positive lifts it). Fossil unchanged (E7 scores fossil financing; a separate transform would double-count). Distribution at run_id=53: Green 46 / Amber 25 / Red 8. HKSC 70.0. All 19 weapons-tripped institutions at S=0. |
Methodology / CC | Done 2026-05-25 |
Phase 1 completion replan — 2026-05-22
Phase 1 completion replan — 2026-05-22. Hard-deadline push to complete Phase 1 EXCEPT cycle 15; cycle 17 (close pass) follows cycle 15. Rationale: cycle 15 is the only very-large remaining build with genuine design questions (Berkshire look-through, pooled-vehicle edge case, NF-infrastructure stress test at ~35 vs 5 NF institutions) and should not be rushed into a client-facing screen at the end of a long day. Target stopping point for today: cycles 12, 13, 14, 16 complete = source catalogue built out + neutral-prior methodology implemented and verified against the current 22-institution universe. That meets three of the four Phase-1-close criteria (catalogue, methodology, v1.5 tail); criterion (b) coverage threshold is set in cycle 13. Phase 1 then formally completes once cycle 15 lands the universe and cycle 17 runs the close pass.
Dependency chain (sequencing is strict): 12 → 13 → 14 are serial (13 coverage assessment is meaningless until 12 scrapers contribute signals; 14 methodology needs 13 coverage data to set the threshold). 16 (PRB completeness + RAG green-band calibration) lands after 14 because RAG calibration needs post-methodology-shift scores (per existing cycle 14 decision (d)).
Cycle 15 reference notes — Holdings universe alignment
Cycle 15 brings the institution universe in line with Rob's actual portfolio. Established 2026-05-21 after holdings reconciliation against cycle 9b.5's /holdings feature surfaced that the current universe (22 institutions) covers only ~4 of the ~62 institutions touched by the portfolio. The operational-tool framing from cycle 7.5 (post-de-scope: ESG screen as go/no-go investment screening) is partially broken until the universe catches up.
Sits after cycle 14 (methodology implementation) so new institutions land directly into the final methodology rather than getting re-scored mid-flight.
Source holdings list: 69 line items from Rob's portfolio export (Holdings.xlsx, 2026-05-21). Classification: - 1 already in universe (Amazon — INST-0005, marked held 2026-05-21) - 15 asset managers (from 26 pooled vehicles, per Rob's "screen the fund manager, no look-through" rule) - ~43 direct equity issuers - A few edge cases
Cycle 15a — Asset managers (financials)
Entry state: cycle 14 complete (neutral-prior methodology live). LEI table for 14 institutions resolved in chat session 2026-05-21.
Input list: BlackRock Inc, M&G plc, Invesco Ltd, Man Group plc, Amundi (or parent Crédit Agricole), Nordea Asset Management (or parent Nordea Bank), Nuveen (or parent TIAA), BlueBay (under RBC parent), RBC Brewin Dolphin (under RBC parent), Ninety One plc, Colchester Global Investors, Heptagon Capital, Redwheel (RWC Partners), Trium Capital, Octopus Investments. Two share an RBC parent → 14 institutions total. Parent-vs-operating-arm calls: Amundi → Crédit Agricole; Nordea AM → Nordea Bank; Redwheel → Schroders plc (acquired); Nuveen → TIAA (non-listed mutual parent exception). MI SELECT MANAGERS = RBC Brewin Dolphin; MLC Sanlam Ninety One = Ninety One. Full LEI table in chat-session handoff 2026-05-21.
Parked items graduating into 15a: (1) NGO-TARGETS seed comprehensive pass — re-read FoE/RAN/ShareAction against expanded universe and add entries for newly-matchable institutions; (2) operating-arm LEI population for seven cycle-2B candidates (AIB, Bank of Ireland, ING, JPMorgan Chase, Citigroup, Goldman Sachs, MUFG) — exercise LEI-resolution path here.
Pooled-vehicle edge case to note when landing: Amundi Euro Stoxx Banks UCITS ETF holds European banks; under no-look-through rule, screen runs on Amundi/Crédit Agricole not the basket. The basket may include institutions already in the screen. Worth a methodology note; not a blocker.
Complete 2026-05-22. Ingest d72aa93: 14 institutions ingested (INST-0023–0036), GICS code 40203010 added, seed overrides wired, run_id=34 confirms signals for all 14. Operating-arm LEI + NGO-TARGETS seed closed d6f2636: migration 045 (7 LEIs), seed 16→19 entries, run_id=35 confirms new signals. Full ShareAction broad-roster pass: CLOSED — ASSESSED AND DECLINED (ADR-0019, 2026-05-23).
Cycle 15.5 — NF infrastructure audit (conditional)
Trigger: run after 15a if the NF institution count has grown enough to stress the infrastructure before adding the full 15b/15c cohort. NF scoring infrastructure was built around 5 NF institutions; cycle 15b takes it to ~35. Cycle 4.5's onboarding-column audit and cycle 2.5's NF scoping audit identified patterns at 5-institution scale; those need re-validation at larger scale.
Done when: a full scoring run with 15a institutions is clean and no NF-rule scoping gaps have emerged. If clean, skip to 15b. If gaps found, fix before 15b.
Complete 2026-05-22. Gate used run_id=35 (36 institutions, 15a state). sectorApplies() confirmed correct for all new 15b sectors (15, 35, 45, 50, 55). G-pillar null for NF and NF-E4 dead-letter gaps are pre-existing non-blockers. Gate declared CLEAN; 15b proceeded immediately.
Cycle 15b — Large screen-ready non-financials
Entry state: 15a complete (and 15.5 if triggered).
Input list: Alphabet, Microsoft, NVIDIA, Mastercard, Visa, Linde, AstraZeneca, Compass Group, NextEra Energy, Iberdrola, Schneider Electric, S&P Global, Thermo Fisher Scientific, ASML Holding, Berkshire Hathaway (→ 15d for look-through decision), RELX, Booking Holdings, Bank of America (financial — adds to universe), Stryker, Emerson Electric, Union Pacific, CSX, Waste Management, Waste Connections, Intercontinental Exchange. ~25 institutions. Existing source catalogue (BHRRC, Google News RSS, ShareAction, NGO-TARGETS) expected to cover most.
Note: Berkshire Hathaway appears in this input list as a candidate but may be handled in 15d if the look-through design decision is not resolved before 15b runs. Nestle and Iberdrola are in the CA100+ focus list (cycle 12) — verify CA100+ flag wires correctly on ingest.
Done when: ~25 institutions ingested, scoring run clean, per-institution source-coverage diagnostics reviewed, any thin-coverage institutions flagged.
Complete 2026-05-22. Migration 046 (28b7616): 24 institutions ingested (INST-0037–0060) — 23 NF across sectors 15/20/25/30/35/45/50/55 + Bank of America (financial, INST-0060). Berkshire deferred to 15d. 46 new GICS classification entries for 5 new sectors. All LEIs verified GLEIF ISSUED/FULLY_CORROBORATED 2026-05-22. Scoring run_id=36 (60 total institutions) clean: all NF rules scoped correctly, peer ladder falls to sector for all NF sub-industries (all below min-n=10, expected). BoA peers at sub_industry 40101010 (n=20). CA100+ watchlist fired for Iberdrola (Partial) and NextEra (No). MSA search names added for 3 GB institutions (AstraZeneca, Compass Group, RELX) — missing at initial ingest, corrected in migration before commit. Score range: AstraZeneca/Iberdrola/ASML/Thermo Fisher 22.86 (SBTi committed); others 0–14.0 depending on SBTi + sector-specific sources. Thin-coverage flags: Waste Connections (0 positive signals; no BHRRC, CA domicile, very thin NGO coverage), RELX (0 positive; no BHRRC), Stryker (0 positive; no SBTi, no CHRB). Singleton peer groups: Linde sector=15 n=1; Alphabet/RELX sector=50 n=2; NextEra/Iberdrola sector=55 n=2; most NF sub-industries singleton → ladder to sector. Note for RAG band recalibration in cycle 16+.
Cycle 15c — Mid-cap non-financials
Entry state: 15b complete.
Input list: Haleon, Croda International, IMI plc, National Grid, Inditex, Brown & Brown, Amphenol, Sunbelt Rentals Holdings, Constellation Software, Xylem, Copart, Ferguson Enterprises, Synopsys, Broadcom. ~14 institutions.
Note: may have thinner NGO/regulatory coverage than 15b institutions; per-institution source-coverage diagnostics required. National Grid is in the CA100+ focus list (cycle 12).
Done when: ~14 institutions ingested, scoring run clean, thin-coverage findings documented and flagged for cycle 16 or Phase 1.5 attention.
Complete 2026-05-22. Migration 047 (92d2934): 14 institutions ingested (INST-0061–0074) — 13 NF across sectors 15/20/25/30/45/55 + Brown & Brown (financial, INST-0072). 12 new GICS classification entries; 255040 label corrected. All LEIs verified GLEIF ISSUED 2026-05-22 (Inditex GLEIF-duplicate trap: canonical LEI corrected; Brown & Brown LAPSED noted). BHRRC confirmed for 5 institutions; CA100+ National Grid; InfluenceMap National Grid; SBTi 3 institutions; UK-MSA/GPG 4 GB entities wired. Scoring run_id=38 (74 institutions) clean. SBTi fix (dafe969, migration 048, ADR-0017): 9 withdrawn_commitment watchlist entries for "Commitment Removed" status resolved — source-taxonomy artefact (admin timeout ≠ renouncement); logged as 5th source-instrument continuity finding. Scores unaffected.
Cycle 15d — Edge cases
Entry state: 15c complete.
Input list:
- Berkshire Hathaway — resolved: screen BRK directly as single listed entity, no look-through to holdings or securities book. Methodology note: docs/design/berkshire-hathaway-direct-screen-note.md; referenced from ADR-0011.
- Octopus Renewables Infrastructure Trust — resolved: screen ORIT directly as NF infrastructure (physical-asset carve-out from screen-the-manager default). Carve-out note: docs/design/orit-physical-asset-infrastructure-carve-out.md; referenced from ADR-0011. MANAGER NOTE CORRECTION: Octopus Investments Ltd is NOT ORIT's investment manager (stale note). Current manager: Octopus Renewables Ltd / Octopus Energy Generation; AIFM: Octopus Energy AIF Management Ltd (since June 2021). Source-continuity-style correction noted.
Done when: design calls documented (ADR or methodology note), institutions ingested, scoring run confirms expected signal patterns.
Complete 2026-05-22. Migration 049 (21f3e50): 2 institutions ingested (INST-0075 BRK + INST-0076 ORIT). GICS: Multi-Sector Holdings (40201030) + Renewable Electricity (55105020) branches added. Scoring run_id=39 (76 institutions, 934 ok, 64 BankTrack-failed expected) confirms: BRK scored as financial sector 40, no BoA coupling; G6 fired from InfluenceMap (E+/34 band — confirmed match); FOREST500 found BRK as a financier (band=0, no deforestation policy — E7 deduction triggered; this is BRK's entity-level policy assessment, not a look-through to investees). ORIT scored as non_financial sector 55 (NF-G4 / NF-S3 / NF-S4 / UN-E1 applicable; CHRB not_found; SFDR/EU-Taxonomy gap confirmed — no rule exists yet). SBTi ADR-0017 fix verified: BRK SBTi signals show 'not_found' confidence=0.5 (neutral) — no false withdrawn_commitment entries. Universe: 76. Cycle 15 COMPLETE.
Cycle 15e — Holdings reconciliation (post-close) — Complete 2026-05-23. ADR-0021. Migration 051 (0a69135): 3 missed direct-equity holdings ingested (Alcon AG INST-0077, Assa Abloy AB INST-0078, Novartis AG INST-0079). All absent from universe despite appearing in 2026-05-21 holdings export — confirmed gap in cycle 15b/c/d ingest pass, not intentional. LEIs verified GLEIF ACTIVE/ISSUED 2026-05-23; GICS Building Products branch (201020/20102010) added for Assa Abloy. Accompanying data fixes: (a) is_held=1 batched on INST-0037–0076 (40 cycle-15b/c/d institutions — held_via already populated in migration 050, is_held deferred pending this confirmation); (b) stale is_held=1 cleared on INST-0013 Deutsche Bank (no holdings line; legacy cycle-9b.5 error) and INST-0036 Octopus Investments (ORIT now screens directly as INST-0076). Held count before/after: 18→59. Scoring run_id=41 (79 institutions, 959 ok, 64 BankTrack-failed expected): all 3 new institutions scored (Novartis 22.9, Assa Abloy 11.4, Alcon 0.0 — thin coverage expected at first run, no seed matches). Existing-76 score hash verified byte-identical (SHA-256 4550aeb7). Universe: 76→79.
Holdings.xlsx source file: Custodian export, dated 2026-05-21. Keep a copy in data/reference/holdings/ (in the esg-screening main repo) for audit trail; update at the next portfolio refresh.
v1.5 complete 2026-05-21. All 7 v1.5 cycles landed. 22 institutions. First full scoring run (run_id=19) at scale with GICS classification, inheritance, watchlist admin, deduction-rule labelling all live. v2 direction: Stage 2 credit + returns wiring.
v1 complete 2026-05-20. All cycles landed. Pilot scoring against full source catalogue.
Cycle 7 (final v1 cycle) delivered: BankTrack + PAX + CBI + BoCC stub + GICS classification + flag wiring + deploy.yml cleanup + ADR-0009. All 8 pilot institutions have composite scores reflecting the full v1 source catalogue. Coverage 37% on financials (8 sources active; BankTrack signals pending site recovery). Weapons and fossil-fuel flags rendering dynamically for all 8 institutions.
Cycle 8 (v1 surface consistency pass) landed 2026-05-20: source-status job
fixed for BHRRC watchlist hits and PAX weapons flag (flag_weapons column
recognition); RAG band reconciliation done (green band corrected from ≥70 to
≥75 throughout); /methodology rule catalogue corrected to 27 rules (UN-E1,
UN-S1, UN-G1–G4 added; G3-PRB split documented; live counts verified);
detail-page flag chips now rendering dynamically for all 8 institutions;
add-institution design note (docs/design/add-institution-design.md) committed
to main repo. Three UI surfaces now consistent with the scoring engine.
v1.5 cycle plan
Thesis: pilot expansion — take the institution universe from 8 to 20+ across financial and non-financial sectors, validate non-UK rule applicability, and harden the admin tooling needed to keep it growing.
Cycles are sequential; each builds on the last. Small one-off items can land opportunistically between cycles (see section below).
Cycle 1.6 (source catalogue tidy) landed 2026-05-20. 22 pending sources classified: 2 kept pending (BOCC-REPORT — live scraper awaiting annual data drop; GOOGLE-NEWS-RSS — v1.5 small cycle, pending UN-G4/G7 overlap audit outcome). 8 moved to deferred (CDP-CORPORATE, CDP-PUBLIC, CORP-WEBSITE, ECB-EU-REGS, FCA-NOTICES, PCAF-SIGNATORIES, SEC-EDGAR, TCFD-SUPPORTERS). 12 moved to candidate (CA100PLUS, CHRB, EP-MEMBERS, EP100, FAIRR, FOREST500, INFLUENCEMAP, NGO-TARGETS, RSPO-MEMBERS, TNFD-ADOPTERS, TPI-SECTOR, WALK-FREE). SBTI-VALIDATED-CSV deleted (never built; superseded by live SBTI-DASHBOARD + SBTI-CORPORATE). The SBTI-DASHBOARD → SBTI-VALIDATED rename remains on the opportunistic backlog. Reconciliation surprises: none. Rule ID cross-reference check deferred (some pending-now-deferred sources have rule_ids_csv entries for rules still in the catalogue — not blocking, noted for future source×rule audit in cycle 4 diagnostics).
Cycle 1.5 (surface completeness) landed 2026-05-20. Three UI surfaces completed: (a) /runs — scrape run history table; 13 runs visible, status chips, signals ok/failed with RAG tint. (b) /watchlist — global watchlist list; 99 active entries, filters by status and signal_source; cross-links to institution detail watchlist panel. Reconciliation: no parked-queue item to remove — cycle 5 watchlist entry remains correctly scoped to admin status-change surface. (c) /add-institution form — duplicate "Sector" label fixed (Identity section now "Institution type"; GICS section retains "GICS sector"). Required-field minimum reduced to Legal name + LEI only; institution_type defaults to Financial; country_hq auto-fills from GLEIF on submit (and on LEI blur via client-side JS); gics_sector_code defaults to "40" for financials. Cycle 1.6 (source catalogue tidy) starts from this baseline.
Cycle 2A.1 (HSBC PRB absence verification) landed 2026-05-20. Fix path B confirmed. Three-check verification: (1) zero HSBC entities in seed; (2) raw HTML fetch — 356 rows in signatory table, 0 HSBC hits under any entity variant, parser correct ("360+" is marketing rounding); (3) HSBC corporate ESG pages list "UNEP Finance Initiative" as a membership but no mention of Principles for Responsible Banking anywhere. INST-0004 (HSBC Holdings plc) watchlist entry written (source_finding_id=prb-absence-INST-0004, finding_type=withdrawn_commitment, severity=medium, signal_source=PRB-SIGNATORIES). No public news coverage of withdrawal found — unusual for a founding-signatory exit. Parked for monitoring. INST-0004 PRB signal unchanged: boolean=0, conf=0.5.
Cycle 2A (PRB seed expansion) landed 2026-05-20. data/seed/prb-signatories.json expanded from 4 (UK pilot banks) to 356 signatories (full PRB universe as of 2026-05-20). Source: UNEP FI signatories page — page is now server-rendered HTML (not AJAX as originally noted). 153 of 356 entries resolved to a GLEIF LEI (140 clean, 10 auto-disambiguated, 3 preserved from pilot seed); 203 have lei=null and fall back to match_name. scripts/refresh-prb-seed.js committed — run manually at ~quarterly cadence. Key reconciliation surprise: HSBC Holdings plc (founding signatory Sep 2019) is absent from the current list — flagged as potential withdrawal. The 3 remaining UK pilot banks (Barclays, NatWest, Lloyds) produce unchanged boolean=1 / conf=1.0 signals via LEI match. INST-0009 (HSBC HK) unchanged: boolean=0, conf=0.5 — not in PRB list as a separate entity. Cycle 2B (first batch of new non-UK financials) is the next step.
Cycle 11 (pre-cycle-2 scoping fixes) landed 2026-05-20. Three scoping fixes corrected before cycle 2 bulk add:
(a) SBTi branch logic — sbti.js was emitting SBTI-CORPORATE signals only for non-financials; UN-E1.3 and UN-E1.4 were silent for all financials. Fixed; all 5 target institutions (4 UK financials + INST-0009) now have UN-E1 signals.
(b) Country-guard transparency — UK-MSA and UK-GPG scrapers silently returned [] for non-GB institutions. New scrape_skip table (migration 016) records explicit skip events; INST-0009 skip records confirmed in DB.
(c) UN-S1 fallback chain — UN-S1.3 (universal rule) was silently absent for non-GB institutions. Now emits honest no-data signal (confidence=0.0) for non-GB; UK path unchanged. ADR-0010 amendment committed.
Cycle 2 starts from this corrected baseline.
Cycle 2.5 (NF scoping audit — Volkswagen AG) landed 2026-05-20. INST-0022 added (sector 25, DE, non-financial, GLEIF ACTIVE/ISSUED/FULLY_CORROBORATED). Universe: 22 institutions (17 financial + 5 non-financial). run_id=17: 154 ok / 20 failed (BankTrack HTTP ongoing). VW ESG=19.6, conf=0.034. One structural scoping gap found and fixed: pax.js:scrapeWatchlist was emitting signal_source:'PAX' but the FK requires 'PAX-WEAPONS', causing the entire writeWatchlistEntries transaction to fail silently on every run since cycle 2.6A — PAX and BHRRC watchlist entries were never persisted in runs 15–16. Fixed in this session; run 17 confirms 32 inserted (14 PAX tripped-financials + VW BHRRC net-new), 107 updated. All other NF paths clean: SBTI positive case validates cycle 11 fix; UK-MSA/GPG skip+no-data; financial-only sources absent; flag_weapons=NULL on VW. BHRRC: 18 entries (Xinjiang, Brazil supply chain, DRC minerals). See docs/design/non-financial-scoping-audit.md.
| Cycle | Scope | Reference |
|---|---|---|
| 1 | Add-institution interface (option 1: form-based, known identifiers) | docs/design/add-institution-design.md |
| 1.5 | Surface completeness — /runs list, /watchlist global list, /add-institution form revision (label, required-field reduction, GLEIF auto-fill) | — |
| 1.6 | Source catalogue tidy — classify 22 pending sources into pending / deferred / candidate; new /methodology groupings; SBTI-VALIDATED-CSV deleted | — |
| 2A | PRB seed expansion (~350 signatories) — landed 2026-05-20 | — |
| 2A.1 | HSBC PRB absence verification — landed 2026-05-20 (Fix path B; PRB HSBC absence watchlist entry written, source_finding_id=prb-absence-INST-0004) | — |
| 2B | First batch of new financials (non-UK, 12 institutions) — landed 2026-05-20 (INST-0010–0021; 21 total institutions, 17 financial + 4 non-financial) | docs/design/geographic-scoping-audit.md |
| 2.6A | PAX seed-backed conversion + weapons flags corrected — landed 2026-05-20 (dontbankonthebomb.com HTML-shell-only; scraper seed-backed; migration 018; 14/17 financials TRIPPED, 3 CLEAR per 2025 report) | — |
| 2.6B | Holdings-vs-subsidiary methodology note — landed 2026-05-20 (docs-only; docs/design/holdings-vs-subsidiary-framework-memberships.md; implementation deferred to cycle 3) |
— |
| 2.5 | Non-financial scoping audit — landed 2026-05-20 (INST-0022 Volkswagen AG added; run_id=17; PAX FK fix; 1 scoping gap found + fixed) | docs/design/non-financial-scoping-audit.md |
| 3 | GICS full population + inheritance logic + run 18 — landed 2026-05-20 (migration 019; 22 institutions fully classified; 3 NF codes corrected for post-2023 GICS; Goldman=Capital Markets singleton; HKSC PAX inherits from HSBC Holdings; DBS PRB inherits via DBS Bank Ltd; 15 TRIPPED financials; peer ladder active at sub_industry for 16 Diversified Banks) | ADR-0009 amendment 1, ADR-0011, docs/design/gics-classification-rationale.md |
| 4 | Coverage diagnostics at scale — landed 2026-05-21 (source_institution_status SQL view; /coverage global matrix; per-institution Coverage panel with singleton-peer flag; 22 inst × 10 active sources; 6-category status taxonomy: data/inherited/silent/skip/failed/missing; HKSC PAX inherited, DBS PRB inherited confirmed; Goldman sector-fallback 17; 16 Div Banks sub_industry; migration 020; docs/design/coverage-diagnostics.md) |
docs/design/coverage-diagnostics.md |
| 4.5 | Onboarding-column audit — landed 2026-05-21 (4 SILENT SKIP columns: peer_group_id, return_profile_id, weapons_financing, securities_issued; 0 SILENT WRONG LABEL findings; 0 FALSE CLEAR findings; 3 DATA GAP items: fossil_fuel_exposure NULL for 14 insts, bhrrc_pk missing for 13 financials, banktrack_slug missing for 12 financials; scripts/audit-onboarding-columns.js; docs/design/onboarding-column-audit.md with taxonomy + checklist; cross-link added to docs/design/add-institution-design.md) |
docs/design/onboarding-column-audit.md |
| B2 | SBTI source ID rename — landed 2026-05-21 (migration 021; SBTI-DASHBOARD → SBTI-VALIDATED, SBTI-CORPORATE → SBTI-VALIDATED-CORP; 238+282 signal rows + 4 rule rows + 2 watchlist_entry rows updated; old IDs deleted; sbti.js + run.js updated; deferred from cycle 1.6) |
— |
| B3 | Cycle 2B identifier backfill — landed 2026-05-21 (migration 022; bhrrc_pk, bhrrc_slug, banktrack_slug, fossil_fuel_exposure for INST-0009–0022; 13 rows updated; sentinel convention: null=confirmed no profile, absent=not yet checked; UniCredit and Bank of Ireland confirmed no-profile; MUFG uses legacy BankTrack slug; DATA GAP items from 4.5 audit resolved) |
— |
| B1 | BHRRC severity mapping — landed 2026-05-21 (migration 023 backfill; classifySeverity() in bhrrc.js; three-tier taxonomy high/medium/low; calibrated against 115 entries across 8 institutions; expected distribution: high≈12%, medium≈87%, low≈1%; full taxonomy in docs/design/bhrrc-severity-mapping.md in main repo) |
docs/design/bhrrc-severity-mapping.md (main repo) |
| 5 | Watchlist admin surface (status: active → resolved / superseded / disputed) — landed 2026-05-21 (migration 024; watchlist_entry_status_history audit table; /watchlist/:id per-entry detail + status change; /watchlist index bulk action with confirm dialog; institution detail cross-links; state machine documented in watchlist-schema-design.md) |
docs/design/watchlist-schema-design.md |
| 6 | Deduction-rule signal-state labelling — landed 2026-05-21 (migration 025 rule_type TEXT column; E7/S6/G5/G7 backfilled as deduction; institution detail: 'no findings'/'findings' labels replace 'positive'/'not found' for deduction rules; 'ded' chip on rule rows; /coverage: deduction-source column markers + legend; /methodology: rule_type column + §3.1 'Rule types' section; ADR-0010 amendment 2; coverage-diagnostics.md addendum — approach (b) chosen over 7th status) |
ADR-0010 amendment 2 |
| 7 | First full scoring run with completed expanded pilot + /methodology and ops-site update — landed 2026-05-21 (run_id=19; 22 institutions; 154 ok / 64 failed (BankTrack ongoing); /methodology PAX text fixed (dynamic count: 15 tripped); ops-site register.md updated to v1.5 state; v1.5 complete) | — |
v1.5 — small cycles to land opportunistically
Not ordered. Land any of these between numbered cycles when convenient; none block the main sequence.
- ~~SBTI-DASHBOARD → SBTI-VALIDATED rename~~ — Done (cycle B2, 2026-05-21). Migration 021; old source IDs absent from all tables.
- ~~BHRRC severity mapping~~ — Done (cycle B1, 2026-05-21).
classifySeverity()live inbhrrc.js; migration 023 backfilled 115 existing entries. Full taxonomy indocs/design/bhrrc-severity-mapping.md(main repo). Expected distribution: high≈12%, medium≈87%, low≈1%. - Cross-link rule rows to /methodology — anchor links from the institution detail page rule table to the matching entry in the /methodology rule catalogue. Small follow-up;
/methodologyshipped in cycle 4. - CBI coverage verification — CBI scraper returns
not_foundfor all financials on first run. CBI-certified bonds are a subset of all green bonds; result may be correct. Verify manually against the CBI certified bonds database for Barclays/HSBC before treating as a scraper gap. - RAG green band check — confirm the ≥75 green band is calibrated appropriately once the expanded pilot has produced several runs. Currently no institution reaches green.
- ~~TCFD source register tidy~~ — Done (cycle 1.6). Full source catalogue classified: 9 healthy, 1 failed, 2 pending (BOCC, Google News), 8 deferred, 12 candidate. TCFD and PCAF moved to deferred. SBTI-VALIDATED-CSV deleted (superseded by live SBTI-DASHBOARD + SBTI-CORPORATE).
/methodologynow shows five source groups. - ~~UN-G4 / G7 overlap audit~~ — Fully resolved (cycle 9a, 2026-05-21). Migrations 026/027/028 landed: UN-G4 renamed NF-G4;
applicable_sectorschanged from'ALL'to non-financial sector list;rule_typecorrected to'deduction';GOOGLE-NEWS-RSS.feeds_rulesupdated to accurate rule list. G7 unchanged. Rule_type structural audit: 39 rules checked, 3 corrections applied (NF-G4, NF-E4, NF-S4), 0 parked. Cycle 9b (Google News RSS) inherits a corrected foundation — no rule_type backfill issues, no naming ambiguity. - ~~Inline-toggle UX on
/~~ — Done (C-small, 2026-05-21, commit f30fe5d). Native checkbox in Held column; optimistic UI with inline ⚠ error indicator; callsPOST /institutions/:id/holdings-toggle. Detail-page mutation path consolidated onto the same endpoint as a side effect (was a separate form POST). - ~~
finding_type='ngo_campaign_target'CHECK constraint extension~~ — Done (C-bundle, 2026-05-21, migration 037, commit 68997a6). 16 NGO-TARGETS entries backfilled fromcontroversy→ngo_campaign_target.ngo-targets.jsupdated to write the correct type going forward. Dead/institution/:id/toggle-heldroute removed in same commit. - Cycle 14 surface decisions — resolved 2026-05-21 (chat session). Carries into cycle 14 as scoped work, not open questions: (a) Rename "Coverage" → "Research depth" on user-facing surfaces only — templates, page headings, nav labels, /methodology text, the per-institution card label, /coverage page heading (URL stays /coverage), summary strip on /. Internal DB columns, SQL views (source_institution_status), variable names, ADRs, and historical design docs left untouched — they describe the mechanical measure which is still genuinely coverage. (b) Rewrite the pink-box explainer on the per-institution card to reflect neutral-prior methodology. Current text describes coverage-as-confidence and becomes wrong under neutral-prior, not just stale. Bundles with the rename — same piece of work, communicates the methodology shift to the user. Rough shape: "Every institution starts at 50/100 (neutral). Rules with live evidence move the score up or down. Rules without live signals don't affect the score — research depth shows how much evidence has been gathered." Final wording in cycle 14. (c) Research-quality measure rendering location — no change needed. The existing card layout (ESG score left, the renamed Research depth right) already does the structural work. Cycle 4's coverage diagnostics surfaces are the right home; only the label changes. (d) RAG band recalibration stays with cycle 16 (PRB seed completeness + RAG green band calibration) — needs real post-shift score data to calibrate against. Cycle 14 implements methodology, cycle 16 calibrates bands. (e) Cutover communication — silent. Portal has zero users beyond the operator; no in-product notice needed at the methodology flip.
- ~~NGO-TARGETS LEI resolution~~ — Done (cycle 10.5 follow-up, 2026-05-21). 8 of 16 seed entries patched with listed-parent-group LEIs (JPMC, HSBC×2, BNPP, Citi, Deutsche×3). Remaining 8 stay on match_name fallback because their listed parent group is not in the GLEIF candidate list (Banco Santander S.A., NatWest Group plc, UniCredit S.p.A., Lloyds Banking Group plc). Next NGO-TARGETS scraper run will use LEI-first match for the 8 patched entries.
- ~~Ops-site copy button~~ — Done (cycle C1, 2026-05-21).
/backlog/and/glossary/now have a sticky top-right "Copy page" button. Copies rendered article body text vianavigator.clipboard; opt-in viacopy_button: truefront-matter (default off). No other pages affected.
Deferred to later phases
Items that require design work beyond current scope, a separate ADR, or infrastructure not yet justified at current scale.
- ~~IFRS-S2-CORP scraper:~~ PERMANENTLY DROPPED 2026-05-24 (ADR-0027). Dropped alongside CORP-WEBSITE. External-only screen by design; corporate-PDF extraction contradicts independence thesis.
- Client-facing summary surfaces — separate styling pass for exports or a read surface for external stakeholders. Deferred until v1.5 expansion is in use internally.
- Sector-varying pillar weights via blend_weight schema change —
blend_weightcurrently has no sector scoping; non-financials use bank weights as a default. Requires a schema change (blend_weightgains asector_codecolumn). v0.6 work. -
~~SFDR/EU-Taxonomy rule~~ — CLOSED — ASSESSED AND DECLINED (ADR-0018, 2026-05-23). ORIT (INST-0076, sector 55, Renewable Electricity) surfaced the gap at cycle 15d. Decision: do not build. Three grounds: (1) n=1 and structurally so — SFDR product classification applies to funds/FMPs; ORIT is the sole fund-type entity in the universe; the rule does not generalise; (2) self-declared signal in tension with neutral-prior (ADR-0012) — Art.9 label and Taxonomy-% are unaudited self-reporting; (3) mid-transition vocabulary — SFDR 2.0 proposal (Nov 2025) replaces Art.6/8/9 framework; not before 2028, same taxonomy-drift class as ADR-0017. Criterion (a) gap is now CLOSED as assessed-and-declined. ORIT's Art.9 / EU-Taxonomy context recorded as a qualitative watchlist entry (flag layer, not scored; CA100PLUS source pattern). Revisit only if the universe gains a material EU-fund/Art.9 cohort, against the SFDR 2.0 category framework then in force.
-
~~Auto-render bundle~~ — DONE 2026-05-23, ADR-0020. Three scripts in esg-screening/scripts/:
generate-universe-md.js(DB→universe.md, wired as post-run step in run.js),sync-adr-to-ops.js(copies all ADR files to ops/decisions/ + regenerates index table + updates mkdocs.yml nav),sync-claudemd-to-ops.js(copies CLAUDE.md to ops/reference/claudemd.md + adds nav entry). Committed-copy approach (ADR-0020): Pages renders committed markdown, no network dependency at build time. Payoff: universe page regenerated from stale 22-institution (cycle-11-era) to 76-institution (cycle-15) state — 54-institution drift confirmed on first run. ADR index now covers all 24 ADRs (was 0001–0006, 0012, 0013 only). CLAUDE.md now at /reference/claudemd. - ~~Per-(source × institution) status surface~~ — Resolved (cycle 4).
source_institution_statusSQL view (migration 020) classifies every (institution × source) pair from the latest run into one of six statuses: data, inherited, silent, skip, failed, missing. Exposed via /coverage global matrix and per-institution Coverage panel. Seedocs/design/coverage-diagnostics.md. - FETCH_LIMIT pagination — BankTrack and NGO news-driven scrapers may hit the 500-item ceiling for big banks. Pagination or per-institution overrides needed at scale; not urgent for pilot.
Parked queue
Not ordered. Items graduate to the next cycle plan as their predecessor lands or as a parked decision is made.
Reviewed 2026-05-22: Bucket B items (BoCC 2026 first pull; BankTrack site recovery; PAX seed annual refresh; PRB seed refresh cadence; HSBC PRB withdrawal monitor) correctly waiting on external triggers — no change.
Updated 2026-05-24: obs-1 cluster closed; post-review items below.
Updated 2026-05-25: ADR-0031 shipped (run_id=53). Five post-ADR-0031 carry-forward items added below.
Updated 2026-05-26: weapons-floor NULL-pillar display defect (#4b) logged; approach A fix (store 0) applied this session.
Updated 2026-05-26 (tenth session): ADR-0031 post-ship carry-forward queue cleared — RAG recalibration CLOSED NO CHANGE; display tidy FULLY CLOSED (rounding standard shipped, phantom rows CLEAN); fossil/E7 weighting CLOSED NO CHANGE. Weapons-floor item annotated with run_id=54 and commit c3cef4d.
Updated 2026-05-26 (eleventh session): ADR-0031 doc-tidy and page-wording backlog lines closed (both shipped 2026-05-25; lines were open in error). Carry-forward queue fully clear.
Updated 2026-05-26 (twelfth session): Detail-page display tidy logged and immediately closed (4b53dae — dark-rule suppression + Major Shareholders panel removal).
Updated 2026-05-26 (thirteenth session): Watchlist → "Additional Findings" presentation tidy logged and immediately closed (fb49123).
Updated 2026-05-26 (fourteenth session): Companies rename, Held pill, explainer, and filter-pill fix — all logged and immediately closed (876e6a4). Separate filter-pill defect line added (confirmed display-logic only; fixed same session).
Updated 2026-05-26 (fifteenth session): Global Additional Findings list + per-finding detail dropped from surface (9a7d3e4). Logged and immediately closed.
-
~~Companies rename + Holdings fold + explainer + filter-pill fix (fourteenth session, 2026-05-26)~~ — DONE 2026-05-26 (screening
876e6a4). Four display-only changes shipped together. (1) Holdings page folded into a "Held" filter pill (count 59) on the Companies page;/holdingsredirects 302 →/?held=1(no 404; hide-not-delete — route kept, template still in repo); Holdings nav item removed. (2) Institutions → Companies: all user-facing text across every view, nav, heading, table column, and /methodology body prose; route/institution/:id, DB tableinstitution, all columns,institution_typefield, INST- IDs, handler and variable names untouched — cycle-14 Coverage→Research-depth text-only pattern. Note: universe includes fund/trust entities (ORIT) but Rob chose "Companies" for readability. (3) Stat strip removed (all seven figures); explainer block made full-width below filter bar using canonical Holdings-page wording adjusted for "company/companies". (4) FLAGS (Fossil fuels, Weapons) and Watchlist filter pills wired as live<a>links; route predicates added for fossil/weapons/watchlist/held — previously all four were non-interactive<span>elements with CSSpointer-events:noneand no route predicate (see separate filter-pill defect line). Score hash byte-identical to run_id=54 (stage1 n=79 sum=5864.178; pillar n=237 sum=6828.071). PM2 reloaded, health 200. -
~~FLAGS (Fossil/Weapons) + Watchlist filter pills non-functional on click~~ — FIXED 2026-05-26 (screening
876e6a4). Root cause: display-logic bug, not data gap. All three pills were<span class="pill disabled">/<span class="pill wl-pill">with CSSpointer-events:none— never interactive. No filter predicate existed in the route handler for fossil, weapons, or watchlist dimensions. Data was correct: counts (fossilTripped=6,weaponsTripped=19,withWatchlist=47) matched expectations; theflagFossil,flagWeapons, andwl_countfields were already present on every enriched row. Fix: converted pills to<a>tags withfilterUrl()hrefs; addedfossil,weapons,watchlist, andheldfilter predicates to route handler; removedpointer-events:noneCSS overrides. Score/Red/Amber/Green/Sector/Research-depth pills were working correctly throughout — same<a>+filterUrlpattern established the fix template. Filters confirmed:/?fossil=tripped→ 6 companies,/?weapons=tripped→ 19,/?watchlist=1→ 47,/?held=1→ 59. Display-only; no DB, schema, or methodology touch. -
~~RAG band recalibration against run_id=53~~ — CLOSED — ASSESSED, NO CHANGE (2026-05-26, run_id=54). Bands stay Green≥70 / Red<40. Distribution shift under ADR-0031 (Green 46/Amber 25/Red 8) is a design property of null-resolution-to-100, not a calibration error; current thresholds land in natural distribution gaps. ≥78 tightening considered (would demote 8 Green→Amber at the 78.1→82.0 gap) and declined — cosmetic, no methodology benefit for an internal screen. Consistent with the prior RAG no-change assessment (run_id=48, ADR-0026). rag.js unchanged.
-
~~ADR-0031 doc-tidy~~ — DONE (2026-05-25, screening
ae8b7e5; ops synced). Agency model + four tag rulings (mandatory→self, assurance→self, Forest500→neutral, Bloomberg GEI/Stonewall S4.5→external) + monotonicity ruling (self k=22.5 ≤ M1 deduction k=45; mandatory→self via migration 066 dissolved the violation; OMIT-vs-scoreValue=0 resolved in favour of reclassification, not a neutral-semantics change) folded into ADR-0031 under §Consequences. Final tag distribution from live rule table: 70 self / 19 external / 1 neutral. Interim#esg-screeningthread amendment note (2026-05-25 08:48 BST) superseded by the ADR. -
~~Page wording: ADR-0031 calc-method + weapons→S=0~~ — DONE (2026-05-25, screening
57e15ca; ops synced). New plain-language explainer copy across five surfaces: institutions list, /holdings, company-detail (pink-box), /methodology, and the ops scoring page. Describes baseline-100, deductions, agency-weighted positives (independent > self-reported), null-pillar handling, and the weapons→S=0 floor. /holdings gained a new explainer block (none existed prior). Display-only; Stage 1 score hash byte-identical. -
~~Display tidy (obs-1 carry-forward)~~ — DONE — FULLY CLOSED (2026-05-26, run_id=54). (a) Score-rounding standard: shipped
8f7bb24— sharedformatScore()helper, round-half-up to 1dp for display; band/sort/rank logic on unrounded value throughout. (b) Phantom row verification: VERIFIED CLEAN — zero phantom rows universe-wide for all 11 retired rules (G1/G2/S2/S3/UN-G1/G4/UN-G2/UN-G3/NF-E1/NF-E2/NF-E7-STAP); 3 de-scoped sources (CORP-WEBSITE/EP-MEMBERS/TCFD-SUPPORTERS) correctly excluded bysource_status IN ('healthy','failed','pending')filter. 5 representative institutions checked across FI/asset-manager/NF/thin-coverage. Item fully closed. -
~~Fossil/E7 weighting review~~ — CLOSED — ASSESSED, NO CHANGE (2026-05-26, run_id=54). E7 stays as-is. Drag measured: 0.72–2.8 composite pts (1 violation), 5.3–8.0 pts (2 violations); 16 clean / 11 one-violation / 11 two-violation / 0 three-plus. Raising E7 rule_weight remains a non-monotone fix under ADR-0031 — NZBA/TPI positives anchor the non-OMIT denominator, capping E7's composite effect (~8 pts) regardless of k; raising it risks band-crossing at the margin with no 3+ violation cohort to justify it. The obs-1 structural conclusion (ADR-0026) holds under the symmetric baseline. No separate fossil transform warranted; fossil remains scored via E7 (a transform would double-count) and the analyst-set fossil flag stays a flag, not a floor.
-
~~Weapons-floor S pillar stored as NULL, not 0 (engine.js:61) — confirmed 2026-05-25 via the #4a rounding pass. NULL collapses the weapons-floored-to-zero state into the no-evidence state, so surfaces variously show 100 / — / '100 · No adverse findings' for a floored S. Composite is correct (floor forces S=0 in the blend); defect is stored-pillar/display only. Fix: approach A (store 0) preferred, audit-gated; approach B (floor_applied flag, display-only) is the fallback. See the 2026-05-25 handoff. DONE 2026-05-26 (run_id=54, screening
c3cef4d) — approach A (audit clean; engine.js:61 NULL→0; composites byte-identical; 19 weapons-tripped S pillars NULL→0; all three surfaces corrected).~~ -
~~Global Additional Findings list + per-finding detail dropped from surface (fifteenth session, 2026-05-26)~~ — DONE 2026-05-26 (screening
9a7d3e4). Display-only; score hash byte-identical to run_id=54. Partly reverses fb49123 global-page work — findings are now a per-company-only view by decision. Six changes shipped together: (1) /watchlist redirects 302 → /methodology#additional-findings; (2) /watchlist/:id redirects 302 → parent company's detail page (DB lookup, fallback to /methodology#additional-findings); (3) "Additional Findings" nav item removed; (4) "detail →" links removed from per-company findings rows — "source →" kept; (5) per-company findings panel scroll-capped (~400px max-height); (6) Companies-page explainer moved inside .table-wrap so it shares the same container width as filter bar and table; /methodology "Additional Findings" subsection trimmed (global-page reference removed). Route handlers, status-change POSTs (POST /watchlist/:id/status, POST /watchlist/bulk-status), state machine, and watchlist_entry_status_history all retained functional but unsurfaced (hide-not-delete — same rationale as fb49123). No 404 for bookmarks. -
~~Watchlist → "Additional Findings" presentation tidy (session review, 2026-05-26)~~ — DONE 2026-05-26 (screening
fb49123). User-facing rename "Watchlist" → "Additional Findings" across nav, institution detail panel, /watchlist, /watchlist/:id (text only — URL /watchlist, DB tables watchlist_entry + watchlist_entry_status_history, signal_source values, and all internal identifiers untouched; cycle-14 Coverage→Research-depth pattern). Detail panel flattened to a single findings list; status filter ("Show resolved & superseded") and all status vocabulary (active/resolved/superseded/disputed) HIDDEN from view. /watchlist/:id stripped to a read-only finding-detail view: status chip, STATUS MANAGEMENT form, and STATUS HISTORY section hidden; finding detail + dates + summary + source retained. SUMMARY escaping bug fixed. /methodology gained an "Additional Findings" subsection (describes existing design — not a methodology change). IMPORTANT — code retained, not deleted: the state machine, watchlist_entry_status_history audit table, status values, and status-change route handler all remain in the codebase, functional but unsurfaced. Rationale for hide-not-delete: filters/status management can be reintroduced if the findings list grows unmanageable at scale. Display-only; Stage 1 score hash byte-identical to run_id=54 (stage1 n=79 sum=5864.178; pillar n=237 sum=6828.071). -
~~Detail-page display tidy (session review, 2026-05-26)~~ — DONE 2026-05-26 (screening
4b53dae). Two display-only fixes to the institution detail page, both surfaced in session screenshot review (neither was a tracked backlog line). (1) Dark-rule suppression: rules with no live scraper (coverage_pct=0, no signal rows ever written) now hidden from the default/ALL rule-evaluation table; revealed by the "Uncovered only" filter pill. Implemented as a single CSS selector widening (data-cov="0"already identified dark rules — binary in this codebase). Counts deliberately unchanged: header "N APPLICABLE RULES", per-pillar research-depth fractions, and all filter-pill counts still count the full applicable set; default table renders fewer rows than the header by design. Verified NatWest (G3/UN-S1/G5/E8 hidden in default, present under Uncovered only; 8+1/4+1/3+2 reconcile to 9/5/5) and Man Group spot-check. (2) Major Shareholders panel removed: static "ownership data not yet integrated" placeholder deleted from the sidebar (no route data-prep existed); Score history panel and rest of sidebar intact. Display-only; Stage 1 score hash byte-identical to run_id=54 (stage1 n=79 sum=5864.178; pillar n=237 sum=6828.071). PM2 reloaded, health 200. No /methodology or /coverage change. -
obs-1 — Scraper-sufficiency review — DONE 2026-05-24. Full-catalogue sufficiency review complete; design note
docs/design/obs-1-scraper-sufficiency-review.md. All 20 unbuilt sources assessed on merits (positive, deduction, flag). Verdict: the live scale is structurally deduction-only — six social/governance positives (S2, S3, UN-G3, G1, UN-G2, G2) are reachable only via CORP-WEBSITE; CDP dropped as non-viable (licence-gated) so G4/UN-G1 join that dead set; the only buildable positives (TNFD->E5, TPI->E1/UN-E1, IFRS-S2 mandate-proxy->E4.1) light environmental/disclosure rules for a minority of the universe and none reaches a social/board-governance rule. Decisions taken (all 2026-05-24): see four sub-items below. -
~~This-cycle build (graduated from obs-1):~~ DONE 2026-05-24. (1) TNFD-ADOPTERS → E5 (migration 053); (2) IFRS-S2-MANDATE → E4.4+UN-E1.5 (migration 054/056); (3) TPI-SECTOR → E1/UN-E1 (migration 057, ADR-0025). Scoring re-run: run_id=43, 79 institutions. Fed baseline-anchor decision (see below).
-
~~Baseline-anchor decision:~~ DONE 2026-05-24. Decision: 100-baseline (deduct-only; clean=100). Formula:
max(0, rawScore×45−125). k=45, −125 coupling load-bearing. ADR-0026 (supersedes ADR-0012, ADR-0023). RAG bands confirmed: Green≥70 / Amber 40–69 / Red<40 (recorded in ADR-0026; rag.js updated). -
~~CORP-WEBSITE — next major workstream:~~ PERMANENTLY DROPPED 2026-05-24 (ADR-0027). Decision: external-only screen by design; corporate-PDF extraction contradicts the independence thesis (external assessors only). IFRS-S2-CORP dropped alongside. Both sources removed from register permanently. The six dead positive rules (S2, S3, G1, UN-G2, G2, UN-G1) remain dark — acceptable given internal-tool scope. Record: ADR-0027.
-
~~RAG band recalibration — unblocked (baseline decided).~~ CLOSED — ASSESSED, NO CHANGE (2026-05-24, run_id=48). Bands confirmed against first clean full-79 scheduled-path run: Green=23 / Amber=30 / Red=26. Green threshold (≥70) deliberately NOT raised — the 70–99 cluster thinness (3 institutions in 70–99 vs 20 at exactly 100) is a structural consequence of the deduction-led catalogue, not a miscalibration. Raising the threshold would reclassify ~3 institutions to Amber with no methodology benefit. Record: ADR-0026.
-
~~Pass B — retire 9 fully-dead rules~~ — DONE 2026-05-24 (migration 062). Retired: G1, G2, S2, S3, UN-G2, UN-G3, NF-E1, NF-E2, NF-E7-STAP. Score-neutral;
rule_status='retired'mechanism preserves rows in DB and lists them on /methodology with a "retired" chip (display option d). Grey-zone follow-up — UN-G1 and G4: DONE 2026-05-24 (migration 063, ADR-0030). Both retired as CDP-non-viable: CDP-CORPORATE/CDP-PUBLIC are deferred-but-explicitly-assessed-non-viable (licence-gated, obs-1) — treated as effectively dropped under the ADR-0030 principle. Score-neutral; G-pillar confidence rises by design (two conf=0 rules removed from applicable set); liveRuleCount 22→20. Remaining grey-zone (3 rules): E2 (BOCC-REPORT pending — live stub, awaiting annual data drop), NF-E3 (EP100 candidate), NF-E5-IND (TPI-SECTOR pending) — each has a live/pending/candidate source, not a non-viable deferred one. -
obs-1 per-source weighting review — CLOSED MIXED 2026-05-24. Full analytical pass completed. Decision: rule_weights unchanged. DISPOSITIVE-1 (raise E7 rule_weight) rejected on the data — non-monotone fix; E7 structurally diluted at ~10.4% of composite max regardless of k. DISPOSITIVE-2 (pillar-flooring) not chosen. MIXED: controversy-bearing names read mixed at composite level; honest at per-pillar detail. Tradeoff recorded: headline/RAG can read favourably for non-drill-in viewers; acceptable for internal tool, revisit if client-facing surface added. ADR-0026 updated with full dilution arithmetic.
-
~~BHRRC → NF-S4 wiring gap~~ — DONE 2026-05-24 (ADR-0029, migration 059). Severity-modelled fix: W = (high×3 + medium×1) over 24m window; three stacking BHRRC sub-rules (sub_rule_id=1/4/5); LOW severity excluded. 8 band changes: Green→Amber ×4 (Alphabet, Caterpillar, Microsoft, VW); Amber→Red ×4 (Amazon, Booking, Inditex, Unilever); IKEA Red→Red deeper (30→21); NVIDIA/Thermo Fisher decrease, hold band. Strictly monotone — no institution improves. ADR-0029 records full model, non-monotone-binary rejection (IKEA 30→41 worked example), and CHRB composition rationale.
-
~~NF-S4.2 (GOOGLE-NEWS-RSS) dead-wired~~ — CLOSED — ASSESSED AND DECLINED (2026-05-24, run_id=46 probe). The dead-wiring (
google-news-rss.js:215hardcodesruleIdtoG7/NF-G4) is real but not the operative blocker. A throwaway social-keyword probe (run_id=46, scratch scriptscripts/scratch/nf-s4-2-probe.js, not wired to pipeline) found that fixing it produces zero score movement: (1) the scraper's institution-name-in-title filter requires legal names, which never appear verbatim in news headlines, so 0 of 41 NF institutions trip the filter — a pre-existing scraper-wide recall limitation, not NF-S4.2-specific; (2) under a hypothetical short-name/alias approach only 3 institutions hit (Amazon, VW, Inditex), and all 3 are already at NF-S4 floor=0 from BHRRC deductions (ADR-0029), so an extra deduction moves nothing; (3) those 3 show ~75–100% event overlap with existing BHRRC coverage (same incidents), so NF-S4.2 would mostly re-detect BHRRC signal rather than add new signal. Building would require the wiring fix AND a name-matching redesign for low/zero near-term value. Revisit only if name-matching aliases are added for other reasons, or the NF universe shifts. If ever built, the indicated double-count policy is complementary-only (NF-S4.2 fires only when BHRRCsub_rule_id=1is false). -
~~BHRRC→NF-S4 projection-vs-actual W reconciliation (noted 2026-05-24, run_id=46, ADR-0029).~~ DONE 2026-05-24 (commit cc9fde7,
scripts/bhrrc-w-report.js). Root cause: projection used an ad-hoc binary estimate (not committed), not a proper severity-weighted count; underlying cause is thatwatchlist_entryis a stale partial snapshot of the BHRRC API — populated byscrapeWatchlist()runs, not guaranteed complete. For NVIDIA: watchlist had W=2 (2 medium entries), binary estimate had W=1, live API gave W=4 (4 medium). Systematic across 8 of 12 BHRRC-active NF institutions (diverge=8, match=4). Fix:scripts/bhrrc-w-report.jsreads W fromsignal.raw_value(the scorer's own output) — projection and actual are now identical by construction. No scorer change, no score change, no DB writes. Usage:node scripts/bhrrc-w-report.js [--run <run_id>] [--baseline <run_id>]. -
Watchlist→S/G scored deductions — surface fix (c) DONE 2026-05-24; wire-in (b) not chosen. Surface fix: pillar cards with score=null now show "N active watchlist findings — unscored" when active watchlist entries exist for the institution, instead of asserting unqualified "No adverse findings — 100". Display-only; Stage 1 score hash unchanged. Verified against IKEA (8 active), Microsoft (25 active), Synopsys (20 active). Wire-in (b) — watchlist→scored deductions — not chosen; BHRRC→NF-S4 gap (above) is the correctness-first version of this question. Option (a) leave-as-is: not needed now surface fix (c) lands.
-
~~G5/G7 points=+5 oddity~~ — Graduated to small cycle S8 (2026-05-23). See cycle plan above.
-
~~Lever B — NZBA/PRB applicability for asset managers:~~ DONE 2026-05-24 (migration 058, ADR-0028). Denominator-only exclusion for GICS 40203010 (Asset Management & Custody Banks);
denominator_exclude_sub_industriescolumn onruletable. Score-neutral. Verified run_id=43. -
~~Lever C — on-page explainer rewrite:~~ DONE 2026-05-24. /methodology prose rewritten to reflect 100-baseline deduction-led model; pillar-card caption updated for null-score pillars with active watchlist entries. Display-only; Stage 1 score hash unchanged.
-
Sources declined / dropped in obs-1 (record so they're not re-raised): RE100/EP100/EP-MEMBERS — declined on merit (membership-boolean false-positive risk). CDP-CORPORATE/CDP-PUBLIC — dropped, not viable at this stage (licence-gated; reconsider if a free tier returns or a paid dependency is accepted; A-List-as-flag possible). Non-viability of CDP was the basis for retiring UN-G1/G4 as permanently dark — see ADR-0030. RSPO/FAIRR/WALK-FREE — deferred (thin universe / WALK-FREE is country-level not institution-level). FCA/ECB/SEC/PCAF/PROFUNDO — opportunistic-only (corroborate existing rules; never a dedicated cycle). TCFD-SUPPORTERS (deprecated) and ETHICAL-CONSUMER (watchlist-only) confirmed out.
-
~~Migration runner BEGIN/COMMIT defence~~ — Graduated to small cycle S1 (2026-05-22). See cycle plan above.
-
~~Backup failure notification~~ — Graduated to small cycle S2 (2026-05-22). See cycle plan above.
-
Source-instrument continuity review — standing quarterly cadence (formalised 2026-05-22). For each live source on the scraper register, read the source's About / Methodology page and confirm the scaffold note + register entry still describes it correctly. Catches methodology drift the URL-health check misses. Confirmed instances (five source-taxonomy): (1) CHRB scaffold note said "biennial-ish per sector" — partially wrong at C11.5 compile time; Social Benchmark launch could have caused a misread had it not been caught; (2) FOREST500 vintage/Floresta 250 instrument distinction (C11.5b — Floresta 250 is a separate Brazilian-companies instrument, not the same dataset); (3) InfluenceMap/CA100+ overlap — InfluenceMap lobbying analysis is an INPUT to CA100+ benchmark scoring (C12 scoping); (4) FOREST500 label-vs-feed drift (C14 D3) — E7's catalogue label ("Project finance controversies (24m)") didn't describe FOREST500-finance, which feeds it; fixed via display-layer relabel; scoring split reversed (see D3); (5) SBTi "Commitment Removed" taxonomy (C15c, ADR-0017, 2026-05-22) — "Commitment Removed" status on the Target Dashboard is an administrative 24-month timeout, NOT a commitment withdrawal; 9 false
withdrawn_commitmentwatchlist entries resolved across 15a–15c cohort. SBTi taxonomy actively changing: Jan 2023 (Commitment Compliance Policy), Mar 2024 (bulk removal wave ~239–284 companies), Dec 2025 (new categories), Apr 2026 (ACA/standard). At each quarterly review: confirm Excel column names (near_term_status,net_zero_status), confirm status vocabulary, and re-validate the 5-state mapping in ADR-0017. Plus (reporting hygiene, not source-taxonomy): score_value vs score_coverage_weighted (C16) — handoffs had been reporting score_value (range 0–27), not score_coverage_weighted (range 0–39); canonical score is score_coverage_weighted (ADR-0004 amendment). Frequency: quarterly. Output: register updates where needed, optional ADR if a source shift implies a methodology decision. -
~~Universe reference page auto-render~~ — Consolidated into auto-render bundle (deferred, Phase 1.5) 2026-05-22. See Deferred section above.
-
~~Holdings-vs-subsidiary framework memberships~~ — Resolved (cycle 3). Migration 019 adds
parent_lei+operating_arm_leicolumns. INST-0009 HKSC inherits PAX TRIPPED via HSBC Holdings (parent_lei); INST-0019 DBS Group inherits PRB membership via DBS Bank Ltd (operating_arm_lei). Both cases confirmed in run 18. ADR-0011 records the per-scraper inheritance pattern. Seedocs/design/holdings-vs-subsidiary-framework-memberships.md. -
~~Operating-arm LEI population for cycle 2B holdings — seven candidates~~ — Done — cycle 15a (2026-05-22,
d6f2636, migration 045). AIB→Allied Irish Banks p.l.c. (3U8WV1YX2VMUHH7Z1Q21); Bank of Ireland→Governor & Company of the Bank of Ireland (Q2GQA2KF6XJ24W42G291, Dublin head office — Paris branch ruled out); ING→ING Bank N.V. (3TK20IVIUJ8J3ZU0QE75); JPMorgan Chase→JPMorgan Chase Bank N.A. (7H6GLXDRUGQFU57RNE97); Citigroup→Citibank N.A. (E57ODZWZ7FF32TWEFA76); Goldman Sachs→Goldman Sachs Bank USA (KD3XUN7C6T14HNAYLU02, bank entity preferred over broker-dealer FOR8UP27PHTHYVLBNG30); MUFG→MUFG Bank Ltd. Tokyo (C3GTMMZIHMY46P4OIX74, consistent with existing BankTrack slug bank_of_tokyo_mitsubishi_ufj). All seven GLEIF ACTIVE/ISSUED verified 2026-05-22. -
Goldman Sachs singleton Capital Markets peer cohort — Goldman Sachs Group is the sole Capital Markets (40203020) institution in the pilot. Sector fallback (40 — Financials) applies for peer ranking. Morgan Stanley (Capital Markets) is the natural addition to enable industry-group-level ranking. Add as cycle 4 candidate or standalone when ready. Parked per cycle 3 GICS rationale note.
-
Ambiguous-source-level inheritance — BHRRC slug reuse, news-based sources — For sources whose tracking level is genuinely ambiguous (BHRRC company-profile slug reuse across group/subsidiary, news-based sources covering corporate groups loosely), no inheritance is currently applied. Current behaviour preserves the honest "this is what the source said" read. Revisit if cycles 3+ produce concrete artefacts where ambiguous inheritance produces a wrong signal.
-
PRB seed refresh cadence — currently manual (
node scripts/refresh-prb-seed.js). Cadence is ~quarterly persignal_source.cadence_days=91. The source is now server-rendered HTML (not AJAX); re-verify this on first quarterly refresh. Refresh log atdata/seed/prb-signatories.refresh.log.json. -
HSBC PRB withdrawal — no public coverage found — Cycle 2A.1 confirmed Fix path B: HSBC Holdings plc (founding signatory Sep 2019) is genuinely absent from the UNEP FI PRB signatory list. Watchlist entry written (source_finding_id=prb-absence-INST-0004, withdrawn_commitment, medium). HSBC corporate site lists UNEP Finance Initiative as a membership but is silent on PRB specifically. No public news coverage of the withdrawal found — this is unusual for a founding-signatory exit and may indicate a quiet administrative exit rather than a policy-driven withdrawal. Monitor for: (a) any HSBC press release or ESG report mentioning PRB withdrawal; (b) UNEP FI announcements; (c) next quarterly seed refresh to confirm continued absence.
-
PRB LEI gaps — 203 of 356 seed entries have lei=null (GLEIF couldn't resolve unambiguously). These fall back to match_name in the scraper. As new financial institutions are added, check their LEIs against the seed — if their LEI isn't present (e.g. a non-UK bank with lei=null in the seed), LEI-first match will miss even if they're a PRB signatory; they'll need a
prb_match_nameinscraper_config_json. The refresh log'sambiguous_unresolvedsection contains 97 cases that could be manually resolved by a human auditor if needed. -
Source catalogue periodic re-review — at each v1.5 cycle close, check whether any candidate-bucket sources have been promoted to a real cycle or deferred-bucket sources should move to a numbered cycle. The catalogue classified in cycle 1.6 is the baseline; a source's classification should be updated when the project's scope or scraper roadmap changes.
-
Handoff verification discipline + Pages rebuild miss — partially resolved (cycle 3.2); modes 1 and 2 actions remain — post-cycle check that handoffs' claimed commits exist on
origin/mainwith the claimed diffs, AND that the live site reflects them. Pattern has three confirmed instances across two distinct categories:
Pipeline-failure modes (live site stale):
1. Cycle 8 — Pages trigger miss (commit a0ff6e9 on origin, Pages didn't rebuild)
2. Cycle 2.6C — missing push (commits eda92c6 and 24ad513 were local-only, never reached origin)
Verification-failure mode (live site correct, but poll didn't detect it):
3. Cycle 3.1 — post-push curl-grep poll loop sat in a waiting state for 20+ min despite ops.esg-screen.org/backlog/ correctly rendering the two new parked items within the expected ~3 min window
Same surface symptom in all three (handoff claims success or hangs) but qualitatively different failure points: modes 1 and 2 are pipeline failures with a stale site; mode 3 is a verification failure with a correct site.
Action items — modes 1 and 2 (still open):
- (a) SHA-comparison check between origin/main HEAD and the SHA visible in the live page footer or a /version endpoint
- (b) Mandatory post-cycle push-verify step at session close: git log origin/main -1 --oneline compared to expected SHA before declaring cycle closure
Action items — mode 3 (fully resolved 2026-05-21):
- (c) curl-grep live-site poll replaced with scripts/verify-pages-deploy.js — Cloudflare Pages deployments API check keyed on commit SHA, returning when deployment status === 'success'. Run from the esg-screen-ops repo directory after pushing. Requires /etc/esg/cf-pages.json. Provisioned in cycle 3.2; file-permissions gap (root:root 600) discovered and fixed 2026-05-21 — now azureuser:azureuser 600. Script confirmed clean (exit 0) against ea5736e.
- (d) Hard 5-minute timeout wrapping the API check; explicit failure message on timeout: Pages deployment for <sha> not confirmed within 5 min — check dashboard manually
- (e) Async-via-Slack decoupling (close session on push-verify, post Pages confirmation to #esg-screening) recorded as v2 shape if polling remains fragile after cycle 3.2. Recurrence confirmed across D1/D2/D3 sessions (C14): verify-pages-deploy.js checks esg-screen-ops Pages deployments — must be (a) run from the esg-screen-ops directory and (b) passed the esg-screen-ops commit SHA, not the esg-screening SHA. Most cycles commit to esg-screening first, making the SHA mismatch the default failure mode: passing the screening SHA produces a spurious "looking for <sha>" miss until re-run with the ops SHA. Now motivates async decoupling more concretely.
-
BoCC 2026 first pull — when the 2026 Banking on Climate Chaos report drops (expected March/April 2026, not yet published as of 2026-05-20), the BoCC stub scraper will automatically populate E2.3 ("not in current Banking on Climate Chaos top financiers"). No code change expected; data appears on the next scheduled run after publication. Monitor
BOCC-REPORTsource register status for the transition frompendingtohealthy. -
BankTrack site recovery check — Partial recovery as of 2026-05-21 (cycle 8 check).
banktrack.orgroot returns HTTP 200 (homepage accessible); RSS feed endpoints (/bankrss/*) still return HTTP 404. Run 19: 64/64 signals failed, allbanktrack_slug-equipped financials.source_status=failed. The site root is back but scraper targets are not. Will self-heal on next Sunday run if RSS endpoints recover. No code change needed. Note: INST-0010–0021 slug status post-cycle-B3: only INST-0011 (Bank of Ireland) still hasbanktrack_slug=null(confirmed no profile); all other cycle 2B financials have slugs set. INST-0005–0008 (non-financials) and INST-0022 (Volkswagen) havebanktrack_slug=nullas expected — BankTrack does not cover non-financial institutions. Next check: cycle 9 session-start. -
PAX seed annual refresh —
data/seed/pax-hall-of-shame.jsoncompiled from the 2025 "At Great Cost" report (Tables 5+6). Update annually when the new report is published (typically Feb–Mar). Procedure: download the new PDF, check Tables 5 (investors) and 6 (creditors) for changes, update the institution list and bumpretrieved_at. The seed coverage note (_coverage_note) flags that the current file is not the full ~280-institution list — expand on the next annual update if universe has grown significantly. No code change needed; seed file update triggers new TRIPPED/CLEAR evaluation on next run. -
Binary-flag surface logic audit — the PAX false-clear (cycle 2B) showed that a scraper returning HTTP 200 with empty content writes
'clear'toinstitution.flag_weapons, which renders honestly only if the rendering logic distinguishes NULL from 'clear'. The rendering inindex.jsandinstitution.jsalready does this correctly (NULL → no_data, 'clear' → CLEAR). Cycle 2.5 confirmed: INST-0022 (VW AG, sector 25, non-financial) hasflag_weapons=NULLafter run 17 —evaluateWeaponsFlagscorrectly skips non-financials. A related defect (PAX watchlistsignal_source:'PAX'FK mismatch) was also fixed in cycle 2.5 — this was separate from the binary-flag column write but caused all watchlist entries to fail since cycle 2.6A. The standing audit concern remains: audit any future binary-flag source added outside the signal table to confirm it writes NULL on failure, not 'clear'. -
Add-institution form: edit path for post-create source-identifier overrides — cycle 2B workaround was direct DB awareness (overrides set at script creation time via prb_match_name, nzba_match_name fields). At scale, an institution added without the correct match-name override will silently produce boolean=0 / conf=0.5 for seed-backed scrapers. The form needs a post-create edit path that exposes scraper_config_json fields (prb_match_name, nzba_match_name, sbti_match_name, bhrrc_pk, banktrack_slug, pax_match_name) as editable form fields. Without this, analysts must use direct DB updates which is fragile and not documented in the UI. Note:
pax_match_nameis now used by the seed-backed scraper (cycle 2.6A) — same override pattern as NZBA/PRB. -
~~Silent wrong GICS labels — onboarding column audit (cycle 3 finding)~~ — Resolved (cycle 4.5). Full audit landed 2026-05-21:
scripts/audit-onboarding-columns.jscovers all candidate columns;docs/design/onboarding-column-audit.mddocuments the three-category taxonomy (SILENT SKIP, FALSE CLEAR, SILENT WRONG LABEL) and the onboarding checklist. Current universe: 0 SILENT WRONG LABEL, 0 FALSE CLEAR, 4 SILENT SKIP (orphaned legacy fields), 3 DATA GAP (fossil_fuel_exposure + bhrrc_pk + banktrack_slug missing for INST-0009–0022). Checklist added todocs/design/add-institution-design.mdcross-link. -
Trim project instructions and CLAUDE.md content duplication — /backlog reference added 2026-05-19. Trimmed primer produced 2026-05-23 (chat-side, pasted into project knowledge). Remaining trim spans two surfaces only: project instructions +
.claude/CLAUDE.md(the /operating-model reference in prior versions of this item was a dead link — that page does not exist). Parked until a session has bandwidth for the documentation tidy. -
~~NGO-TARGETS seed comprehensive pass~~ — Done — cycle 15a (2026-05-22,
d6f2636). 16 → 19 entries: 3 added for newly-in-universe institutions (Nordea E7.3 ShareAction backsliding; BlackRock E7.3+S6.2 ShareAction Asset Manager Survey 2025 bottom band). LEI-first match via config.lei — no ngo_targets_match_name migration needed. run_id=35 confirms all signals conf=1.0. BOCC exclusion preserved (E2 handles BoCC). FOREST500 E7.4 / NGO-TARGETS E7.3 overlap for BlackRock is correct — distinct signals (deforestation-financing list vs NGO-campaign-target), not a double-count (recorded in seed metadata). Full ShareAction broad-roster pass (leaders + asset-manager ranking table): CLOSED — ASSESSED AND DECLINED. ADR-0019, 2026-05-23. Three grounds: (1) deduction-source no-op skew — NGO-TARGETS records campaign targets/backsliders only; uncaptured roster skews to leaders (no-deduction outcome, scoring-identical to absence) and lower-confidence matches; (2) no-defer principle — Chrome session not booked; honest action is to close, not carry; (3) open provenance — RAN BoBC/BOCC labelling question unresolved; seed better treated as complete at 15a state until that is addressed at the quarterly review. Seed is complete for Phase 1. -
NGO-TARGETS time-window expansion — current seed is "currently live on NGO sites" (per Rob's Q2=C decision). When refreshing seed annually, consider whether to broaden to 24-month window matching E7/S6 controversies lookback. Decision parked until first quarterly refresh.
-
Three data-model open items — raw payload retention, source-vs-signal_source rename, run retention. ~~score_value/score_coverage_weighted column divergence~~ resolved 2026-05-22 (ADR-0007, migration 043):
score_raw_v04renamed toscore_raw_intermediateonscore_stage1_esgandscore_pillar; "Raw v0.4" hero line dropped from institution detail; all pipeline readers updated.score_raw_intermediateis the internal diagnostic handle;score_coverage_weightedis the canonical surfaced score. (Watchlist storage shape resolved in cycle 5 migration 013 and watchlist-schema-design.md.) -
~~Ranking.js peer-ladder defect~~ — Resolved 2026-05-22.
ranking.jsre-pointed toscore_coverage_weightedfor all peer-group ladder, universe ranking, and distribution computations. Null CW handled defensively. Position movements confirmed correct and within the divergence prediction (null-pillar financials up, S-data financials relatively down, non-financials stable). ADR-0007 amended to record the fix. Scores unchanged; only ranks moved. -
~~Weekly automated refresh:~~ DONE 2026-05-24.
ENABLE_SCHEDULER=1added toecosystem.config.cjsenv;startScheduler()wired inserver.js. PM2 saved. Log confirms[scheduler] registered: scrape=0 2 * * 0 source-status=0 3 * * * UTC. Sunday 02:00 UTC cron live. -
Sub-rule expansion toggle on
/methodologyrule catalogue — design decision is "yes eventually"; build decision deferred.[+ show sub-rules]per rule, surfacing per-sub-rule point contributions on click. -
Watchlist dedup key revisit — dedup key is
(signal_source, source_finding_id)withoutinst_id. BHRRC articles covering multiple companies map to a single row (first-company-wins). Pilot run 4 showed 7 cross-company updates. Cycle 5 delivered the admin surface; if cross-company entries need operator-side reconciliation, the/watchlist/:iddetail page and bulk action are available as tools. → DEFER to Phase 1.5 / post-close (2026-05-22). Won't fix for Phase 1; revisit in universe-expansion era if cross-company mis-mapping surfaces in the ~50-institution universe. Schema change required to allow multiple institutions per source_finding_id.
Open methodology questions
Distinct from parked work — these need a decision before they can become work.
- ~~Aggregate-rule coverage semantics.~~ Resolved (cycle 14 D1, 2026-05-22,
5d041e6). G3 excluded from Research-depth denominator viais_aggregateflag on theruletable (migration 040). Denominator-only change; scores unchanged. Any future aggregate rule picks up the exclusion automatically. - ~~BankTrack failed-signal scoring semantics.~~ Resolved (cycle 14 D2, 2026-05-22,
ad8a76e).extraction_ok=0excluded from scoring for both rule types (omission, not score-at-50). "Preserve prior run's values" option explicitly rejected — hidden state dependency that conflicts with weekly-refresh reconstructability. - ~~×20 scale factor — weighting bug?~~ Assessed — no action (2026-05-23). Investigated as a suspected leftover unit-conversion constant (origin: original workbook's 1–21→0–100 credit scale + 1–5 criteria normalisation). Diagnostic finding: ×20 is a uniform 0–5→0–100 scale conversion applied to every rule; it cancels in all downstream weighted means and does NOT override real weights. Real weights (
points ±1/±2,rule_weight0.10–0.20,blend_weightE0.40/S0.30/G0.30) are active end-to-end and govern rankings. Conclusion: no weighting bug; ×20 is cosmetic except for its coupling to the deduction −50 baseline (documented in ADR-0023, cycle S6). Closed as assessed-no-action.
(No open items currently.)
Future ideas — out of scope for current build
Preserved here for future consideration. Not on any roadmap. Not gating phase 1 close.
Stage 2 — financials triangulation with credit and returns scoring. Original
v0.5 design combined Stage 1 ESG with credit and returns dimensions for
financial institutions only, producing a triangulated composite score.
De-scoped 2026-05-21 because the primary product purpose is ESG screening, not
multi-dimensional financials research; credit and returns data sources don't
exist; and the triangulation thesis only ever applied to banks. The Stage 2
schema placeholders (e.g. score_stage2_composite) remain in the database as
inert columns. The historical design note
docs/design/DESIGN_NOTE_v2_two_stage.md is retained as a superseded reference.
To revive: requires new ADR, credit and returns data source identification, and
explicit re-scoping decision.
How this page works
Universe page: auto-rendered from DB after each scoring run (scripts/generate-universe-md.js, ADR-0020).
ADR index on /decisions/: regenerated by scripts/sync-adr-to-ops.js at session close when ADRs are added.
CLAUDE.md on /reference/claudemd: regenerated by scripts/sync-claudemd-to-ops.js at session close when CLAUDE.md changes.
This backlog page: hand-maintained. End-of-session discipline: if a session completes an item, reorders
the v1 plan, or moves something into or out of the parked queue, update this page in the same session.
The canonical hierarchy still applies: ADRs > design note > migrations. This page summarises and links; it does not override.
Session operational note — 2026-05-21
Holdings status as of 2026-05-21: 4 institutions marked held — INST-0005 Amazon, INST-0004 HSBC Holdings, INST-0017 JPMorgan Chase, INST-0013 Deutsche Bank. Reflects the in-universe overlap with Holdings.xlsx portfolio export. Full ~50-institution universe alignment is cycle 15. Holdings.xlsx added to data/reference/holdings/ in the esg-screening main repo as audit trail (commit 7c53b39).