Close the gap from artifact to product: packet-first landing, offline flagship demo, governance parity, quick-win fixes - #1
Merged
Conversation
"BD Feasibility Scanner & Tracker" contradicted the packet-not-score thesis in the first five seconds. New H1: "Opportunity Packet & Public-Evidence Tracker". Nav page name and PAGE_RENDERERS key are untouched (only the H1 changes) so the two-page contract tests and demo_tour.target_page stay valid. DEMO_SCRIPT.md's operator Q&A about the old name is replaced with an honest one-liner. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…sh-corrupt the citation FR API URLs legitimately contain "[]" in query params (e.g. conditions[type][]=NOTICE); the blanket bracket-escape rendered visible backslashes that broke paste-and-resolve in both the packet body and the downloadable export. A clean absolute http(s) value with no whitespace/ angle-brackets/pipe now renders as a CommonMark autolink (<https://...>), which preserves "[]()" literally and cannot itself carry an injection. Applied to pl_activity's source_url/html_url lines and packet_export's manifest reference column only -- titles, filenames, and every other field stay on the existing fully-escaped renderer. New tests assert the verbatim bracketed URL is present with no backslash escapes, in both the packet body and the Source manifest, while a non-URL reference (e.g. an uploaded filename) is still two-layer escaped. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
The Scenario selectbox and Planning target slider only rendered on the Governance (non-BD) page and changed nothing downstream -- both page renderers immediately `del` them. Interactive controls that do nothing invite a reviewer to distrust the trust page, and the 70-82 slider visually resembled an ODLH knob the tool insists it never computes. scenario/target_pct now stay unconditionally set to their prior defaults so PAGE_RENDERERS(...) and the renderer signatures need no changes. Extended the existing packet-pages-render AppTest to assert neither widget renders on the Governance page in normal (non-pilot) mode. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
.small-note (#627D98 on the #F4F6F8 paper background) carries the legal/evidence-caveat disclaimers site-wide and fell below WCAG AA for small text. Swapped to #52606D, the slate already used for .page-subtitle and the metric label, which clears AA. Single hex value changed; no other rule touched. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…lips _render_guided_demo's three sidebar buttons (Previous / Open / Next) split the sidebar width evenly, clipping "Open" to "Ope/n" at 1440px -- the first interactive element a new evaluator sees. Column ratios changed to [1, 2, 1] so Open gets ~half the width; buttons, keys, callbacks, and use_container_width are unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
Every local scan dumped a developer-facing st.json payload right beside the friendly status banner. The JSON is preserved for analysts who want it, now tucked behind a collapsed expander instead of always-visible. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…UUID/version noise _cases_frame led with the raw case_id UUID and always showed the internal `version` column -- default-developer-table feel that undersold the product (§6.2). Reordered to lead with title/location/state, then team/role, then any of contract/service/headcount/start/job-families that actually has data across the current caseload (all-empty optional columns are dropped); case_id moves to the last column instead of the first, widest one. `version` is dropped from the displayed frame entirely (still on the Case object for any code that needs it). `state` semantics (repo.displayed_state(case).value) are untouched. New unit tests cover both the reorder and the empty-column suppression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…g to match the shipped product (ADR-024) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
… CHANGELOG Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…r to match Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…at the point of failure Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…d offline facts (ADR-025) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…dance (ADR-026)
Generate data/samples/sample_nib_npa.xlsx (scripts/make_sample_nib_npa.py) so
the tracker's NIB_NPA lane -- previously only satisfiable by a real workbook
upload -- can be driven offline. _render_scan gains an in-form checkbox
("Use the bundled SYNTHETIC NIB/NPA example instead of an upload") that
forces SourceKind.NIB_NPA and feeds the bundled bytes through the unchanged
WorkbookScanner.run_scan path; the attestation checkbox is still required.
Primary proof is a unit test parsing the actual committed sample through the
real NIB_NPA schema (3 PARSED rows); a secondary AppTest seeds a case
against the isolated ledger and confirms the checkbox renders wired with no
exception. DEMO_SCRIPT.md Act 4 and PILOT_RUNBOOK.md both note the new
offline lane.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…-aware reuse guard) The stored geography result's reuse guard matched on county+state only, so changing the ACS vintage year without re-pulling left a stale prior-year figure attached -- contradicting README.md's "editing an input detaches any stale result built on it" invariant. Stamp the stored result with the pull year and require it to match the current op_packet_year input before reuse. New AppTest confirms the bug (verified failing pre-fix) and the fix: pull at 2022, change the year to 2019 without re-pulling, assert the section falls back to "Not yet retrieved" instead of showing the stale figure. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…ing them as source outages Add a module logger (logging.getLogger(__name__)) to bd_page.py. All eight packet-path except Exception: handlers now call logger.exception(<static context>) before showing the operator message (never the URL, key, or PII). Because ConnectorError -- the genuine upstream-outage case -- is already caught first in every one of these blocks, the bare except is by definition an unexpected internal error, so the five handlers that previously said "The public source may be unavailable" (contract facts x2, subawards, ACS, Federal Register) are reworded to say an internal error was logged instead of blaming the source. The three handlers that already named the real cause (handoff, directory upload, PL workbook) keep their message, gaining only the logger.exception call. New offline test drives a monkeypatched ValueError through the contract facts pull and asserts both the operator-facing message (no "source may be unavailable", mentions "logged") and the actual logger.exception call via caplog. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
…dows execution-policy notes in the README Dockerfile: the SQLite case ledger (data/runtime/tens_hq.sqlite3) is .dockerignore'd, so it is absent from the built image. Create the directory and chown it to the runtime user (uid 10001) WHILE STILL ROOT, then declare it VOLUME ["/app/data/runtime"] -- a bare VOLUME on a path that doesn't yet exist would make Docker auto-create it root-owned, which the runtime user could not write, silently breaking the ledger on any container recreation that didn't already have a working writable mount by luck. README.md Quickstart: documents the execution-policy-safe invocation (`powershell -ExecutionPolicy Bypass -File .\run_demo.ps1`) for the default Windows 11 policy that blocks `.\run_demo.ps1` directly, and adds a Hosting note surfacing the single-user/unauthenticated ledger boundary (already documented deep in ARCHITECTURE.md) at the point a deployer actually reads the quickstart. docs/PILOT_RUNBOOK.md: documents the persistent-volume `docker run` form so a container recreation does not silently lose the ledger. Note: `docker build` could not be executed in this local sandboxed environment (no Docker CLI/daemon installed here). The Dockerfile change is a careful manual syntax review plus a direct transcription of the plan's own verified snippet; the full pytest + validate_demo_data.py gate stays green. `docker build` itself runs in CI and at the final pre-PR gate a later stage owns. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
Pin ruff==0.15.22 in requirements-dev.txt and pyproject.toml's dev extra; add a [tool.ruff]/[tool.ruff.lint] config (line-length 100, target py311, select E/F/W/I) and a "Lint (ruff)" CI step before the pytest step. E501 (line-too-long) is deliberately excluded via ignore = ["E501"]: 515 of 585 initial findings were this one rule, spanning nearly every module in a codebase whose established style favors long single-line comprehensions and assertions over hard-wrapping -- enforcing it would force a sweeping, high-risk reformat rather than a surgical lint gate (the plan's own risk-mitigation guidance: narrow the noisy rule rather than mass-reformat). `ruff format --check` is not added either, for the same reason (48 files would reformat) -- the plan only adds it "if it is already clean". Every other finding was fixed for real: 13 unused imports and 32 unsorted import blocks (mechanical, verified by AST name-set diff against every touched file -- nothing added or dropped beyond the intended prunes), 14 `l`-named comprehension variables renamed to `lead`/`line` in two test files, and 2 unused local variables (one was a genuine dead computation in synthetic.py; the other, in case_store.py, was an existence-check call kept in place with its now-intentionally-discarded return value documented). Also added `# noqa: E402` to the three scripts/entry points whose sys.path mutation must precede their src-layout imports (app.py, generate_demo_data.py, validate_demo_data.py) -- reordering was not an option there. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
test-and-build now runs as a matrix over python-version ["3.11", "3.12"] so the numpy>=2.5.1 branch real deployments install (requirements.txt's python_version marker) is exercised in CI, not just 3.11/numpy 2.4.6. Pin pip-audit==2.10.1 (requirements-dev.txt + pyproject.toml dev extra) and add a "Dependency vulnerability scan" step. It is intentionally non-blocking (`|| true`) with an inline TODO(owner) comment: pip-audit currently flags 27 known advisories across the pinned streamlit==1.46.1 and its transitive pillow dependency, and every fix requires a streamlit major-version bump -- a runtime-behavior change this CI-hardening task does not make unilaterally against a suite where 563+ tests are AppTest-pinned to this exact version. Per the plan, this is an owner-gated triage decision (§7 [OWNER] item 6). Add a container smoke boot after `docker build`: run the built image detached, poll /_stcore/health for up to ~30s, fail the step if it never reports healthy, then always stop/remove the container. A comment notes the health endpoint does not execute app.py or open the case ledger, so it proves the container boots and serves -- not a substitute for the Dockerfile VOLUME's writable-ownership review from the prior commit. pip-audit, docker build, and the smoke boot all run once, on the 3.11 leg only -- they exercise the shipped artifact, not the interpreter matrix. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the gap between an excellent artifact and an excellent product: makes the cited Opportunity Packet the first thing a stranger sees, gives the offline demo a resolvable flagship moment (bundled SYNTHETIC contract-facts + NIB/NPA samples), rewrites the self-contradicting Privacy & Governance page, and clears the quick-win frictions — without adding any score/ranking/feasibility semantics, without weakening the evidence/citation discipline, and without breaking a single existing test.
19 tasks (S0–S18) executed in order from
C:/Users/cjudk/.claude/plans/reconradar-oneshot-2026-07-23.md, one gate-green commit per task:.small-notecaptions to WCAG AAAPI_RETRIEVEDResult: 582 passed (baseline 563 + 19 new tests), 0 failures;
validate_demo_data.pyexits 0;ruff check .exits 0. Full task-by-task detail in the shared worklog.An adversarial QC + security review ran over this branch before this PR was opened; the one finding raised (docker build unverified locally) was confirmed as a documented, already-flagged fact rather than an actionable gap — see notes below.
Known gaps (flagged honestly, not hidden)
VOLUMEaddition (S16) and the new CI container smoke-boot step (S18) were authored and manually reviewed for correctness but never run against a live Docker daemon locally — this PR's CI run (which does have Docker) is their first real exercise. Watch thedocker build+ smoke-boot step specifically.pip-auditfound 27 live advisories against pinnedstreamlit==1.46.1and its transitivepillowdependency (fix versions 1.53.1/1.54.0 and 12.1.1–12.3.0 respectively). Left non-blocking (|| true) with an inlineTODO(owner)per the plan's explicit fallback instructions — bumping Streamlit is a real, options-y decision against 563+AppTest-pinned tests and does not belong in a CI-hardening task. This is [OWNER] step 6 in the plan.Not in this PR (owner-gated, per plan §7)
Merge decision, hosted-instance reboot/redeploy, DRAFT pilot success-criteria finalization, private-mirror sync decision, and prioritization of descoped "Next" follow-ups are all explicitly reserved for the owner.
🤖 Generated with Claude Code
https://claude.ai/code/session_01EUwLDdWYTGrWQwqb94VGCk