Skip to content

feat(b2b_learner_records): bounded date-range filters for partitioned backfills - #72

Merged
blarghmatey merged 3 commits into
mainfrom
b2b-learner-records-date-range-filters
Sep 25, 2026
Merged

blarghmatey merged 3 commits into
mainfrom
b2b-learner-records-date-range-filters

Conversation

@blarghmatey

Copy link
Copy Markdown
Member

What are the relevant tickets?

N/A

Description (What does it do?)

Add updated_before (paired with updated_since) so partners can pull bounded [since, before) windows instead of only an open-ended sync, for partitioned backfills. Add contract_is_active, courserun_id, and courserun_starts_after/courserun_starts_before filters to /courses.

Implementation details

updated_since alone only supports "everything new since X." Adding updated_before lets a partner request [since, before) windows, so a backfill can be split into non-overlapping partitions across workers or replayed one window at a time. Applies to /learners and /enrollments.

/courses gets a separate kind of range filter: mv_b2b_contract_courserun carries no record_updated_on, because dim_contract is a type-1 dimension with no change-tracking column upstream. So the catalog can't support a real updated_since/updated_before cursor yet (that would need an ol-data-platform change). courserun_starts_after/courserun_starts_before is a workable substitute in the meantime, partitioning the catalog by course-run start date, which is its natural grain.

courserun_start_on is nullable (self-paced/unscheduled runs), and SQL >=/< are never true against NULL, so those runs match neither bound and fall outside every partition window. Documented on the endpoint and in queries.courses(): a partitioned sync needs one unfiltered pass to pick them up. _window_predicates() centralizes the [since, before) predicate pair so _shared_predicates() (record_updated_on) and courses() (courserun_start_on) share one implementation instead of two.

How can this be tested?

ruff check, ruff format --check, mypy src, bin/generate-openapi-spec --check, and the full pytest suite (284 tests, including 2 new ones covering these filters) all pass locally. No manual/live StarRocks testing — the test suite stubs the DB pool.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EtEZ67aBRDvprCjGiDBeEs

@github-actions

Copy link
Copy Markdown

OpenAPI Changes

Show/hide changes
## Changes for b2b_dashboard.yaml:
No changes detected

## Changes for b2b_learner_records.yaml:
6 changes: 0 error, 0 warning, 6 info
info	[new-optional-request-parameter] at head/openapi/specs/b2b_learner_records.yaml
	in API GET /api/v1/learner-records/organizations/{organization_id}/courses
		added the new optional `query` request parameter `contract_is_active`

info	[new-optional-request-parameter] at head/openapi/specs/b2b_learner_records.yaml
	in API GET /api/v1/learner-records/organizations/{organization_id}/courses
		added the new optional `query` request parameter `courserun_id`

info	[new-optional-request-parameter] at head/openapi/specs/b2b_learner_records.yaml
	in API GET /api/v1/learner-records/organizations/{organization_id}/courses
		added the new optional `query` request parameter `courserun_starts_after`

info	[new-optional-request-parameter] at head/openapi/specs/b2b_learner_records.yaml
	in API GET /api/v1/learner-records/organizations/{organization_id}/courses
		added the new optional `query` request parameter `courserun_starts_before`

info	[new-optional-request-parameter] at head/openapi/specs/b2b_learner_records.yaml
	in API GET /api/v1/learner-records/organizations/{organization_id}/enrollments
		added the new optional `query` request parameter `updated_before`

info	[new-optional-request-parameter] at head/openapi/specs/b2b_learner_records.yaml
	in API GET /api/v1/learner-records/organizations/{organization_id}/learners
		added the new optional `query` request parameter `updated_before`



Unexpected changes? Ensure your branch is up-to-date with main (consider rebasing).

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

Fractional exclusive upper bounds currently exclude records that fall before the requested instant.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Adds bounded synchronization windows and course catalog filtering to the learner-records API.

Changes:

  • Adds exclusive updated_before filters.
  • Adds contract, course-run, and start-date filters.
  • Updates tests and generated OpenAPI documentation.
File Description
tests/​test_learner_records.py Tests new filter bindings.
src/​.../​routers/​organizations.py Exposes new query parameters.
src/​.../​queries.py Builds range and catalog predicates.
openapi/​specs/​b2b_learner_records.yaml Publishes the new parameters.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/ol_analytics_api/tenants/b2b_learner_records/queries.py Outdated
blarghmatey added a commit that referenced this pull request Sep 25, 2026
_cursor_value() floored every bound to whole seconds. That is safe for an
inclusive lower bound (>=): it can only widen the window backward, so a
record gets re-sent, never skipped. Applied to the exclusive upper bound
(<) it does the opposite: updated_before=06:15:00.500 floored to
06:15:00, so a row stored at 06:15:00.250, genuinely before the requested
instant, failed the predicate and was silently dropped rather than
deferred to the next window.

_cursor_value(round_up=True) rounds a fractional exclusive bound up to the
next whole second instead, so `<` matches through that second and the
same row is re-sent by whichever window starts there. Applies to
updated_before and courserun_starts_before via _window_predicates().

Found by copilot-pull-request-reviewer on #72.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EtEZ67aBRDvprCjGiDBeEs
blarghmatey and others added 3 commits September 25, 2026 14:33
… backfills

updated_since alone only supports an open-ended "everything new" sync. Add
updated_before so a partner can request [since, before) windows, letting a
backfill be split into non-overlapping partitions across workers or replayed
one window at a time. Applies to /learners and /enrollments.

Also add contract_is_active, courserun_id, and courserun_starts_after/before
to /courses. mv_b2b_contract_courserun carries no record_updated_on (dim_contract
is a type-1 dimension with no change-tracking column), so the catalog can't yet
support a real updated_since/updated_before cursor; courserun_starts_after/before
partitions it by run start date instead, which is the catalog's natural grain.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EtEZ67aBRDvprCjGiDBeEs
…nscheduled runs

courserun_start_on is nullable, and SQL >= / < are never true against NULL, so
a self-paced run matches neither courserun_starts_after nor
courserun_starts_before. A partitioned backfill built only from those windows
would never see it. Document the gap on the endpoint and in queries.courses(),
and extract the [since, before) predicate pair courses() and
_shared_predicates() both built independently into one _window_predicates()
helper, parameterized by column.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EtEZ67aBRDvprCjGiDBeEs
_cursor_value() floored every bound to whole seconds. That is safe for an
inclusive lower bound (>=): it can only widen the window backward, so a
record gets re-sent, never skipped. Applied to the exclusive upper bound
(<) it does the opposite: updated_before=06:15:00.500 floored to
06:15:00, so a row stored at 06:15:00.250, genuinely before the requested
instant, failed the predicate and was silently dropped rather than
deferred to the next window.

_cursor_value(round_up=True) rounds a fractional exclusive bound up to the
next whole second instead, so `<` matches through that second and the
same row is re-sent by whichever window starts there. Applies to
updated_before and courserun_starts_before via _window_predicates().

Found by copilot-pull-request-reviewer on #72.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EtEZ67aBRDvprCjGiDBeEs
@blarghmatey
blarghmatey force-pushed the b2b-learner-records-date-range-filters branch from b6544ca to f060381 Compare September 25, 2026 18:33
@blarghmatey
blarghmatey merged commit bd00c1e into main Sep 25, 2026
6 checks passed
@blarghmatey
blarghmatey deleted the b2b-learner-records-date-range-filters branch September 25, 2026 18:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants