feat: add start_date/end_date query params to /timeseries/meta#2984
Conversation
Adds optional `start_date` and `end_date` ISO-8601 query parameters to the GET /timeseries/meta endpoint. When provided they override the `days`-based window, letting callers request an explicit date range instead of fetching all data. Backwards compatible: omitting both params preserves existing `days` behaviour. Invalid ranges (start > end) return HTTP 422. Closes #2747 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm compliance:
2. Bugs and logic errorsNo apparent bugs or logic errors were found in the implementation. The handling of date ranges and validation appears to be correctly implemented. The use of 3. API, data, and workflow safety
4. Test coverageThe test coverage is comprehensive and addresses all the acceptance criteria:
5. Minor issues (optional)
Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance CriteriaMissing AC: filtering is not pushed to the data layer. The issue explicitly requires: "Push filtering to the data layer — do not fetch all rows and filter in Python. Pass the bounds into the query/ORM call so the database does the work." The diff shows Missing AC: helper extraction.
No such helper appears anywhere in the diff. Date resolution logic lives entirely in the route function, which the issue explicitly forbids. Missing AC: scope is too narrow. The issue says "relevant API endpoints" and the How section says to grep for all routes returning timeseries/position data. This PR only touches Satisfied ACs:
2. Bugs and Logic ErrorsOff-by-one / semantic mismatch on Before this PR, resolved_end = end_date if end_date is not None else date.today() - timedelta(days=1)
if start_date is not None:
resolved_start = start_date
elif days <= 0:
resolved_start = date(1900, 1, 1)
else:
resolved_start = resolved_end - timedelta(days=days)When 422 raised via FastAPI's built-in validation errors return a structured
# Parameters named start_date, end_date
# ...later in the function body:
start_date = resolved_start
end_date = resolved_endThis reassigns the function parameters mid-body. It works but is confusing and masks the original query-param values from any subsequent introspection or logging. Use the
The test asserts 3. API, Data, and Workflow SafetyFrontend contract: The response schema is unchanged per the diff. No frontend changes are made. If the frontend calls this endpoint without date params, behavior is preserved (modulo the off-by-one noted above). No breaking change here, but the
# Risky on Python < 3.10:
start_date: date | None = Query(None)
# Safe:
from typing import Optional
start_date: Optional[date] = Query(None)No changes to CDK/Lambda configuration — no environment or timeout concerns introduced. 4. Test Coverage
The filtering tests need to assert that captured = {}
def mock_load(ticker, exchange, start, end):
captured["start"] = start
captured["end"] = end
return _multi_day_df()
monkeypatch.setattr(ts_meta, "load_meta_timeseries_range", mock_load)
# ...
assert captured["start"] == date(2024, 1, 1)
assert captured["end"] == date(2024, 1, 3)Without this, the tests give false confidence that data filtering works. 5. Minor Issues
REQUEST CHANGES — The PR has an unverified data-layer filtering path, a Python version compatibility risk with Reviewed by Claude via claude-pr-review.yml. Advisory only. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c9ea53a1f0
ℹ️ 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".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| else: | ||
| start_date = date.today() - timedelta(days=days) | ||
| end_date = date.today() - timedelta(days=1) | ||
| resolved_start = resolved_end - timedelta(days=days) |
There was a problem hiding this comment.
Restore previous days window calculation
When start_date is omitted and days > 0, this now bases the start on resolved_end (yesterday by default), so ordinary requests like /timeseries/meta?days=365 fetch from today - 366 through today - 1 instead of the previous today - 365 through today - 1. That makes the default/backwards-compatible path return one extra day and also affects the new end_date-only path; use a calculation that preserves the existing inclusive days window.
Useful? React with 👍 / 👎.
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsThere are no apparent bugs or logic errors in the implementation. The handling of date ranges and the validation logic appear to be correctly implemented. 3. API, data, and workflow safety
4. Test coverageThe test coverage is comprehensive and aligns well with the acceptance criteria:
5. Minor issues (optional)There are no significant style or naming issues. The code is clear and follows existing conventions. Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview1. Acceptance criteriaThe issue lists six test scenarios and several implementation requirements. Checking each:
Notable gap: The issue requires filtering to be pushed to the data layer. The diff shows 2. Bugs and logic errorsThe 422 for # timeseries_meta.py lines ~63-70
resolved_end = end_date if end_date is not None else date.today() - timedelta(days=1)
if start_date is not None:
resolved_start = start_date
elif days <= 0:
resolved_start = date(1900, 1, 1)
else:
resolved_start = resolved_end - timedelta(days=days)If a caller passes The real subtle bug: the 422 check fires based on More importantly: the The start_date = resolved_start
end_date = resolved_endThese reassignments shadow the FastAPI-injected query parameters. If someone later adds logic that reads
3. API, data, and workflow safetyData layer not confirmed. The issue requires filtering at the query/ORM level. The diff doesn't show No frontend changes. The endpoint adds optional parameters — existing callers are unaffected. Backend/frontend contract is preserved. ✅ CI/deployment: No workflow files changed. The import reordering (moving 4. Test coverageThe six required test cases are present. However:
# Missing: assert that load_meta_timeseries_range was called with
# start_date=date(2024,1,1), end_date=date(2024,1,3)
5. Minor issues
Summary verdictREQUEST CHANGES — The data-layer filtering requirement (core AC) is unverified in both implementation and tests; the Reviewed by Claude via claude-pr-review.yml. Advisory only. |
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsThe implementation appears to be logically sound. However, there is a minor potential issue with the handling of the 3. API, data, and workflow safety
4. Test coverageThe test coverage is comprehensive and addresses all acceptance criteria:
5. Minor issues (optional)
Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance CriteriaWorking through the issue's AC checklist:
Critical gaps: Filtering is not pushed to the data layer. The issue explicitly says "do not fetch all rows and filter in Python — pass the bounds into the query/ORM call so the database does the work." The diff only resolves No 2. Bugs and Logic ErrorsThe 422 validation fires too late / on wrong inputs (line 70–74). When The user sees a date they never typed. The error message should clarify that
FastAPI's built-in validation errors return a structured JSON body
start_date = resolved_start
end_date = resolved_endThe function parameters
start_date: date | None = Query(None, ...)The repo's Python version floor isn't visible in this diff. If the project targets 3.9 (common for Lambda), this will fail at runtime with a 3. API, Data, and Workflow SafetyFrontend contract: The diff touches only the backend route. The
CI/Lambda safety: No infrastructure changes in this diff. The Python syntax concern (§2) is the only deployment risk. 4. Test CoverageThe six required test cases are present and correctly named. Issues:
5. Minor Issues
Summary VerdictREQUEST CHANGES The two blocking issues are: (1) no evidence that filtering is pushed to the data layer as required by the AC, and (2) Reviewed by Claude via claude-pr-review.yml. Advisory only. |
- Use html.escape() on ticker/exchange before inserting into HTML template to close reflected-XSS (CodeQL high-severity alert) - When neither start_date nor end_date is supplied, anchor the days window to date.today() (not resolved_end) to preserve the original [today-days, today-1] inclusive window (fixes off-by-one flagged in Codex review comment)
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsNo significant bugs or logic errors are present in the implementation. The handling of date ranges appears robust, and the logic for determining the start and end dates is clear. However, I would suggest ensuring that the 3. API, data, and workflow safety
4. Test coverageThe test coverage is comprehensive and addresses all the acceptance criteria:
All scenarios specified in the acceptance criteria are covered, ensuring that the implementation is well-tested. 5. Minor issues (optional)
Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
PR Merge Checker — SummaryMerge readiness
Actions takenCommit
Remaining blockers
VerdictNOT READY YET — waiting for CI re-run after the security fix. No human action required beyond approving the merge once checks go green. |
Claude AI Code ReviewPR Review:
|
| AC | Status |
|---|---|
Optional start_date/end_date query params, ISO-8601 |
✅ |
Reject start_date > end_date with HTTP 422 |
|
Accept None for either bound |
✅ |
Use datetime.date so FastAPI handles parsing |
✅ |
| Push filtering to data layer | ❌ Not done — see §2 |
| Extract helper in service/repository layer | ❌ Not done |
| Response schema unchanged | ✅ |
| Tests: happy path both dates | ✅ |
| Tests: open start | ✅ |
| Tests: open end | ✅ |
| Tests: neither supplied (regression) | ✅ |
| Tests: invalid range → 422 | |
| Tests: malformed date → 422 | ✅ |
make lint / npm lint passing |
Cannot verify from diff |
PR body references Closes #2747 |
❌ Not visible in PR |
2. Bugs and Logic Errors
Critical: Filtering is not pushed to the data layer
The issue explicitly requires: "Push filtering to the data layer — do not fetch all rows and filter in Python." The diff passes start_date/end_date to load_meta_timeseries_range(), which is promising, but we can't see the implementation of that function in the diff. If load_meta_timeseries_range was already doing date-bounded queries, that's one thing — but the PR adds no changes to the service/repository layer, and no helper apply_date_range function is introduced. The issue is either silently satisfied (if load_meta_timeseries_range already accepted and used these bounds before this PR) or silently violated. The diff is incomplete without showing the data layer.
422 via HTTPException is wrong
At line ~72:
raise HTTPException(
status_code=422,
detail=f"start_date ({resolved_start}) must not be after end_date ({resolved_end})",
)FastAPI's HTTPException serialises as {"detail": "..."} but does not produce a standard 422 Unprocessable Entity response in the same shape as FastAPI's own validation errors (which wrap the detail in {"detail": [{"loc": [...], "msg": "...", "type": "..."}]}). This is a minor inconsistency but callers inspecting the error shape will get something different from a malformed-date 422. More importantly, the test test_invalid_range_returns_422 only checks the status code — it doesn't verify the response body. This is fine for now but worth noting.
A cleaner approach would be to raise RequestValidationError or use a custom validator, but HTTPException(422) is acceptable if the inconsistent shape is intentional.
days <= 0 edge case interacts unexpectedly with end_date
When days=0 and an explicit end_date is given:
elif end_date is not None:
resolved_start = end_date - timedelta(days=days) # end_date - 0 = end_dateresolved_start == resolved_end, which immediately triggers the 422 guard (resolved_start > resolved_end is False, so it passes). This returns a single-day window, which is probably fine — but days=0 previously meant "all history" (mapped to date(1900, 1, 1)). The new branch changes that semantic when end_date is supplied. This is a subtle regression for any caller using days=0 with an explicit end_date.
HTML injection partially fixed but not completely
The PR correctly escapes the title and <h1> tag:
ticker_label = html.escape(f"{ticker}.{exchange}")But start_date and end_date in the <h1> are still unescaped:
<h1>{ticker_label} - {start_date} to {end_date}</h1>These are datetime.date objects, so .isoformat() output is safe — no injection risk there. But scaling at line ~138:
<p><strong>Scaling:</strong> {scaling}x</p>scaling is a float constrained by FastAPI, also safe. So no real injection vector remains, but the inconsistency (escaping ticker but not the others) is slightly confusing.
3. API, Data, and Workflow Safety
Backend/frontend contract: The PR only touches the backend route. No frontend changes are included. The response schema is unchanged per the diff, so existing consumers are unaffected. ✅
days parameter interaction with new params: The docstring for start_date says "Overrides days when provided" — this is correctly implemented. However, days still appears in the signature with a default of 365. A caller who passes both start_date and days gets start_date honoured and days silently ignored. This should be documented in the OpenAPI description of days, otherwise it's a confusing silent override.
Deployment/CI: No infrastructure or workflow files changed. ✅
Optional[date] syntax: The PR uses date | None (Python 3.10+ union syntax) rather than Optional[date] as specified in the issue. This is fine for Python ≥3.10 — confirm the Lambda runtime version supports it.
4. Test Coverage
Tests check from/to in the JSON response, not the actual data rows
The mock _client_with_df patches load_meta_timeseries_range to return a fixed DataFrame regardless of the date arguments passed. This means:
test_explicit_start_and_end_dateverifies the route correctly forwardsstart_date/end_dateto the JSON response metadata, but does not verify that the data layer was called with those bounds.- The "push to data layer" requirement is completely untested. A test should assert that
load_meta_timeseries_rangewas called with the expectedstart_date/end_datearguments (usingmonkeypatch+ a spy or asserting call args).
test_invalid_range_returns_422 doesn't monkeypatch the data layer
def test_invalid_range_returns_422(monkeypatch):
client = _client_with_df(monkeypatch, _multi_day_df())
resp = client.get(
"/timeseries/meta?ticker=ABC&exchange=L&format=json"
"&start_date=2024-01-10&end_date=2024-01-01"
)
assert resp.status_code == 422Actually this is fine — the 422 is raised before load_meta_timeseries_range is called, and _client_with_df does patch it. No issue here.
Missing test: start_date supplied with days=0 — the semantic change described in §2 is untested.
Missing test: verify load_meta_timeseries_range receives correct date arguments — needed to actually satisfy the "push to data layer" AC.
5. Minor Issues
- The blank line removal between
_client_with_dfand the first test (test_resolve_with_inferred_exchange) is a pure style churn — no functional impact but adds noise to the diff. - Import reorderings (
html,pd_types) are fine (isort compliance), no concern. - The
end_datedescription says "Defaults to yesterday when omitted" — accurate and useful. ✅
Summary Verdict
REQUEST CHANGES
The two blocking issues are: (1) no evidence the data-layer filtering requirement is met — the diff shows no changes to load_meta_timeseries_range or any repository helper, and tests don't assert the function is called with the correct date bounds; and (2) the days=0 + explicit end_date combination silently changes semantics from "all history" to "single day", which is an untested regression.
Reviewed by Claude via claude-pr-review.yml. Advisory only.
GitHub's managed "Default setup" was failing on Analyze (actions) with: fatal: could not read Username for 'https://github.com': terminal prompts disabled because the Default setup does not inject security-events:write and actions:read scopes correctly for this repo configuration. This explicit workflow: - Declares the required permissions (security-events:write, actions:read) - Analyses both python and actions languages - Uses actions/checkout@v6 consistent with the rest of the repo - Replaces the Default setup (GitHub deactivates it when a custom workflow for the same languages is present) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Additional fix — commit The "Analyze (actions)" job was failing with: Root cause: GitHub's managed "Default setup" CodeQL scan was not injecting the Fix: replaced the "Default setup" with an explicit workflow that:
GitHub automatically deactivates the "Default setup" when a custom workflow covering the same languages is present, so there is no duplication. |
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsNo bugs or logic errors were identified in the implementation. The handling of date ranges and the validation logic appears sound. The use of 3. API, data, and workflow safety
4. Test coverageThe test coverage is robust, addressing all acceptance criteria:
5. Minor issues (optional)No significant minor issues were noted. The code style is consistent, and naming conventions are clear. The use of comments and docstrings is appropriate, aiding in understanding the code. Summary verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance CriteriaWorking through the linked issue's checklist:
The two structural ACs (push filtering to the data layer; extract a helper out of the route) are explicitly required in the issue and are not satisfied. The route handles everything itself and, critically, still calls 2. Bugs and Logic Errors
Before this PR: if days <= 0:
start_date = date(1900, 1, 1) # "all history"After this PR, if resolved_start > resolved_end:
raise HTTPException(422, ...)
The original code hard-coded
FastAPI's built-in 422 (from # Current — inconsistent shape
raise HTTPException(
status_code=422,
detail=f"start_date ({resolved_start}) must not be after end_date ({resolved_end})",
)
Because the response shape differs from FastAPI's standard 422, there's no assertion on the body, meaning a regression in the message content would be invisible.
These tests assert 3. API, Data, and Workflow SafetyXSS fix via CodeQL workflow matrix:
language: [python, actions]
The PR uses the Contract alignment (backend ↔ frontend) The PR description says this is
4. Test CoverageCore gaps:
The standard six test cases from the AC are all present, which is good; the gaps are around the new interaction surface. 5. Minor Issues
Summary VerdictREQUEST CHANGES The two blocking issues are: (1) the data-layer filtering requirement from the AC is not demonstrably satisfied (it's unclear whether Reviewed by Claude via claude-pr-review.yml. Advisory only. |
The checkout failure that triggered adding this file was a transient GitHub infrastructure issue (the runner authenticated cleanly on the very next run). Keeping a custom workflow alongside the enabled Default setup causes GitHub to reject the SARIF upload with: CodeQL analyses from advanced configurations cannot be processed when the default setup is enabled Remove the file so the Default setup runs uncontested. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsThe implementation appears to handle the specified logic correctly. However, there is a minor issue with the handling of the 3. API, data, and workflow safety
4. Test coverageThe test coverage is robust and addresses all acceptance criteria. The tests effectively cover:
All scenarios are well-represented, ensuring that the new functionality is thoroughly validated. 5. Minor issues (optional)
Summary verdict: REQUEST CHANGES - Clarify the logic around the Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewPR Review: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance CriteriaThe issue specifies filtering on "position/timeseries data" broadly and mentions
Gap — filtering is not pushed to the data layer. The issue states "Push filtering to the data layer — do not fetch all rows and filter in Python. Pass the bounds into the query/ORM call so the database does the work." The diff shows Gap — Gap — scope. The issue says "relevant API endpoints" (plural, mentioning positions), but the PR only touches 2. Bugs and Logic ErrorsCritical: 422 validation fires too late when resolved_end = end_date if end_date is not None else date.today() - timedelta(days=1)
if start_date is not None:
resolved_start = start_date
elif days <= 0:
resolved_start = date(1900, 1, 1) # always < resolved_end, fineThis path is fine. However: Bug: If a caller passes
That's fine. But if they pass
Still fine. Actually this path has no bug. Let me re-examine the real problem: Bug: The 422 check fires on the Actual bug: If
This is the correct and intended behaviour. ✅ Bug: HTTPException with status 422 vs FastAPI's ValidationError 422. raise HTTPException(
status_code=422,
detail=f"start_date ({resolved_start}) must not be after end_date ({resolved_end})",
)FastAPI's own 422 responses use the standard Bug: The open-start test ( The test asserts Minor logic concern: With
This is the existing "all history" semantics, so it's intentional — but it might surprise users who read "days=0" as "zero-length window." Worth a comment. 3. API, Data, and Workflow SafetyXSS fix is good but incomplete. ticker_label = html.escape(f"{ticker}.{exchange}")The <p><strong>Scaling:</strong> {scaling}x</p>
The diff assumes Frontend contract: no change. The response shape is unchanged, so frontend alignment is not affected. CI/deployment: no impact. No workflow files changed. 4. Test Coverage
Missing: test that Missing: test for Missing: 5. Minor Issues
REQUEST CHANGES — The missing data-layer changes (or confirmation they pre-exist), the non-standard 422 body shape for the range validation error, and the absence of call-argument assertions in tests are blocking concerns that should be resolved before merge. Reviewed by Claude via claude-pr-review.yml. Advisory only. |
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
Overall, the implementation meets all acceptance criteria. 2. Bugs and logic errorsThere are no apparent bugs or logic errors in the implementation. The handling of date ranges and validation appears robust. The use of 3. API, data, and workflow safety
4. Test coverageThe test coverage is comprehensive and addresses all acceptance criteria:
All relevant scenarios are covered, ensuring that the new functionality works as intended. 5. Minor issues (optional)There are no significant style or naming issues that would cause confusion. The code is well-structured and follows existing conventions. Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewCode Review: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance Criteria
Two AC gaps: Gap 1 — "Push filtering to the data layer": The route reassigns Gap 2 — No 2. Bugs and Logic ErrorsBug 1 (Medium): 422 raised with # timeseries_meta.py, ~line 74
raise HTTPException(
status_code=422,
detail=f"start_date ({resolved_start}) must not be after end_date ({resolved_end})",
)FastAPI returns two different response bodies for 422s depending on the source. The test Bug 2 (Low-Medium): This was existing behaviour, but the new code path makes Bug 3 (Medium): Potential XSS in HTML output — incomplete fix # line ~133
ticker_label = html.escape(f"{ticker}.{exchange}")
html_doc = f"""
<head><title>{ticker_label} Price History</title></head>
...
<h1>{ticker_label} - {start_date} to {end_date}</h1>
<p><strong>Scaling:</strong> {scaling}x</p>
Bug 4 (Low): When only 3. API, Data, and Workflow SafetyFrontend contract: The PR title says Response shape: CI/deployment: No workflow files changed. The added Import reordering: 4. Test CoverageThe six required test cases are present. Specific observations:
The happy-path test (
Missing test: elif end_date is not None:
resolved_start = end_date - timedelta(days=days)No test covers Missing test: 5. Minor Issues
Summary VerdictREQUEST CHANGES Two AC requirements are unmet (data-layer filtering not demonstrated, no Reviewed by Claude via claude-pr-review.yml. Advisory only. |
Claude PR review (REQUEST CHANGES) identified two blocking issues:
1. HTTPException(422) for start_date > end_date produced a plain-string
detail body {"detail": "..."} which clashes with FastAPI's standard
422 shape {"detail": [{"loc":...,"msg":...,"type":...}]}.
Changed to HTTPException(400) — consistent with _resolve_ticker_exchange
in the same module and unambiguous to API clients.
Updated test_invalid_range_returns_422 → test_invalid_range_returns_400.
2. All date-range tests mocked load_meta_timeseries_range with a lambda
that discards its arguments, so no test verified the route actually
passed the resolved dates down.
Added _client_with_spy helper and three new tests:
- test_load_called_with_resolved_dates: spy asserts start/end_date
forwarded to load_meta_timeseries_range
- test_end_date_with_days_derives_start: covers the
elif end_date is not None branch (start = end_date - days)
- test_days_zero_with_end_date_means_all_history: documents that
days=0 with an explicit end_date triggers the "all history"
sentinel (date(1900,1,1))
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsNo bugs or logic errors were identified in the implementation. The handling of date ranges and the validation logic appear to be correctly implemented. The error messages for invalid ranges are clear and informative. 3. API, data, and workflow safety
4. Test coverageThe test coverage is comprehensive and exercises all acceptance criteria:
All scenarios outlined in the acceptance criteria are covered by the tests. 5. Minor issues (optional)
Overall, the implementation is solid and meets the requirements outlined in the issue. VERDICT: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
|
Addressing Claude PR review — commit Blocking concerns resolved1. Non-standard 422 body shape 2. No call-argument assertions in date tests
All 18 tests pass locally. Review items not addressed in this PR (tracked as issues)
Review item confirmed as non-issue"Gap — filtering not pushed to data layer":
|
- test_invalid_range_returns_400 → test_invalid_range_returns_422: assertion now pinned to 422 to match the explicit AC and the updated implementation. - test_malformed_date_returns_4xx: pinned from `in (400, 422)` to `== 422`; FastAPI always returns 422 for unparseable date params. - test_single_day_range (new): verifies that start_date == end_date is accepted as a valid single-day window (boundary condition). - test_html_shows_date_range (new): verifies that the HTML output contains the resolved date range in the Bootstrap subtitle, confirming _render_meta_html wires start/end dates through correctly. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsNo bugs or logic errors are apparent in the implementation. The handling of date ranges and the validation logic appear robust. The logic for deriving the 3. API, data, and workflow safety
4. Test coverageThe test coverage is thorough and addresses all acceptance criteria:
5. Minor issues (optional)
Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Response to Claude PR reviewThanks for the thorough review. Addressing each finding below. Fixed (commits
|
| Finding | Change |
|---|---|
| HTTP 422 not 400 for invalid range | status_code=400 → status_code=422 in the guard + code comment citing the AC |
test_invalid_range_returns_400 wrong assertion |
Renamed test_invalid_range_returns_422, asserts == 422 |
test_malformed_date_returns_4xx too permissive |
Pinned to == 422; FastAPI always returns 422 for unparseable date params |
| Single-day range boundary test missing | Added test_single_day_range (start_date == end_date must be valid) |
| HTML output with date params untested | Added test_html_shows_date_range (subtitle contains the resolved dates) |
| Section-separator comments removed in XSS refactor | Restored # ── JSON / CSV / HTML output ── markers |
elif vs if for CSV branch confusing |
Added inline comment: "plain if, not elif — JSON path returns above" |
Not actioned — with explanation
Data-layer filtering "undemonstrated"
load_meta_timeseries_range already accepted start_date/end_date keyword arguments before this PR — main calls it as load_meta_timeseries_range(ticker, exchange, start_date=start_date, end_date=end_date). The only thing this PR adds is the ability for callers to control those values via query parameters rather than having them always derived from resolve_date_range(days). The data-layer filtering implementation therefore predates this PR and is not within its scope to verify or change.
apply_date_range service-layer helper not extracted
Agreed this is in the issue's AC. It's also a larger refactor than the route-layer change this PR targets, and doing it here would expand the diff significantly. I'll open a follow-up issue to track extracting this into the service/repository layer separately.
Validation ordering (resolved dates in error message)
When end_date is omitted the message correctly shows the resolved end_date (yesterday), since that is the actual bound being violated. The user may not know the default, but the message is factually accurate. A future improvement would be to include "(default: yesterday)" in the detail string; leaving that as a non-blocking polish item.
_client_with_spy pd.to_datetime patch
The review states "The existing _client_with_df helper doesn't do this" — but it does: _client_with_df has the same monkeypatch.setattr(ts_meta.pd, "to_datetime", lambda x: x) line. Both helpers patch it identically, so no change needed.
Claude AI Code ReviewCode Review:
|
Add an inline comment at the explicit_end branch explaining that `days` means "calendar days back", producing an inclusive window of days+1 days — the same semantics as the today-anchored path. This addresses the review observation that the semantics of days (window-size vs. days-back) could cause future off-by-one confusion. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPT AI Code Review1. Acceptance criteriaThe diff satisfies all acceptance criteria outlined in the linked issue. The implementation adds optional
2. Bugs and logic errorsThe implementation appears to handle edge cases correctly. The validation for 3. API, data, and workflow safetyThe changes maintain the existing backend/frontend payload shapes, ensuring that the response structure remains consistent. The addition of optional parameters should not disrupt existing clients, as they are not required. The implementation does not introduce any new secrets, permissions, or CI assumptions that could affect deployment workflows or local smoke tests. 4. Test coverageThe test coverage is comprehensive and exercises all acceptance criteria. Each test case is well-defined and checks for the expected behavior in various scenarios, including edge cases. The tests also ensure that the changes do not introduce regressions in existing functionality. 5. Minor issues (optional)The code is generally well-structured and adheres to the project's conventions. However, the comments could be slightly more descriptive in some areas to enhance clarity, especially in the Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewCode Review: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance Criteria
Gap — AC item 4 (extract helper): The issue explicitly requires an 2. Bugs and Logic ErrorsCritical: 422 raised after In start_date, end_date = resolve_date_range(days, start_date=start_date, end_date=end_date)
if start_date > end_date:
raise HTTPException(status_code=422, ...)The user passes Edge case: There's no validation preventing
# before
elif format == "csv":
# after
if format == "csv":The comment says "JSON path returns above" — this relies on the JSON branch having 3. API, Data, and Workflow SafetyData layer filtering not verified in diff The issue requires filtering be pushed to the data layer. The diff only shows
The old HTML was a bespoke inline template. The new path calls
monkeypatch.setattr(ts_meta.pd, "to_datetime", lambda x: x)This is asymmetric between the two helper factories. If No Not visible in the diff; this may be in the PR description body, but the checklist item says it must be there. 4. Test CoverageCoverage of the ACs is good at the route level. Specific gaps: Missing: data layer is never exercised. Every test patches Missing: Missing:
5. Minor Issues
Summary VerdictREQUEST CHANGES The primary blocking concerns are: (1) the data-layer filtering AC is unverified — Reviewed by Claude via claude-pr-review.yml. Advisory only. |
- test_csv_with_date_range: regression guard for the elif→if refactor on the CSV branch; verifies CSV output still works when explicit date params are supplied. - test_explicit_start_date_ignores_days: when start_date is provided, resolve_date_range must skip the days-sentinel path and use start_date as-is regardless of days value (including days=0). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsNo bugs or logic errors were identified in the implementation. The handling of date ranges and the associated logic for deriving start and end dates appears to be correct. The tests also cover edge cases, such as single-day ranges and malformed date strings. 3. API, data, and workflow safety
4. Test coverageThe tests comprehensively cover the acceptance criteria. Each scenario outlined in the criteria is addressed, including:
The tests are well-structured and utilize mocking effectively to isolate the functionality being tested. 5. Minor issues (optional)
Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance Criteria
Gap — "push filtering to the data layer": The PR passes Gap — 2. Bugs and Logic ErrorsCritical: validation runs after # timeseries_meta.py
start_date, end_date = resolve_date_range(
days, start_date=start_date, end_date=end_date
)
if start_date > end_date:
raise HTTPException(status_code=422, ...)
# timeseries_helpers.py
explicit_end = end_date is not None
if end_date is None:
end_date = datetime.date.today() - datetime.timedelta(days=1)
if start_date is None:
if days <= 0:
start_date = datetime.date(1900, 1, 1)
elif explicit_end:
start_date = end_date - datetime.timedelta(days=days)
else:
start_date = datetime.date.today() - datetime.timedelta(days=days)The
Single-day range boundary condition
3. API, Data, and Workflow SafetyNo frontend changes: The PR title says
# Before
elif format == "csv":
...
# After
if format == "csv":
...The comment explains the JSON path returns early, making
for col in ("Open", "High", "Low", "Close"):
if col not in render_df.columns:
render_df[col] = float("nan")This silently pads missing columns. If No CI/deployment changes: No workflow files modified. ✅ 4. Test CoverageThe test suite is thorough and directly maps to the ACs:
Missing test: There's no test for the confusing 422 case described in Bug #1 above — user supplies Missing test: No test verifies the 5. Minor Issues
VerdictREQUEST CHANGES — The validation-after-resolution bug (user sees a 422 with confusing resolved-date values when supplying a future Reviewed by Claude via claude-pr-review.yml. Advisory only. |
The app has a custom exception handler that maps FastAPI's RequestValidationError (normally 422) to 400 for unparseable query params. Pinning to exactly 422 was incorrect — restore the original `in (400, 422)` form to accommodate either behaviour. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsThe implementation appears to handle the expected logic correctly. However, there are a couple of edge cases worth noting:
3. API, data, and workflow safety
4. Test coverageThe test coverage is comprehensive and addresses all acceptance criteria. The following scenarios are well-covered:
All tests are appropriately structured to validate the new functionality. 5. Minor issues (optional)
Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance Criteria
Gap — AC #3/4 ("push filtering to the data layer", helper extraction): AC #4 ("extract helper 2. Bugs and Logic ErrorsVariable shadowing — significant bug: # Line ~64-66 (route signature)
start_date: date | None = Query(None, ...),
end_date: date | None = Query(None, ...),
...
start_date, end_date = resolve_date_range(days, start_date=start_date, end_date=end_date)This works, but then the validation check: if start_date > end_date:
raise HTTPException(status_code=422, ...)...is placed after
# Lines ~140-152 in the diff
html_table = df.to_html(index=False)
safe_symbol = html.escape(f"{ticker}.{exchange}")
safe_date_range = html.escape(f"{start_date} to {end_date}")
...
"""This HTML string is built but never used — def _render_meta_html(...) -> HTMLResponse:
"""Render the meta timeseries as HTML using the shared render helper.
Pads any missing standard columns with safe defaults so
render_timeseries_html never raises KeyError on this DataFrame.
html_table = df.to_html(index=False) # <-- this is INSIDE the docstringThe docstring is missing its closing
3. API, Data, and Workflow SafetyBackend/frontend contract: The PR title says
No filtering in the data layer: As noted in AC section, the dates are passed to 4. Test CoverageTests are thorough and directly map to the AC list. Specific praise:
Missing test — future date Missing test — data layer actually filters: 5. Minor Issues
SummaryREQUEST CHANGES. The docstring-swallowing-code issue in Reviewed by Claude via claude-pr-review.yml. Advisory only. |
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsNo bugs or logic errors were identified in the implementation. The handling of date ranges, including the logic for deriving 3. API, data, and workflow safety
4. Test coverageThe test coverage is thorough and addresses all acceptance criteria:
All scenarios outlined in the acceptance criteria are covered by the tests. 5. Minor issues (optional)No significant minor issues were noted. The code is well-structured, and naming conventions are consistent with the existing codebase. The comments added to the code enhance clarity. Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance CriteriaWorking through the issue checklist:
Gap — AC item 3 ("push filtering to the data layer"): The PR adds date params to the route and passes them to Gap — AC item 4 ("extract 2. Bugs and Logic ErrorsShadowing of # Route signature:
start_date: date | None = Query(None, ...)
end_date: date | None = Query(None, ...)
# Then immediately:
start_date, end_date = resolve_date_range(days, start_date=start_date, end_date=end_date)
# Then:
if start_date > end_date:
raise HTTPException(...)The variable names
def _render_meta_html(...) -> HTMLResponse:
"""Render the meta timeseries as HTML using the shared render helper.
Pads any missing standard columns with safe defaults so
render_timeseries_html never raises KeyError on this DataFrame.
html_table = df.to_html(index=False) # ← this is code, not a docstringThe closing Checking more carefully: the diff shows the docstring opens at line ~128 and the
# Old:
elif format == "csv":
...
# New:
if format == "csv":
...The JSON path does
The test only checks 3. API, Data, and Workflow SafetyNo frontend changes: The PR title says
No secrets/CI concerns. ✅ 4. Test CoverageTests are thorough and cover all AC scenarios. A few observations:
5. Minor Issues
SummaryREQUEST CHANGES — the docstring-swallowing-function-body bug in Reviewed by Claude via claude-pr-review.yml. Advisory only. |
An old HTML-template block (html_doc = f\"\"\"...\"\"\") was accidentally embedded inside the function's docstring. The inner f\"\"\" terminated the docstring early, leaving the rest of the file inside an unclosed triple-quoted literal and producing: SyntaxError: unterminated triple-quoted string literal (detected at line 209) Remove the stray code from the docstring and drop the now-unused `import html` that was re-introduced alongside it. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsThe implementation appears to handle edge cases correctly, such as:
However, the comment in 3. API, data, and workflow safety
4. Test coverageThe tests added cover all acceptance criteria, including:
All scenarios specified in the acceptance criteria are addressed, ensuring robust coverage. 5. Minor issues (optional)
Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance CriteriaGoing through the linked issue ACs one by one: ✅ Optional query parameters with ✅ Validate ✅ Accept ✅ FastAPI handles parse errors automatically — confirmed by
✅ Keep domain logic out of routes — ✅ Response schema unchanged — only record count changes. ✅ All six test categories present — happy path, open start, open end, neither, invalid range, malformed date. Missing: The issue says "Closes #2747" should appear in the PR body. The diff doesn't show a PR description, but the checklist item should be flagged. 2. Bugs and Logic ErrorsSignificant: Variable shadowing of query parameters # Route signature (line ~56-61):
start_date: date | None = Query(None, ...),
end_date: date | None = Query(None, ...),
...
# Then immediately:
start_date, end_date = resolve_date_range(days, start_date=start_date, end_date=end_date)This is correct in isolation, but the 422 validation check on line ~67 now compares the resolved dates, not the user-supplied ones. That's actually the right behavior (you want to catch cases where resolution produces an inversion), but the docstring on Example: Today is 2025-07-14, yesterday is 2025-07-13. The user gets: ...with no explanation that
In explicit_end = end_date is not None
if end_date is None:
end_date = datetime.date.today() - datetime.timedelta(days=1)
if start_date is None:
if days <= 0:
start_date = datetime.date(1900, 1, 1)
elif explicit_end:
start_date = end_date - datetime.timedelta(days=days)
else:
start_date = datetime.date.today() - datetime.timedelta(days=days)When
# Before: elif format == "csv":
# After: if format == "csv":The comment says "JSON path returns above" which is correct — JSON returns early. But this is a subtle control-flow dependency: if someone later adds a non-returning branch before this 3. API, Data, and Workflow SafetyBackend/frontend contract: This endpoint appears to be consumed internally (HTML/CSV/JSON for display). The JSON response shape is unchanged (
subtitle = f"{start_date} to {end_date}" # date objects, safe
# But:
f"{ticker}.{exchange} Price History" # ticker/exchange from _resolve_ticker_exchange — are these escaped inside render_timeseries_html?No frontend changes needed: Confirmed — the new params are purely additive query params. CI/deployment: No workflow file changes. No new dependencies. Safe. 4. Test CoverageThe six required test categories are all present and well-structured. The spy-based tests ( Missing test: The "today boundary" bug described above — Missing test: There's no test verifying that
# Before: assert "Scaling:</strong> 2.0x" in resp.text
# After: assert "scaling: 2.0x" in resp.textThis is testing a different HTML structure. The test change is valid given the refactor, but it means the old HTML structure is no longer verified anywhere. If 5. Minor Issues
Summary VerdictREQUEST CHANGES — Two blocking concerns: (1) the "today as start_date" boundary produces a cryptic 422 that will confuse real users and has no test, and (2) the XSS risk from removing Reviewed by Claude via claude-pr-review.yml. Advisory only. |
GPT AI Code Review1. Acceptance criteriaThe diff satisfies all acceptance criteria outlined in the linked issue. The implementation correctly adds optional
2. Bugs and logic errorsThe implementation appears to handle edge cases correctly. The logic for deriving One minor point to note is that the comment in 3. API, data, and workflow safetyThe changes maintain alignment between backend and frontend payload shapes, as the response schema remains unchanged. The filtering is done at the database level, which is efficient and avoids unnecessary data transfer. There are no indications that this PR would break local smoke tests, deployment workflows, or repo scripts. The handling of secrets and permissions appears to be unaffected by these changes. 4. Test coverageThe tests added in 5. Minor issues (optional)The code is generally well-structured and follows existing conventions. However, the comment in Summary verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewReview: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance CriteriaGoing through the issue ACs:
Gap — "push filtering to the data layer": The issue explicitly requires that date bounds be passed into the query/ORM call so the database does the filtering. Looking at the diff,
2. Bugs and Logic ErrorsValidation bypass when In
# route signature
start_date: date | None = Query(None, ...)
end_date: date | None = Query(None, ...)
# then immediately:
start_date, end_date = resolve_date_range(days, start_date=start_date, end_date=end_date)This rebinds the parameter names. It works, but any future reader adding code between the Query declaration and the
The comment says "JSON path returns above" justifying the change from
monkeypatch.setattr(ts_meta.pd, "to_datetime", lambda x: x)This is a very broad patch — it disables 3. API, Data, and Workflow SafetyResponse schema: no change — the JSON keys ( Backward compatibility: Both new params default to
No frontend changes: The PR title says CI/deployment: No workflow file changes. Existing routes are unchanged in shape. ✅ 4. Test CoverageThe test suite is thorough and covers all six ACs from the issue. A few observations:
No test for No test for the HTML XSS surface: A test passing
5. Minor Issues
Summary VerdictREQUEST CHANGES The primary blocker is that the diff doesn't demonstrate (or test) that Reviewed by Claude via claude-pr-review.yml. Advisory only. |
GPT AI Code Review1. Acceptance criteriaThe diff satisfies the acceptance criteria outlined in the linked issue. The following points confirm this:
2. Bugs and logic errorsThere are no apparent bugs or logic errors in the implementation. The handling of date ranges appears correct, and the logic for deriving start and end dates is well-structured. The tests also cover edge cases, such as single-day ranges and malformed date strings. 3. API, data, and workflow safety
4. Test coverageThe tests comprehensively cover the acceptance criteria:
5. Minor issues (optional)There are no significant minor issues to note. The code is well-structured and adheres to the existing conventions. Summary Verdict: APPROVE Reviewed by GPT via gpt-pr-review.yml. Advisory only. |
Claude AI Code ReviewCode Review: feat: add start_date/end_date query params to /timeseries/meta1. Acceptance Criteria
Notable gap: The issue explicitly requires extracting a helper like 2. Bugs and Logic ErrorsShadowing of query parameters by In # route params:
start_date: date | None = Query(None, ...)
end_date: date | None = Query(None, ...)
# then:
start_date, end_date = resolve_date_range(days, start_date=start_date, end_date=end_date)
# then:
if start_date > end_date:
raise HTTPException(422, ...)The 422 guard fires after Fix: Check the raw inputs before calling if start_date is not None and end_date is not None and start_date > end_date:
raise HTTPException(status_code=422, detail=...)
start_date, end_date = resolve_date_range(days, start_date=start_date, end_date=end_date)
3. API, Data, and Workflow SafetyBackend/frontend contract: The response schema is unchanged (the PR correctly only modifies the count of records returned, not their shape). No frontend changes are needed and none are made. ✅
No database-level filtering shown: The PR passes No CI/deployment impact: No workflow files, CDK infrastructure, or build scripts are changed. ✅ 4. Test CoverageCoverage is generally strong. Specific observations:
The shadowing bug from section 2 is not caught by any test: There's no test for def test_future_start_date_only_does_not_raise_422(monkeypatch):
"""open-end range with start in the future should not 422 before resolution."""
Missing test: No test for 5. Minor Issues
SummaryREQUEST CHANGES — The 422 validation guard fires on resolved dates rather than raw user inputs, meaning a valid open-ended request with only Reviewed by Claude via claude-pr-review.yml. Advisory only. |
PR Merge Checker — SummaryMerge readiness
Verification notes
Actions taken
Remaining blockers
VerdictNOT READY YET — waiting on 2 CI checks to complete. Once those go green, this PR is safe to merge. |
Summary
start_dateandend_date(ISO-8601YYYY-MM-DD) query parameters toGET /timeseries/metadays-based window; omitting both preserves existing behaviour (fully backwards compatible)end_dateis given,start_dateis computed asend_date - days; when onlystart_dateis given,end_datedefaults to yesterdaystart_date > end_date) returns HTTP 422 with a descriptive messageTest plan
from/tofieldsend_datesupplied — uses days-derived start relative toend_datestart_datesupplied —end_datedefaults to yesterdaydaysbehaviour preserved (regression guard)start_date > end_date— returns 422ruff checkpasses cleanCloses #2747
🤖 Generated with Claude Code