Conversation
There was a problem hiding this comment.
Pull request overview
This PR updates the marts__micromasters_dedp_exam_grades mart so the MITxOnline branch sources semester and passing_grade from the dimensional model dim_course_run (intended to centralize and de-duplicate that derivation).
Changes:
- Add a
dim_course_run-backed CTE for MITxOnlinesemester/passing_grade. - Replace MITxOnline output columns to read from
dim_course_runinstead of the intermediate proctored-exam grades model. - Keep the pure-MicroMasters branch unchanged (still sourcing those fields directly from the MicroMasters intermediate model).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
d64395e to
1ba8823
Compare
🔎 ol-dbt impact — column-level blast radius✅ No column-level downstream impact detected for the changed models. Posted by |
b61973a to
f0015bf
Compare
dim_course_run.semester was NULL for MITxOnline course runs whose proctored exam is an embedded unit in the course rather than a dedicated MicroMasters exam-run course object: those runs have no micromasters_examruns match, so er.examrun_semester is NULL. #2319 moved this field into dim_course_run but populated it only from the exam-run join, dropping the fallback branch that int__mitxonline__proctored_exam_grades.sql had. That regression blocks #2088 / PR #2403, which reads semester from dim_course_run: 72 of the 84 affected course runs came through with no semester at all. Restore the original derivation with coalesce(..., cr.courserun_tag). Validated on dev_local against production-shaped data: a full-refresh rebuild of dim_course_run and marts__micromasters_dedp_exam_grades diffs row-for-row clean against the pre-migration baseline snapshot -- 20908/20908 rows, all 12 columns identical, zero NULL semesters on either side. dbt tests for both models pass, including the dbt_expectations compound-uniqueness check on the mart, so the coalesce does not fan out rows. Refs #2088, #2319 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s-db macro
dim_course_run.sql was the last model still calling regexp_like() raw.
Every other call site already dispatches through the macro in
macros/cross_db_functions.sql, which maps to regexp_matches() on DuckDB;
DuckDB has no regexp_like function, so the raw call makes the model
unbuildable on the dev_local target:
Catalog Error: Scalar Function with name regexp_like does not exist!
That blocks local validation of #2088 / PR #2403, which reads
dim_course_run. The macro is already on main -- this is a call-site
consistency fix, not a new dependency, and it is independent of the
in-flight to_iso8601 migration in PR #2600.
Refs #2088
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The semester fallback currently populates every unmatched MITxOnline course run, contrary to the documented exam-only contract.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 2/3 changed files
- Comments generated: 1
- Review effort level: Balanced
f0015bf to
53a4932
Compare
dim_course_run.semester was NULL for MITxOnline course runs whose proctored exam is an embedded unit in the course rather than a dedicated MicroMasters exam-run course object: those runs have no micromasters_examruns match, so er.examrun_semester is NULL. #2319 moved this field into dim_course_run but populated it only from the exam-run join, dropping the fallback branch that int__mitxonline__proctored_exam_grades.sql had. That regression blocks #2088 / PR #2403, which reads semester from dim_course_run: 72 of the 84 affected course runs came through with no semester at all. Restore the original derivation with coalesce(..., cr.courserun_tag). Validated on dev_local against production-shaped data: a full-refresh rebuild of dim_course_run and marts__micromasters_dedp_exam_grades diffs row-for-row clean against the pre-migration baseline snapshot -- 20908/20908 rows, all 12 columns identical, zero NULL semesters on either side. dbt tests for both models pass, including the dbt_expectations compound-uniqueness check on the mart, so the coalesce does not fan out rows. Refs #2088, #2319 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…s-db macro
dim_course_run.sql was the last model still calling regexp_like() raw.
Every other call site already dispatches through the macro in
macros/cross_db_functions.sql, which maps to regexp_matches() on DuckDB;
DuckDB has no regexp_like function, so the raw call makes the model
unbuildable on the dev_local target:
Catalog Error: Scalar Function with name regexp_like does not exist!
That blocks local validation of #2088 / PR #2403, which reads
dim_course_run. The macro is already on main -- this is a call-site
consistency fix, not a new dependency, and it is independent of the
in-flight to_iso8601 migration in PR #2600.
Refs #2088
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reviewer input wanted:
|
Copilot review on #2659 found five overclaims. All five are valid. - The incremental trap was framed as "an unchanged key set makes the run a no-op". dbt does execute the model SQL; what varies is which rows the model's own is_incremental() predicate reselects. Reframed around the predicate, with the two real failure modes (excluded rows keep old-code values; a predicate that matches everything yields a true no-op) and the SCD2 consequence that dim_course_run appends a second generation rather than replacing the first. Stopped asserting a root cause for #2403's 0.11s no-op that was never established — the lesson is not to reason from the predicate at all. - "Both sides read the identical polluted source and the duplication cancels" is false exactly where a #2072 migration re-points ref(), which is every migration PR. override_ref resolves each unbuilt ref() to its own glue__ view with independent __dbt_tmp pollution, and joins/filters mean the two do not offset. Added: build the divergent upstreams locally, or label the result unverified source noise rather than accepting/rejecting on the registration artifact the section warns about. - Step 3 used `dbt build`, contradicting the cautious-test section below it and risking an unmaterialized comparison side when a test fails. Now `dbt run`. - "Row-count delta 0 means the grain is intact" — dropped rows offset by duplicates net to zero too. Now count parity, with a count(*) = count(distinct key) check on both relations. - Step 6's distinct-pair assertion cancels duplication but not rows a polluted view is missing; scoped accordingly. Also propagated the incremental correction to ol-dbt-local-dev, including its two worked examples, which both rebuilt without --full-refresh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…e tag fallback The description claimed "Null for all other platforms and non-exam MITxOnline course runs". The coalesce this PR adds makes the second half false: measured on dev_local, semester is populated for 4,513 of 4,513 mitxonline is_current rows, 4,501 of them via the courserun_tag fallback rather than a MicroMasters exam-run match. The description was not a contract this PR violates. It was added in ac99b17 (#2319), the same commit that added the column, and it described that commit's implementation — which populated semester only from micromasters_examruns and silently dropped the courserun_tag fallback that int__mitxonline__proctored_exam_grades line 66 had been applying all along. So the documentation recorded an implementation gap as if it were intended behaviour. This PR restores the original derivation; the description now matches what the column actually contains. "Null for all other platforms" is unchanged and still true — edxorg, mitxpro, residential and bootcamps each set semester to null explicitly, and all four measure 0% populated. Note passing_grade's near-identical wording is deliberately left alone: it takes no coalesce, because the pre-migration model's second branch emitted `null as proctoredexamgrade_passing_grade`. Its description remains accurate. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
f3909a6 to
393f026
Compare
…ve a proctored exam
The courserun_tag fallback added earlier on this branch was applied to every
MITxOnline course run, because int__mitxonline__course_runs is every MITxOnline
course run and there was no exam predicate. That populated semester for 4,513 of
4,513 current MITxOnline runs where only 12 had a value before — including ~2,730
UAI partner cohorts and other runs with no proctored exam at all, for which a term
label is meaningless. Over half those new values were not even clean term
identifiers (3T2026_AGCO_C1, 2013_Spring, T12026).
int__mitxonline__proctored_exam_grades already had the right predicate: its
exam_unit_grades CTE identifies embedded proctored exams by a course structure
block titled 'proctored exam'. Reusing it against int__mitxonline__course_structure
(a pure structure model — no grades dependency, no cycle) narrows the fallback to
exactly the runs it was meant for.
Measured on dev_local:
semester populated 4,513 -> 100 (97 clean terms, 3 suffixed '1T2022a')
left null 0 -> 4,413
other platforms 0 populated (unchanged)
marts__micromasters_dedp_exam_grades is unaffected: all 84 course runs it consumes
fall inside the 100. Whole-row symmetric EXCEPT ALL against the pre-migration mart
built from one registration in one invocation returns 0 rows in both directions
across all 12 columns, and semester is still 0% null on both sides. So this keeps
the property that made the migration safe while dropping the over-broad write.
Also shrinks the one-time SCD2 re-version on deploy from 4,501 rows to ~100, since
semester participates in the change-detection predicate.
Adds assert_dim_course_run_semester_covers_proctored_exam_runs. The gate is a live
lookup against course structure rather than a static property, so if structure data
stops reporting the block for a run that genuinely has proctored exam grades,
semester would silently go null and the mart would quietly lose values — the same
shape as the 8,137-null regression reported on this PR in July. Verified the test
actually fires: stubbing the gate predicate fails it with 72 results, the exact
count of affected runs; restored, it passes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n PRs The two existing dbt skills drive the tools (`ol-dbt-local-dev` for register/build, `ol-dbt-fast-validation` for validate/impact/diff) but neither answers the question a migration PR actually turns on: is the data the same? This adds the acceptance procedure, derived from validating #2403. The load-bearing part is which local numbers are trustworthy. A fresh register of staging/intermediate/dimensional on 2026-09-09 still put 624 of 640 non-raw Glue views on `__dbt_tmp` metadata locations, which return duplicated and partially-missing rows while the build succeeds. So an absolute row count or fill rate read through a `glue__` view is not evidence, and several have already been quoted in PR bodies as though they were. A difference between two locally built sides of the SAME registration is evidence, because the pollution cancels. The skill is organised around that distinction: register once, build both sides in one invocation, compare. Also records three traps that each cost real time: - `--full-refresh` is mandatory when the model under test is incremental. #2403's own documented test command omits it, and `dim_course_run` therefore merged in 0.11s without re-deriving the column under test. This contradicts `ol-dbt-local-dev`'s "prefer incremental" rule, which is right for iterating and wrong for validating; the new skill says so explicitly rather than silently disagreeing. - The per-column multiset diff needs no join key at all and localises a difference to one column in a single pass. - A column unpopulated on both sides is unverified, not passing. Registers the skill in agent-config.toml and adds a `migration` profile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… test-selection traps Follow-up to the initial draft, from validating PR #2403 end to end. Amends `ol-dbt-local-dev`'s rule rather than leaving the new skill to contradict it. The old wording — "reserve --full-refresh for when the incremental state is stale or wrong" — cannot fire, because the failure mode it needs to catch is a state that is perfectly valid and merely does not reflect your new code. An incremental run whose key set is unchanged is a no-op that still reports OK, so a changed expression is never re-evaluated and you read the old values believing they are new. #2403's own documented test command hit exactly this: dim_course_run merged in 0.11s and never re-derived the column under test, for two months. The rule is now framed as iterating vs concluding, which is the distinction that actually decides it. Adds two traps neither existing skill covers: - ~/.ol-dbt/local.duckdb is shared by every worktree and session on the machine. A concurrent session rebuilt dim_course_run from main mid-validation and semester went from 4,513/4,513 populated to 12/4,513 with no warning from either side. This is the second independent reason to materialize both comparison sides in ONE invocation: the #2403 result survived only because both marts were already physical tables. - dbt defaults to --indirect-selection=eager, which pulls in relationships_* tests owned by other models and compares your local build against production Glue views. Measured 20 tests eager vs 11 cautious on #2403's two models; the 9 difference were all cross-model, including a 42.7M-row tfact_grade scan and every failure the PR body documented as expected. Also records why expected-failure counts do not belong in a PR body — #2403 documented 87/74/13 and the same tests produced 861/178/150 plus an undocumented fourth. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot review on #2659 found five overclaims. All five are valid. - The incremental trap was framed as "an unchanged key set makes the run a no-op". dbt does execute the model SQL; what varies is which rows the model's own is_incremental() predicate reselects. Reframed around the predicate, with the two real failure modes (excluded rows keep old-code values; a predicate that matches everything yields a true no-op) and the SCD2 consequence that dim_course_run appends a second generation rather than replacing the first. Stopped asserting a root cause for #2403's 0.11s no-op that was never established — the lesson is not to reason from the predicate at all. - "Both sides read the identical polluted source and the duplication cancels" is false exactly where a #2072 migration re-points ref(), which is every migration PR. override_ref resolves each unbuilt ref() to its own glue__ view with independent __dbt_tmp pollution, and joins/filters mean the two do not offset. Added: build the divergent upstreams locally, or label the result unverified source noise rather than accepting/rejecting on the registration artifact the section warns about. - Step 3 used `dbt build`, contradicting the cautious-test section below it and risking an unmaterialized comparison side when a test fails. Now `dbt run`. - "Row-count delta 0 means the grain is intact" — dropped rows offset by duplicates net to zero too. Now count parity, with a count(*) = count(distinct key) check on both relations. - Step 6's distinct-pair assertion cancels duplication but not rows a polluted view is missing; scoped accordingly. Also propagated the incremental correction to ol-dbt-local-dev, including its two worked examples, which both rebuilt without --full-refresh. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…is tuned an order of magnitude too loose ol-dbt-local-dev told you to register, and `list-sources` warns when the registry is ">1 day old". Both understate the problem. `register` stores the Iceberg metadata_location Glue reports at that instant, and for a dbt-built table Glue routinely points the canonical name at a __dbt_tmp directory that the next production materialization swaps and deletes. Measured while validating #2403: a registry refreshed 60 MINUTES earlier failed with HTTP 404 reading the metadata JSON for int__micromasters__dedp_proctored_exam_grades, killing two mart models mid-validation. Re-registering only the intermediate layer (41 pointers moved, 118 unchanged) fixed it. Re-registering the same three layers 26 hours later moved another 336 pointers — 184 staging, 108 intermediate, 44 dimensional — which is the churn rate the one-day threshold is failing to track. The 404 is the lucky shape. When the __dbt_tmp directory still exists but holds a mid-build snapshot, the view returns duplicated or partial rows and nothing fails at all: a clean run and wrong numbers, which is the dangerous case when those numbers are about to be quoted in a PR. So the guidance is unconditional re-registration immediately before a build, not a reaction to a visible error. #2660 stopped __dbt_tmp tables being registered as sources in their own right but deliberately scoped out canonical names pointing at __dbt_tmp locations (621/636 dbt-built tables at last count); that remains tracked separately. Until it lands, freshness is the caller's responsibility, so the skill now says so. Verified the three documented register commands run verbatim from a clean worktree (0 errors each) and pre-commit passes on the file. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ical table is immune Fourth Copilot round. Both findings valid, both verified before fixing. The ref-discovery command I added last round only matched single-quoted ref(). Measured: 13 double-quoted occurrences across 5 models, THREE OF THEM IN reporting/ -- the layer #2072 migrates. On reporting/data_detail_problems.sql the shipped pattern returns zero matches, so the diff shows no divergent refs, the reader omits the upstreams, and the comparison is uncontrolled. It fails silently and in the direction that looks clean, which is the worst shape for this particular command. Now a char-class pattern (portable -- a backreference errors under ugrep) that also strips quotes and the ref() wrapper, so it compares bare model names and re-quoting a ref between versions cannot read as a false divergence. Verified verbatim: finds the double-quoted refs, still surfaces the real divergence on the ac99b17 -> main dim_course_run case, clean across 60 models. "Table-materialized output is immune once written" contradicted this section's own opening sentence, which says another session's dbt run will overwrite your tables. It will. A physical table is immune to POINTER ROT, not to another dbt run. The #2403 marts survived because the concurrent session rebuilt dim_course_run, their upstream, and never selected the marts -- targeting, not immunity. Replaced with that distinction plus the operational requirement: confirm exclusive use of dev_local and measure immediately, since one invocation narrows the window but is not atomic. Added real isolation as the option when a result will be published -- HOME=/tmp/iso-validation redirects dev_local to a private DuckDB file (verified; the 19GB warehouse is untouched), at the cost of a full re-register since that database starts empty. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
The regression test can miss absent dimension rows and allows up to ten affected runs without failing.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/5 changed files
- Comments generated: 2
- Review effort level: Balanced
Addresses two Copilot findings on the new singular test. The project sets `+error_if: ">10"` for all tests (dbt_project.yml), and this query returns one row per affected course run — so a regression touching up to ten runs would have downgraded to a warning, which is exactly the silent failure the test exists to catch. Override error_if/warn_if to `!= 0` locally. Switch the inner join to a left join: a proctored-exam run with no current dim_course_run row at all does the same downstream damage as one with a NULL semester, because the mart left-joins the dimension. The inner join dropped precisely those rows and passed. Verified on production Trino: 79 proctored-exam runs, all with a semester, 0 with no current dimension row — so the left join closes the hole without changing today's result. Test passes against a locally built dim_course_run. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🔵 Needs a closer look
The fallback gate currently includes historical course-structure snapshots instead of only the latest structure.
Review details
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/ol_dbt/models/dimensional/dim_course_run.sql:56
int__mitxonline__course_structureretains every retrieval and marks only the newest snapshot withcoursestructure_is_latest(seeint__mitxonline__course_structure.sql:7-12,55-60). Without filtering here, a block that existed only in an old snapshot keeps this run in the fallback forever, sosemesterdoes not become null when the current structure stops reporting the proctored-exam unit as the new comments and regression test specify. Restrict the gate to the latest structure snapshot.
- Files reviewed: 4/5 changed files
- Comments generated: 0 new
- Review effort level: Balanced
int__mitxonline__course_structure retains every retrieval, so the unfiltered gate matched a 'proctored exam' block title from any historical snapshot. A run whose exam block was renamed or removed stayed in the fallback forever, which contradicts the regression test's stated contract. Mirror what the upstream actually does. int__mitxonline__proctored_exam_grades reaches course structure through int__mitxonline__courserun_subsection_grades, which dedups to the newest snapshot per (course run, block) and matches the title on that. Reproduce the same dedup rather than filtering on coursestructure_is_latest: is_latest keeps only blocks present in the newest whole-course retrieval, which drops runs whose exam block has since left the structure while their graded attempts remain. Measured on production 2026-09-15 against the 79 course runs that have proctored exam grades with a semester: gate as shipped (any snapshot) 107 runs, misses 0 of the 79 coursestructure_is_latest 105 runs, misses 1 of the 79 newest snapshot per block (this) 106 runs, misses 0 of the 79 The run is_latest would have dropped is course-v1:MITxT+14.310GZ+3T2024, which has exam grades and a semester the pre-migration mart populated. The run this change removes is course-v1:MITxT+14.73GZ+1T2025, which has no proctored exam grades at all, so no mart output changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Re: Copilot's "fallback gate includes historical course-structure snapshots" — the diagnosis is correct and is fixed in a01101b. The recommended remedy was not, and I measured before applying it. The diagnosis holds
It also meant my own code comment was false: it said we reuse The recommended fix would have caused a regression
row_number() over (partition by courserun_readable_id, coursestructure_block_id
order by coursestructure_retrieved_at desc) as row_num
...
where course_structure.row_num = 1Those differ precisely for a block that has left the structure: per-block dedup still returns its most recent snapshot,
The run What landedMirrored the per-block dedup. The only run removed is Verified against a locally built
|
There was a problem hiding this comment.
🟡 Changes recommended
The regression test derives its expected rows through the same course-structure predicate, so it cannot detect the documented source-drift failure.
Get a fresh assessment by requesting another Copilot review.
Review details
- Files reviewed: 4/5 changed files
- Comments generated: 1
- Review effort level: Balanced
…rift The test's header said it fires when course structure stops reporting the 'proctored exam' block. It does not, and cannot: dim_course_run's gate and int__mitxonline__proctored_exam_grades' exam_unit_grades CTE read the same predicate, so drift removes the run from both sides at once. The upstream stops reporting it, the query has nothing left to select, and the test passes. Measured on production 2026-09-15: 72 of the 79 course runs carrying a semester depend on that shared predicate. Only the 7 with a MicroMasters exams_examrun record are independent of course structure. The earlier negative control did not expose this because it stubbed the gate in dim_course_run only, breaking the symmetry the real failure preserves. No query change — what the test does catch is real and worth keeping: divergence between the dimension's hand-reproduced gate and the upstream derivation, plus any run missing a current dimension row. The header now states that scope, records why drift is invisible here, and notes that drift removes mart rows outright and has to be caught upstream against a signal independent of the current block title. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟢 Approval recommended
The current revision resolves the previously identified correctness gaps and preserves the mart’s established output semantics.
Review details
- Files reviewed: 4/5 changed files
- Comments generated: 0 new
- Review effort level: Balanced
|
Out of scope for this PR, but directly relevant context — and it sharpens something I said earlier in this thread. When I narrowed the gate in a01101b I noted the only run removed was That run has a block that used to be titled
It is the only affected run today. 14.73 is a DEDP course, so this is probably a genuine reporting gap rather than an artifact — but whether Tracked as @KatelynGit flagging in case the 32 missing learners matter for DEDP reporting independently of this migration. |
The migration validation flow in this PR's testing instructions materializes the pre-migration version of each changed model as a sibling model file (`<model>_pre.sql`, built from origin/main) so `ol-dbt diff` can compare the two sides. Those files are throwaway, but they are real dbt models in a tracked directory, so they show up as untracked and a `git add -A` commits them. Verified the pattern catches both baselines this PR uses and shadows no tracked model (`git ls-files 'src/ol_dbt/models/**/*_pre.sql'` is empty). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd state the scope up front Folds in what two manual validation runs on #2403 and #2686 learned but the skill did not yet say. CAUTIOUS DROPS THE PR'S OWN TEST. `--indirect-selection=cautious` includes a test only when EVERY model it references is selected, so a new singular test the PR itself adds — referencing the changed model plus something outside the selection — is silently excluded. On #2403 that was the PR's own new test, the one test most worth seeing. Switched the recommendation to `buildable`, which relaxes exactly that condition and still excludes the eager relationships noise. Re-measured both modes rather than trusting the July figures: on dim_course_run eager 17 / buildable 8 / cautious 8, and the single test buildable adds is the course_fk -> dim_course relationship. RUN THE TESTS BEFORE THE DIFF, with the reason: a named failing test is more diagnostic than a 20k-row mismatch report, and a broken model makes a MATCH uninterpretable — you cannot distinguish agreement from two identically-wrong sides. WHAT TO EXPECT was missing entirely — the skill said which figures to trust but never what the report looks like or what a pass is. Step 5(d) now says to diff EVERY changed model with a per-model verdict written down first: unaffected -> MATCH/exit 0; intentionally changed -> MISMATCH/exit 1 where the SHAPE is the assertion (row delta 0, only the intended columns, the intended direction). So a MISMATCH is not a failure and a MATCH is not automatically a pass. Includes the #2403 numbers that turned an asserted claim into a measured one (semester 12 -> 99 non-null over 87 re-versioned rows, body had said "~100"), and the --exclude-columns effective_date,end_date that SCD2 models need or the report is unreadable. Also rewrote the frontmatter description and the opening paragraph, which undersold the skill as "the acceptance procedure" when it is the whole register/build/test/diff workflow. Anyone scanning the description could not tell whether it covered the higher-level sequence or just the diff command. Two stale spots caught by grepping for what the change touched: "test separately, cautiously" now misread as the mode name, and a "runs 7 tests" figure that re-measurement put at 8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two gaps found by checking the skill against everything the #2403 and #2686 manual runs produced, rather than against the review findings. NO NEGATIVE CONTROL. The skill said what a clean result looks like but never how to prove the comparison can fail. A MATCH is unfalsifiable on its own — it is indistinguishable from a diff that compared nothing. Added the recipe (replace the column under test with a literal in _pre, rebuild that model only, rerun) with the #2403 evidence that it does fail loudly: perturbing semester gave `mismatch — 17888 unmatched row-side(s), 1 column value mismatch(es)`, exit 1, rows listed. Also says to put the one-liner in the PR body, since a reviewer cannot otherwise distinguish the two. THE SHARED DUCKDB CAN STOP YOU, not just mislead you. The section covered staleness and clobbering but not the hard failure: with another session holding the file, ol-dbt diff dies on `_duckdb.IOException: Could not set lock on file ... Conflicting lock is held` — 5 retries at ~20s on #2403. Called out as contention rather than a defect in the change under review, so nobody reports it as one. Renumbered that list's intro from "Two consequences" to "Three", caught by grepping what the edit touched. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Populated only for MITxOnline course runs that correspond to a MicroMasters | ||
| exam | ||
| run. Null for all other platforms and non-exam MITxOnline course runs. | ||
| Academic term identifier (e.g. "2T2022") for a course run that carries a |
There was a problem hiding this comment.
These definitions are not great and clearly not written or reviewed by a human
There was a problem hiding this comment.
- I got one warning when running a build but otherwise it was clean:
[WARNING]: in test not_null_dim_course_run_course_fk (models/dimensional/_dim_course_run.yml)
[WARNING]: Got 626 results, configured to warn if != 0
-
The definitions could be improved they are definitely not written by a human and don't make sense
-
Was a data diff run against old and new?
What are the relevant tickets?
Partially addresses #2088 (follow-up to #2319 —
dim_course_runsemester/passing_grade)Description (What does it do?)
Migrates the MITxOnline branch of
marts__micromasters_dedp_exam_gradesto readsemesterandpassing_gradefromdim_course_runinstead of re-deriving them through a duplicate join to MicroMasters staging insideint__mitxonline__proctored_exam_grades.#2319 added those columns to
dim_course_runbut populatedsemesterfrommicromasters_examrunsonly, dropping thecourserun_tagfallback the old mart applied to course runs with an embedded proctored-exam unit — which is why a straight swap nulled 8,137 rows when Kate first tried it.dim_course_runnow restores that fallback, gated on the same course-structure predicate the intermediate model used (a block titled'proctored exam', read from its newest snapshot per block) so it only applies to runs that actually have an exam — 100 course runs, not all 4,513.passing_gradedeliberately gets no fallback: the old model emittednullon that branch, so a bareer.examrun_passing_gradereproduces it. A new singular test asserts the dimension keeps covering every run the upstream reports with a semester.Mart output is unchanged and the dimension changes in exactly one column, both reproduced in step 3. Expect a one-time SCD2 re-version of 87
dim_course_runrows on the first production run after merge.How can this be tested?
Everything below runs on this PR's branch — the baseline side is read out of
origin/mainwithgit show, so there is no branch switching. Start from the repo root; step 1cds intosrc/ol_dbtand steps 2 and 3 continue from there.1. Register the sources and build both sides of the comparison.
PASS=5.2. Test the two models that changed.
PASS=13 WARN=1 ERROR=0, includingassert_dim_course_run_semester_covers_proctored_exam_runs. The warning isnot_null_dim_course_run_course_fk, pre-existing and identical on main.3. Diff both changed models, old against new.
The mart must not change:
MATCH, 12 columns,20867 / 20867 Δ=0, exit 0. Negative control: replacesemesterwith a literal in${MART}_pre.sql, rebuild that model, rerun — it reportsmismatch.dim_course_runmust change, in one column only:# effective_date/end_date are current_timestamp, so they differ on every build. uv run --frozen ol-dbt diff --old dim_course_run_pre --new dim_course_run \ -k courserun_pk --exclude-columns effective_date,end_dateMISMATCH, exit 1 — expected here.8510 / 8510 Δ=0, one column moving:semester: 1.02% (87 rows), samples allNone → '<tag>'. Nopassing_grade. Non-nullsemester12 → 99.Both diffs are needed: the mart diff proves nothing about
dim_course_run, which has eight other downstream consumers.Two known snags:
~/.ol-dbt/local.duckdbis shared across worktrees, so ifol-dbt difffails with a DuckDBConflicting lockerror another session has it open — rerun when free. And a build failing on an HTTP 404 for a__dbt_tmpmetadata path just means that layer's registration went stale; re-register it and rerun.🤖 Generated with Claude Code