Repository navigation
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>
…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>
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?
…n PRs (#2659) 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 turns on: is the data the same? This adds the end-to-end local validation workflow (register, build both sides, test, diff, decide), derived from validating #2403 and #2686. The load-bearing part is which local numbers are trustworthy. 651 of 667 non-raw Glue views (97.6%) point at `__dbt_tmp` metadata locations, which return duplicated or partially-missing rows while the build succeeds — and the pollution is heterogeneous, from 1.00x to 61.85x across views in a single registration. 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 delta between two sides built locally from the SAME registration is evidence, because identical inputs cancel. The skill is organised around that distinction: register once, build both sides in one invocation, compare. The rules that cost the most to learn, each with the measurement behind it: - Re-register immediately before every build. A registry 60 minutes old 404'd mid-validation; 336 pointers moved over 26 hours (65% staging, 68% intermediate, 79% dimensional). And a stale registration does not look stale, it looks like agreement — on #2686 a stale staging layer read 4,513 rows where a fresh one read 4,996, and because both comparison sides came from the stale side the diff was a clean 0/0. That 483-row gap was published and retracted. - `--full-refresh` whenever the model under test is incremental. An incremental run does execute the model SQL, but re-derives only what `is_incremental()` reselects; `dim_course_run` reported OK in 0.11s without re-deriving the column under test, for two months. - `~/.ol-dbt/local.duckdb` is shared by every worktree and session on the machine. A concurrent rebuild took `semester` from 4,513/4,513 populated to 12/4,513, with no warning from either side. - `dbt run`, not `dbt build`. `+error_if: ">10"` makes the skip intermittent: 4 failing rows WARN and the downstream model builds, 24 FAIL and it is skipped, leaving a comparison side that silently does not exist. - Test with `--indirect-selection=buildable`, on the two comparison models only. `eager` pulls in `relationships_*` tests owned by other models, which compare your local build against production Glue (17 tests vs 8 on dim_course_run); `cautious` drops the PR's own new test. - Take the join key from the model's own uniqueness test, including its `where` clause. Unfiltered, an SCD2 relation reads 6 rows / 5 distinct keys — a fan-out that is ordinary history; filtered, 3/3. And with an unproven key the per-column mismatch rate is the key-sensitive figure, not the unmatched-row count: two byte-identical 4-row tables with one repeated key report 0 unmatched rows alongside a 33.33% column mismatch. - Negative-control the diff before believing a MATCH. Perturbing `semester` in the `_pre` side produced `mismatch — 17888 unmatched row-side(s)`, exit 1. - Gate the build on the registration log, not the exit status. `ol-dbt local register` exits 0 with failed tables, and never counts a Glue database-level failure or an empty layer at all, so `✗ Errors: 0` can print while an entire layer went unrefreshed. Alongside the new skill, `ol-dbt-local-dev` gains the freshness rules, the shared-DuckDB warning, and a PATH prerequisite (`ol-dbt` shells out to bare `dbt`, so a stray 1.8.1 earlier on PATH fails with "Could not find adapter type duckdb!"); its "prefer incremental" rule is reframed as iterating vs concluding, which is the distinction that actually decides it. `agent-config.toml` registers the skill and adds a `migration` profile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Katelyn flagged dim_course_run.semester's description as "not great and
clearly not written or reviewed by a human". She was right, and the same
register ran through every comment block this PR adds, not just that one.
Rewrote all six against the repo's long-standing house style for column
descriptions (`str, semester for the exam, e.g., 2T2022`) rather than the
paragraph-length prose the dimensional yml files had drifted into. 76 lines
of prose down to 32.
- _dim_course_run.yml semester: dropped the repo-history clause ("the
derivation X used before this field moved into the dimensional layer"),
the internal model and CTE names, the duplicated null enumeration, and the
"usually but not always" hedge. Kept the suffix caveat, which is the one
fact that changes how an analyst uses the column: `group by semester`
splits 1T2022 from 1T2022a.
- _dim_course_run.yml passing_grade: not otherwise touched by this PR, but it
sits in the same block and its closing sentence ("-1.0 is used as the SCD2
change-detection sentinel") invites the misreading that -1.0 appears in the
column. It does not. -1.0 occurs once, in the incremental change-detection
comparison at dim_course_run.sql:334, and is never stored. The entry now
also records the divergence this PR creates: semester gained a course run
tag fallback, passing_grade deliberately did not.
- dim_course_run.sql, the mart, and the singular test: kept every
load-bearing rationale (why coursestructure_is_latest is deliberately not
used, the SCD2 fan-out guard, the error_if override, the LEFT join, the
test's blind spot) and dropped the point-in-time production measurements.
Those rot in a comment and already live in the PR discussion, dated.
Both descriptions are exact against enforced tests:
stg__micromasters__app__postgres__exams_examrun carries not_null on
examrun_semester and examrun_passing_grade, so a matching exam run record
always supplies both, and the other four platforms set each column to an
explicit null literal.
Executable SQL is unchanged. Compiled output for all three models is
identical to the pre-edit build once comment lines are stripped (346, 62 and
13 executable lines, zero non-comment changed lines), so no rebuild or data
diff is required. Verified locally: pre-commit clean on all four files,
sqlfluff clean, `ol-dbt validate` 0 errors, and `dbt test -t dev_local
--indirect-selection=buildable --select dim_course_run
marts__micromasters_dedp_exam_grades` gives PASS=13 WARN=1 ERROR=0. The
warning is the pre-existing not_null_dim_course_run_course_fk, identical on
main.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#2319 removed this test and replaced it with a description asserting the column is nullable. That commit's own message records why: an initial attempt to migrate the mart onto dim_course_run "would null out semester for ~9,987 rows and break the not_null test". The migration was reverted; the test was not put back, and the description it left behind describes the behaviour of the reverted code rather than what shipped. This is the exact regression the guard existed to catch, and it is the one this PR performs for real. Reverts that hunk byte-for-byte (69f9c58 back to 8e8c476). The description was also wrong on merged main, independently of this PR: int__mitxonline__proctored_exam_grades has derived `courserun_tag as semester` for the embedded-exam branch since long before #2319, so the mart has always carried a semester for runs with no MicroMasters exam run record. Measured today, that is 8,850 of 20,867 mart rows. Safe to restore because the mart is one row per proctored exam grade, not per course run. Its universe is the 79 course runs that have exam grades, all of which sit inside the dimension's gate. Measured against production upstream data with this branch's dim_course_run: mitxonline upstream rows / distinct runs 9,443 / 79 runs with no current dim_course_run row 0 runs with a dim row but null semester 0 micromasters branch rows / null semester 11,923 / 0 mart rows / null semester 20,867 / 0 It is enforced rather than merely true today. int__mitxonline__proctored_exam_grades.semester carries not_null, so this PR's assert_dim_course_run_semester_covers_proctored_exam_runs covers every one of the 79 runs (both "no current dim row" and "dim row with null semester"), and the mart's left join reads the same is_current set that test checks. The micromasters branch reads examrun_semester, also not_null. Without this PR's courserun_tag fallback the test would fail again: 8,850 of the 9,443 mitxonline rows depend on it. Restoring this on main alone would go red. Verified by negative control, not just a green run: replacing the mart's semester with a null literal and rebuilding gives "FAIL 8944 ... Got 8944 results, configured to fail if >10", the same shape as the July failure on this PR. Reverted and rebuilt clean. Full set is now PASS=14 WARN=1 ERROR=0, the warning being the pre-existing not_null_dim_course_run_course_fk. Deliberately not adding not_null to proctoredexamgrade_passing_grade: it has 8,850 nulls by design, since the tag-fallback runs have no MicroMasters exam run record and so no passing grade. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The comment credited `ol-dbt diff` with building them. It does not — it only reads them. Three separate steps produce one: `git show origin/main:<model>.sql` writes the file, `dbt run --select <model>_pre` builds it, and `ol-dbt diff --old <model>_pre` compares it. Anyone tracing where these files originate would have gone looking in the wrong tool. Also drops "Never committed", which only restates what the gitignore entry below it already does. Comment only; the `**/models/**/*_pre.sql` pattern is untouched and still matches (verified with git check-ignore against probe paths under both models/dimensional/ and models/marts/). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Regarding the warning - not_null_dim_course_run_course_fk is pre-existing and identical on main so that is a known issue on main (527 residential course runs plus 93 mitxpro and 3 mitxonline whose courses aren't in dim_course yet). I went through and updated a handful of descriptions/comments to make sure they were human readable and easy to understand. Yes, a data diff was run and I made sure to use the same snapshot for the before/after build, here is a summary of the results. I included the instructions for running the diff on your own machine if you'd like to, it should produce the same MATCH/MISMATCH verdicts but there may be some drift in the row counts which is expected. I also just added back a not_null test on marts__micromasters_dedp_exam_grades.semester which was removed in my first PR for this ticket (2319). #2319 is still on main (this PR builds on the columns it added), but dropping that test there meant nothing flagged the null semesters. The test would have caught the issue you brought up in your earlier review of this PR (8137 row failure). |
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