Skip to content

Add Streamlit analysis stories and visual design review - #15

Merged
hudsonaikins merged 9 commits into
mainfrom
codex/streamlit-analysis-stories
Nov 14, 2026
Merged

hudsonaikins merged 9 commits into
mainfrom
codex/streamlit-analysis-stories

Conversation

@hudsonaikins

@hudsonaikins hudsonaikins commented Nov 14, 2026 •

Copy link
Copy Markdown
Contributor

Verified analysis reports now generate a local Streamlit dashboard and business workbook. Readers get a business brief, a guided chart narrative, table exploration, and supporting definitions instead of having to reconstruct the decision from exported artifacts.

Changes

  • Add dashboard generate, serve, and review, plus opt-in automatic review through analyze --review-dashboard.
  • Add an independent native Anthropic visual reviewer scoped to dashboard review and at most two Sol presentation revisions. Strict plans preserve source tables, values, units, and chart bindings; the loop retains the better candidate and holds unsuccessful designs.
  • Capture desktop/mobile views with the trusted Streamlit app and Playwright, check navigation/search/downloads, and record screenshots, scores, and usage in LangSmith.
  • Add the taxi business narrative, month labels, readable portrait heatmaps, explicit color-scale endpoints, and mobile axis-title spacing.
  • Enforce the remaining analysis deadline across automatic dashboard/workbook generation, browser capture, reviewer, and writer requests. Initial and revision workbook generation runs in an owned child process; expiry or cancellation stops it and removes staged files before publication. ChatGPT-plan streaming is cancellable, and timed-out reviews retain only provisional results.
  • Preserve human-readable source descriptions in dashboards, workbooks, and reviewer context; normalize required labels, disambiguate section headings, and size airport chart axes from both retained series.
  • Validate complete taxi tables and numeric values, map coverage counts by source label, autoscale quantitative axes, and show histogram ranges. Reject invalid counts and percentages. Screenshot capture waits for every chart to render, uses the same explicit theme as the reader app, and requires passing reviews to cover every supplied view.
  • Match taxi growth narration to the measured direction and reject airport totals or months outside the quarterly scope. Review-only mode needs no writer credentials; LangSmith feedback-upload errors are recorded separately from the retained assessment.
  • Reconcile pickup-area shares and coverage counts against retained totals. Eligibility follows the source SQL: valid-duration records minus fare-tail exclusions equals the retained population. Reject blank review feedback and capture long valid pages within the 200-image/24 MB limit.
  • Preserve completed analytical delivery when optional review is unavailable, reconcile monthly rates and fare distributions, and display small nonzero KPI values without rounding them to zero.
  • Include offline storytelling guidance, setup documentation, optional dependencies, and browser-test support in CI.

Validation

  • Local suite: 329 passed, 15 skipped. Ruff lint/format, Vulture, dependency-lock consistency, Git whitespace checks, and Gitleaks passed.
  • Rendered desktop (1280px) and mobile (390px) checks covered navigation, table search, workbook values, month labels, every heatmap hour, and unclipped color-scale endpoints. The final mobile axis-title gaps exceeded 20px; the quality detector reported no issues.
  • Live taxi design review: 4/5 on all five criteria, no blockers, 18 browser checks, 30 screenshots. This review preceded the final endpoint/spacing polish, which received direct browser verification. The score is an automated design judgment, separate from the frozen analytical benchmark.

Current limits

Serving remains local. Design review is opt-in and uses the configured native provider accounts; it can make up to three reviewer calls and two writer calls. Presentation revisions cannot invent calculations or arbitrary app code. Datasets, generated workbooks/screenshots, and raw review records remain outside Git.

See dashboard setup and evidence for commands, boundaries, and the retained taxi example.

Generate business-focused dashboards and workbooks from verified analysis reports. Add the taxi narrative, readable month labels, mobile heatmaps, explicit color-scale endpoints, and axis spacing.

Add native Claude visual review with up to two Sol presentation revisions, browser checks, and LangSmith evidence. Include CLI integration, offline guidance, tests, and CI dependencies.
@hudsonaikins
hudsonaikins marked this pull request as ready for review November 14, 2026 20:22
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 14, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ❌ Failed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-14T22:31:32.753510Z 241e3ca New commits
🔒 Security Review ❌ Failed 2026-11-14T22:26:53.107064Z
We couldn’t complete this review. Comment @codex security review to try again.
241e3ca New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 624e3aaf7b

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/backintel/models.py Outdated
Comment on lines +192 to +193
if provider == "anthropic":
from langchain_anthropic import ChatAnthropic

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Restrict native Anthropic construction to dashboard review

Because build_model() is also called by the general ask, benchmark, and judge paths, accepting anthropic/* here lets commands such as backintel ask --model anthropic/... run the analyst directly through Anthropic rather than limiting the native provider to the independent dashboard reviewer. Add an explicit review-only gate or a dedicated reviewer factory so the new provider cannot bypass the repository's model-routing boundary.

AGENTS.md reference: AGENTS.md:L36-L41

Useful? React with 👍 / 👎.

Comment thread src/backintel/presentation/capture.py Outdated
menuitem = page.get_by_role("menuitem", name=name, exact=True)
target = menuitem if menuitem.is_visible() else link
target.click(timeout=10000)
page.get_by_role("heading", name=name, exact=True).wait_for(timeout=15000)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Scope the page-heading wait to the section heading

When a valid design plan gives a section and one of its charts the same title, the page renders both an st.header and st.subheader with that accessible name. This unscoped locator then matches multiple headings, causing Playwright's strict wait_for to fail and the review to end as review_error even though the dashboard rendered correctly; constrain the lookup by heading level or select the section heading explicitly.

Useful? React with 👍 / 👎.

Comment thread src/backintel/analysis.py Outdated
if dashboard_reviewer is not None:
from backintel.presentation.review import review_dashboard

record["dashboard_review"] = await asyncio.to_thread(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep automatic review inside the run deadline

When analyze --review-dashboard is used, this review starts after the graph's asyncio.timeout and the publisher's remaining-time check, with no deadline of its own. It can therefore perform three reviewer calls, two writer calls, and repeated browser captures after timeout_s has already elapsed, so --timeout no longer bounds the analysis command; pass the remaining budget into the review or enforce a deadline around the entire stage.

Useful? React with 👍 / 👎.



class Section(StrictModel):
title: str = Field(min_length=1, max_length=45)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject whitespace-only design copy

Pydantic's min_length=1 accepts values such as " ", so a generated or supplied plan can pass validation with a visually blank section title. That produces an unusable navigation entry and can make capture fail while waiting for a heading with no accessible name; trim or explicitly reject whitespace-only values for required presentation strings.

Useful? React with 👍 / 👎.

)
f.update_layout(barmode="group", bargap=0.4, showlegend=True, legend=dict(orientation="h", y=1.25, x=0))
f.update_xaxes(
range=[0, 17],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Derive the airport axis range from retained shares

Any accepted taxi story containing an airport share above 17% has its bar truncated at this fixed maximum, which visually understates the retained value even though the hover and label report the larger percentage. The valid fixture in tests/test_taxi_story.py already supplies a 20% JFK fare share, so this occurs for data the validator accepts; calculate the upper bound from the plotted shares with suitable padding.

Useful? React with 👍 / 👎.

report = validate_report(spec)
# Explicit allowlists prevent arbitrary report extensions and execution metadata
# from entering the browser. Narrative text remains the analyst's responsibility.
result = {key: report[key] for key in ("title", "summary", "findings", "caveats")}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve sanitized source context in dashboard data

For reports whose title and summary do not redundantly name the source, this projection discards the required sources records entirely, including their human-readable descriptions. Consequently the dashboard, downloaded workbook, and design reviewer cannot tell readers what dataset or period the analysis represents; retain a sanitized source description while continuing to exclude snapshots, hashes, and execution metadata.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a1f84386b4

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/backintel/analysis.py Outdated
record["artifacts"] = verify_bundle(output / "artifacts" / "report")
from backintel.presentation.dashboard import generate_dashboard

record["dashboard"] = generate_dashboard(rendered["spec"], output / "dashboard")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include dashboard generation in the run deadline

When artifact publication finishes close to timeout_s, this always-on dashboard step builds and styles the workbook without checking the remaining budget or running under a timeout. A report near the 100,000-cell contract limit can therefore keep analyze running past --timeout and still be recorded as completed; apply the remaining whole-run deadline to dashboard generation as well.

AGENTS.md reference: AGENTS.md:L28-L31

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3465bc2706

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +117 to +118
if list(frame(story, "fare_bands").Fare_band) != FARE_BANDS or len(frame(story, "quality")) != 10:
raise ValueError("Incomplete fare or coverage table.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require the ten-zone table promised by the story

When the zones table contains any nonempty number of rows other than ten, validate_story still accepts it—the committed test fixture contains only three—while the Explore view describes the result as “Ten pickup zones” and zone_chart plots every supplied row. An incomplete or oversized aggregate can therefore be presented with a false population claim; validate that this table contains exactly the ten expected ranking rows before displaying it.

Useful? React with 👍 / 👎.

)
)
f.update_xaxes(
range=[0, 28],

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Size the zone axis from the retained fare values

When any accepted pickup zone has more than $28 million in Gross_fare_USD, this fixed axis maximum clips its bar and outside value label, visually understating the retained aggregate. validate_story permits any finite fare total, so derive the upper bound from the plotted values with label padding rather than assuming this dataset-specific ceiling.

Useful? React with 👍 / 👎.

Comment on lines +103 to +105
if len(row) != len(table["columns"]):
raise ValueError(f"Invalid {name} row.")
if any(isinstance(value, (int, float)) and not math.isfinite(value) for value in row):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject nonnumeric values in numeric story columns

When an unbound numeric field such as monthly.Gross_fare_USD contains a string, this check accepts it because it only tests values already typed as numbers. prepare_story then publishes the invalid story, and the Streamlit app raises an uncaught decimal.InvalidOperation while computing summary() instead of showing its readable invalid-data state. Validate every required numeric column by schema rather than allowing arbitrary strings through.

Useful? React with 👍 / 👎.

Comment thread src/backintel/presentation/capture.py Outdated
Comment on lines +165 to +166
if expected:
await charts.first.locator(".main-svg").first.wait_for(timeout=15000)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Wait for all charts before taking review screenshots

When a page contains multiple Plotly charts and later charts render more slowly than the first, the count assertion succeeds as soon as their containers exist, but this waits for an SVG only in the first chart. Screenshots can therefore capture blank later charts and cause the reviewer to reject a correctly rendered dashboard based on incomplete evidence; await the rendered SVG for every chart before capture.

AGENTS.md reference: AGENTS.md:L36-L39

Useful? React with 👍 / 👎.

Comment on lines +160 to +161
for row, label in zip(rows, names, strict=True):
row[0] = label

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve the meaning of quality rows when relabeling

When the retained quality table contains the expected ten populations in a different order, this positional rewrite silently assigns each count to the wrong business label—for example, an eligible-record count can be presented as source records. Neither prepare_story nor validate_story checks the original Population values, so map recognized source labels explicitly or reject an unexpected order before replacing them.

Useful? React with 👍 / 👎.

Comment thread src/backintel/presentation/dashboard.py Outdated
Comment on lines +229 to +230
centers, counts, width = _histogram(chart, table)
f.add_trace(go.Bar(x=centers, y=counts, width=width, marker_color="#176581"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Show histogram bin ranges instead of only their centers

For every histogram dashboard, _histogram returns each bin's width but this trace discards it for hover semantics, so Plotly reports only the bin center and count; a reader can mistake a center such as 15 for an observed value rather than the interval 10–20. The existing artifact renderer uses the same width to expose lower and upper bounds in artifacts.py lines 439–443, so retain those bounds in the dashboard trace as well.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c2f67db6ea

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +123 to +126
if type(value) not in (int, float):
raise ValueError(f"Nonnumeric value in {name}.{column}.")
if not math.isfinite(value):
raise ValueError(f"Non-finite value in {name}.{column}.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate numeric domains before publishing the story

When a retained report contains fractional or negative record counts, or a percentage outside 0–100, this validation accepts it because every finite int or float passes. The resulting public story can show impossible shares or negative source records, and fractional monthly.Trips are silently truncated by summary() while the workbook retains the original values, causing the headline and supporting data to disagree. Enforce column-specific domains, including integral nonnegative counts and bounded percentage fields, before publication.

Useful? React with 👍 / 👎.

Comment on lines +82 to +84
if not self.passed and not (self.blocking_issues or self.revisions):
raise ValueError("A failing review must provide actionable feedback.")
return self

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Require reviews to cover every captured view

When the reviewer cites one valid screenshot for each rating but ignores the remaining pages or the entire mobile capture, this check still accepts the review because it only rejects unknown view names. A five-star response can therefore mark the dashboard ready without assessing layout failures visible in uncited captures, despite the capture stage supplying every section at both widths. Require the union of cited views to cover view_names before allowing a passing review.

Useful? React with 👍 / 👎.

Comment thread src/backintel/presentation/capture.py Outdated
Comment on lines +38 to +43
"--server.headless",
"true",
"--browser.gatherUsageStats",
"false",
"--server.fileWatcherType",
"none",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render review captures with the delivered theme

When dashboard review is run from an installed package or any directory without this repository's .streamlit/config.toml, this preview command receives only server flags and falls back to Streamlit's default client and theme settings. The normal dashboard serve command supplies the BackIntel colors, fonts, sizing, borders, and toolbar options explicitly, so the reviewer can accept or reject screenshots that do not match what readers receive. Pass the same client and theme flags to the capture server rather than relying on the caller's working directory.

AGENTS.md reference: AGENTS.md:L36-L40

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc25b1e0b3

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +136 to +137
if list(monthly.Month) != list(MONTHS) or (monthly.Trips_per_day <= 0).any():
raise ValueError("This story requires the complete January–March 2025 monthly view.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject data that contradicts the fixed March-growth story

When a structurally valid Q1 report has March Trips_per_day equal to or below January, this check still accepts it because it only requires every daily rate to be positive. The dashboard then says activity “rose,” while the KPI unconditionally prepends + and can render values such as +-10.0%; either validate the growth premise or make the KPI and narrative conditional on the measured direction.

Useful? React with 👍 / 👎.

Comment on lines +138 to +139
if monthly.Trips.sum() <= 0 or monthly.Gross_fare_USD.sum() <= 0:
raise ValueError("Quarterly trip and fare totals must be positive.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Bound computed airport shares against quarterly totals

When the JFK and LaGuardia rows contain trip or fare totals greater than the quarterly totals in monthly, every individual value passes these checks, but summary() divides those airport values by the smaller monthly denominators and publishes a share above 100%. The bounded *_share_pct columns do not protect this separately computed headline, so validate the cross-table totals before accepting the story.

Useful? React with 👍 / 👎.

Comment thread src/backintel/cli.py Outdated
"Use the existing OpenAI/ChatGPT writer; the reviewer must be a different family."
)
reviewer = build_dashboard_reviewer(args.reviewer_model)
writer = build_model(writer_id, reasoning_effort="medium", max_retries=0, timeout_s=120)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Skip writer construction for review-only runs

When dashboard review --max-revisions 0 is run with native Anthropic credentials but without an OpenAI key or ChatGPT-plan login, this unconditional build_model call aborts before the reviewer runs, even though the loop can never invoke the reviser and the documented option is specifically meant to review without revising. Defer writer construction and validation unless max_revisions is greater than zero, or permit an absent reviser in that mode.

Useful? React with 👍 / 👎.

Comment on lines +72 to +78
await client.create_feedback(
run_id=run.id,
session_id=session_id,
key="dashboard_" + rating.criterion,
score=rating.score,
comment=rating.evidence,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep feedback-upload failures from invalidating a review

When LANGSMITH_TRACING=true and LangSmith is temporarily unavailable or rejects feedback writes, this awaited create_feedback call raises after the model has already returned a valid assessment. The outer review loop then records review_error instead of accepting or retaining that assessment, making optional observability availability determine dashboard readiness; preserve the local review result and record feedback-upload failure separately.

Useful? React with 👍 / 👎.

Comment on lines +162 to +165
segments = frame(story, "segments")
for month in (date[:7] for date in MONTHS):
if not set(AIRPORTS) <= set(segments[segments.Month == month].Pickup_segment):
raise ValueError("Incomplete monthly airport coverage.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject airport-mix rows outside the declared quarter

When segments contains all required January–March rows plus rows for another month, this loop accepts the table because it checks only that each expected month is present. The story still declares January–March 2025 while business_workbook() exports every segment row, so the delivered workbook silently includes out-of-scope data; require the set of segment months to match the declared quarter.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 670bc71b6d

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/backintel/presentation/review.py Outdated
plan = validate_plan(plan.model_dump(), data)
if plan:
(folder / "presentation.json").write_text(plan.model_dump_json(indent=2))
(folder / "analysis.xlsx").write_bytes(workbook_bytes(data, plan))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Move workbook creation outside the event loop

When automatic review starts with little time remaining, or a valid report approaches the 100,000-cell limit, this synchronous OpenPyXL serialization blocks the event loop, so the surrounding asyncio.timeout cannot expire until the workbook finishes; every revision repeats the work. As a result, analyze --timeout can still be exceeded despite the new whole-review deadline. Run workbook creation in a cancellable owned process, as dashboard generation already does, or create it before entering the remaining-time budget.

Useful? React with 👍 / 👎.

Comment on lines +170 to +172
for column in ("Trips", "Gross_fare_USD"):
if sum(Decimal(str(x)) for x in airports[column]) > sum(Decimal(str(x)) for x in monthly[column]):
raise ValueError("Airport totals cannot exceed the quarterly trip or fare totals.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Cross-check displayed airport shares against totals

When the two airport rows contain bounded but incorrect Trip_share_pct or Fare_share_pct values, this new check accepts them because it validates only raw trip and fare totals. For example, the retained counts can make summary() report a 5% combined airport share while two 90% share fields make airport_chart() display 90% for each airport. Even after the new count-total check, the dashboard can therefore contradict its own headline; validate the supplied shares against their monthly/quarterly denominators within a rounding tolerance, and ensure mutually exclusive airport shares cannot exceed 100% in aggregate.

Useful? React with 👍 / 👎.

Comment thread src/backintel/presentation/design.py Outdated
Comment on lines +74 to +75
blocking_issues: list[str] = Field(max_length=12)
revisions: list[str] = Field(max_length=8)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject blank review feedback items

When the reviewer returns blocking_issues=[""] or revisions=[" "], whitespace stripping leaves a nonempty list containing an empty string, and checked() treats that as actionable feedback. The candidate is therefore rejected and subsequent paid revision calls are asked to fix an unspecified issue; require every list item to contain bounded nonblank text before accepting a failing review.

Useful? React with 👍 / 👎.

Comment on lines +147 to +150
if list(frame(story, "fare_bands").Fare_band) != FARE_BANDS or list(
frame(story, "quality").Population
) != list(QUALITY_LABELS.values()):
raise ValueError("Incomplete fare or coverage table.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Validate coverage counts against the retained population

When the named quality rows contain individually valid but contradictory counts, this check accepts them because it verifies only their labels. The app can then state that a source with 100 records retained 300 trips, or that the fare guardrail excluded more records than were otherwise eligible, while displaying those incompatible values in the coverage table. Validate the funnel rows and require the eligible count minus the tail exclusion to agree with the quarterly retained-trip total before publishing this narrative.

Useful? React with 👍 / 👎.

Comment thread src/backintel/presentation/capture.py Outdated
Comment on lines +165 to +168
if height > 16000:
raise ValueError(
"Dashboard view is too tall to review completely; split it into sections."
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow valid long dashboards to reach the revision loop

When an unplanned but contract-valid report contains the allowed twelve 40-row bar charts, their declared 1,200-pixel heights alone consume 14,400 pixels, and the headings, metrics, and page chrome can push the initial Story view past this 16,000-pixel cutoff. Capture then aborts before the first review, so the writer never gets an opportunity to split the charts into the sections that this error requests. Capture the complete baseline in bounded viewport segments, or otherwise create a splittable initial plan before enforcing the per-page height limit.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 377c68cdbf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread src/backintel/analysis.py Outdated
if dashboard_reviewer is not None:
from backintel.presentation.review import review_dashboard_async

record["dashboard_review"] = await review_dashboard_async(

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Keep optional review failures from failing completed analysis

When --review-dashboard is requested for a valid table-only report, review_dashboard_async() raises before its internal error-recording block because the dashboard has no charts. This unguarded call then reaches the outer exception handler and changes the entire run to failed, even though the analysis and dashboard export completed successfully; record the review as unavailable/error without invalidating analytical completion.

AGENTS.md reference: AGENTS.md:L36-L41

Useful? React with 👍 / 👎.

Comment on lines +142 to +146
monthly = frame(story, "monthly")
if list(monthly.Month) != list(MONTHS) or (monthly.Trips_per_day <= 0).any():
raise ValueError("This story requires the complete January–March 2025 monthly view.")
if monthly.Trips.sum() <= 0 or monthly.Gross_fare_USD.sum() <= 0:
raise ValueError("Quarterly trip and fare totals must be positive.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reconcile monthly rates with retained totals

When a monthly row has positive but inconsistent derived values, this validation accepts it—for example, 3,100 trips with Trips_per_day=1, or $31,000 in fares over 3,100 trips with Mean_fare_USD=50. The dashboard then displays those derived values alongside conflicting quarterly totals and explicitly describes them as calendar-day rates and average fares; validate both fields against the retained trip/fare totals and the known month lengths within the report's rounding tolerance.

Useful? React with 👍 / 👎.

Comment on lines +154 to +157
if list(frame(story, "fare_bands").Fare_band) != FARE_BANDS or list(
frame(story, "quality").Population
) != list(QUALITY_LABELS.values()):
raise ValueError("Incomplete fare or coverage table.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reject fare-band shares that do not form a distribution

When the five required fare bands contain individually bounded shares whose total is not approximately 100%, this check still accepts the story. The Explore view then presents them as an exhaustive share of eligible quarterly trips and can make claims from a distribution totaling, for example, 250%; require the retained band shares to sum to 100% within an appropriate rounding tolerance.

Useful? React with 👍 / 👎.

for magnitude, suffix in [(1e9, "bn"), (1e6, "m")]:
if abs(value) >= magnitude:
return f"{value / magnitude:,.2f}{suffix}"
return f"{value:,.2f}".rstrip("0").rstrip(".") if value % 1 else f"{value:,.0f}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve small nonzero metric values in KPI formatting

When a valid metric has a small nonzero value such as 0.001, formatting it to two decimal places and stripping trailing zeros renders the KPI as 0 in both generated dashboard variants, while the retained table and workbook still contain 0.001. This can materially misstate rates, ratios, or small currency measures; use significant-digit or magnitude-aware formatting so a nonzero metric is never displayed as zero.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector

Copy link
Copy Markdown

The account paying for this security review has reached its Codex usage limits. The payer can check the Codex usage dashboard. For personal accounts, using credits requires enabling “Use credits for security reviews” in Code review settings. If you do not manage the paying account, contact this repository's admins.

@hudsonaikins
hudsonaikins merged commit df71414 into main Nov 14, 2026
2 checks passed
@hudsonaikins
hudsonaikins deleted the codex/streamlit-analysis-stories branch November 14, 2026 22:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant