Rebuild an EPA eGRID Model
Prove EPA model lineage with a local Git, DVC, and MLflow pipeline.
Introduction
30 Second Summary
A result is only trustworthy when you can show exactly where it came from. When the source changes, that proof needs to follow the result.
In this project, you will build a local MLOps lineage lab around two revisions of the EPA eGRID2023 plant workbook. You will use Git, DVC, and MLflow to connect each classifier to its exact code, data fingerprint, registered version, and clean-clone verification.
What You'll Build
You will demo a local proof chain that starts with an EPA workbook revision, ends with a verified registered champion, and survives a complete rebuild in a fresh repository copy.
By the end of this project, you'll have:
- A schema-tested training pipeline that prints SCHEMA OK before fitting and records its ROC AUC, data MD5, Git commit, and model URI in a visible METRICS result.
- A two-revision registry history where Revision 1 and Revision 2 have different data fingerprints and champion resolves to Revision 2.
- A clean-clone verification that restores the pinned workbook, rebuilds the classifier, and finishes with three PASS lines plus ALL CHECKS PASS.
- Secret Mission: Break the clone with stale Revision 1 data, recover the champion bytes with DVC, and create a teach-back, after-action review, and self-contained readout.html evidence report.
Are there any prerequisites?
You need Windows with PowerShell and enough free disk space for workbook copies, model artifacts, and DVC stores. The main project runs locally at no cost and requires no new account, while the optional Secret Mission close-out uses an existing ChatGPT or Claude client or the free local Ollama fallback, which installs software and downloads a model.
The learner-supplied workbooks must already exist at $HOME\egrid\egrid2023_data_rev1.xlsx and $HOME\egrid\egrid2023_data_rev2.xlsx.
Before We Start
This opening checkpoint fixes the promise of your local eGRID2023 lineage lab before tools compete for attention. You will define how a Git commit plus an MD5 workbook fingerprint identifies the exact lineage of a model.
Set Up the Local Lineage Lab
A lineage claim depends on the environment that produced it. If versions or input locations drift, the later evidence cannot be repeated.
You will use PowerShell to record the setup in check-out.txt. Git will hold the project history.
DVC will connect that history to workbook fingerprints. This step makes the runtime explicit before model work begins.
In this step, get ready to:
- Prepare the pinned Python runtime.
- Install the verified project dependencies.
- Initialize the local lineage stores.
Prepare the project workspace
The project needs a fixed location so every later path resolves from the same folder. You will create egrid-lineage in your user folder ($HOME).
- Press the Windows key to open the search bar.
- Type pwsh into the search bar.
- Press Enter to open PowerShell 7 if it appears.
- Type Windows PowerShell into the search bar if Windows cannot find pwsh.
- Press Enter to open Windows PowerShell.
- Create the egrid-lineage folder in your user folder ($HOME) by running these commands:
New-Item -Path "$HOME\egrid-lineage" -ItemType Directory -Force
Set-Location -Path "$HOME\egrid-lineage" -PassThru
What do these commands do?
- The first command finds your Windows Desktop even when its location has been redirected.
- The next command moves PowerShell to that Desktop location.
- The third command creates the egrid-lineage directory.
- The final command enters the new directory and prints its full path.
🙋♀️ Project folder not created?
Check that the final path ends in Desktop\egrid-lineage. If the command failed, confirm that your Windows account can create folders on the Desktop.
Still stuck? Help me create the egrid-lineage folder from PowerShell.
Python 3.13.x provides the runtime family for the lab. The Windows launcher can list installed runtimes before you create the virtual environment.
- List the installed Python runtimes and print the exact Python 3.13 patch release by running these commands:
py --list 2>&1 | Tee-Object -Append check-out.txt
py -3.13 --version 2>&1 | Tee-Object -Append check-out.txt
What do these checks show?
The Python launcher lists the runtime families it can use. The second command selects Python 3.13 and prints its exact patch release.
Tee-Object displays both results while appending the same evidence to check-out.txt.
✔️ I see Python 3.13
Your installed Python 3.13 patch release is ready. Continue to the Git check below.
ⓧ Python 3.13 is not listed
Python 3.13 is not installed. Install Python 3.13.16 alongside your existing runtime.
- Open the official Python release page for version 3.13.16.
- Download the recommended 64-bit Windows installer.
- Complete the installer with the Python launcher enabled.
- Return to PowerShell after the installer finishes.
- Run both Python checks above again.
ⓧ Command not found
The Python launcher is unavailable. Installing Python 3.13.16 adds the runtime and launcher needed for this project.
- Open the official Python release page for version 3.13.16.
- Download the recommended 64-bit Windows installer.
- Complete the installer with the Python launcher enabled.
- Return to PowerShell after the installer finishes.
- Run both Python checks above again.
Git 2.x records the code state associated with each model lineage. Check the installed version before initializing the repository.
- Check the installed Git version by running this command:
git --version 2>&1 | Tee-Object -Append check-out.txt
What does this check do?
The command prints the installed Git version. It also adds that version to the evidence log.
✔️ I see Git 2.x
Git 2.x is ready for the repository setup.
ⓧ I see an older Git version
The installed version is below the required Git 2.x release family. Upgrade it before continuing.
- Open the official Git for Windows download page.
- Download the current Git for Windows installer.
- Complete the installer with its default options.
- Return to PowerShell after the installer finishes.
- Run the Git version command above again.
ⓧ Command not found
Git is unavailable from PowerShell. Install the official Windows build before continuing.
- Open the official Git for Windows download page.
- Download the current Git for Windows installer.
- Complete the installer with its default options.
- Return to PowerShell after the installer finishes.
- Run the Git version command above again.
A virtual environment isolates the pinned packages from other Python projects on your machine. The activation command makes every later Python command use .venv.
- Create the virtual environment and activate it by running these commands:
py -3.13 -m venv .venv 2>&1 | Tee-Object -Append check-out.txt
.\.venv\Scripts\Activate.ps1
python --version 2>&1 | Tee-Object -Append check-out.txt
git --version 2>&1 | Tee-Object -Append check-out.txt
What happens here?
- The first command creates .venv with the installed Python 3.13 runtime.
- The activation script directs python to that isolated environment.
- The last two commands print the active Python version and Git version.
- Both version checks are appended to check-out.txt.
The PowerShell prompt now starts with (.venv). That prefix confirms the isolated runtime is active.
Virtual environment not active?
Confirm that PowerShell is inside the egrid-lineage directory. Check that the .venv folder exists there.
A blocked activation script can feel risky to fix. The command below changes the execution policy only for this PowerShell window, so the setting disappears when you close it.
- Allow scripts in the current window and retry the virtual environment activation by running:
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\.venv\Scripts\Activate.ps1
A policy managed by your organization can override this temporary setting. Help me diagnose the remaining execution-policy block.
Install the pinned dependencies
The runtime uses seven pinned distributions. Six are imported directly by the training program, while skops serializes the scikit-learn model that MLflow logs.
MLflow records runs and registry versions. The remaining packages handle data versioning, workbook parsing, table operations, configuration, and model training.
- Create .gitignore inside egrid-lineage by running this command:
@'
.venv/
__pycache__/
*.pyc
check-out.txt
'@ | Set-Content -Path .gitignore
Why ignore these files?
The file keeps the local virtual environment and Python cache files out of Git. It also prevents the machine-specific evidence log from entering repository history.
🙋♀️ Ignore file missing?
Run the command from the egrid-lineage directory. Make sure the closing '@ begins its own line.
Need help? Help me create this PowerShell here-string file.
- Create requirements.txt inside egrid-lineage by running this command:
@'
mlflow==3.17.1
dvc==3.67.1
scikit-learn==1.9.1
openpyxl==3.1.5
pandas==3.0.6
PyYAML==6.0.3
skops==0.14.0
'@ | Set-Content -Path requirements.txt
Why pin every package?
MLflow 3.17.1 uses skops as its default serialization format for scikit-learn models. Skops 0.14.0 trusts the internal tree types used by GradientBoostingClassifier, so model logging reaches the METRICS line.
The remaining pins lock every package imported directly by the training program. Together, the seven versions make the dependency state reviewable.
🙋♀️ Requirements file missing?
Confirm that the virtual environment prompt still shows (.venv). Check that the closing '@ begins its own line.
Need help? Help me create requirements.txt from this here-string.
Compare both files before installing anything.
✔️ Awesome, I've got everything!
Both files are ready. Keep the virtual environment active for the installation.
ⓧ I'd like to double check the full code
.venv/
__pycache__/
*.pyc
check-out.txt
What should match?
Your .gitignore file contains these four entries in this order.
mlflow==3.17.1
dvc==3.67.1
scikit-learn==1.9.1
openpyxl==3.1.5
pandas==3.0.6
PyYAML==6.0.3
skops==0.14.0
What should match?
Your requirements.txt file contains all seven exact pins. Each package appears on its own line.
The installation may take several minutes while Python downloads the packages. A stream of package names during this command is normal.
- Install the pinned dependencies by running this command:
python -m pip install -r requirements.txt 2>&1 | Tee-Object -Append check-out.txt
What does this installation record?
Python installs every version from requirements.txt into the active virtual environment. The full installation output is also appended to check-out.txt.
🙋♀️ Dependency installation failed?
Confirm that the prompt begins with (.venv). Check that requirements.txt matches the double-check tab.
If a download failed, check your network connection before retrying the same command. Help me diagnose the failed package installation.
The import check proves that each installed distribution loads successfully. It also records the version exposed by each package.
- Verify all seven imported packages by running this command:
python -c "import mlflow,dvc,sklearn,openpyxl,pandas,yaml,skops; print('mlflow',mlflow.__version__); print('dvc',dvc.__version__); print('sklearn',sklearn.__version__); print('openpyxl',openpyxl.__version__); print('pandas',pandas.__version__); print('PyYAML',yaml.__version__); print('skops',skops.__version__)" 2>&1 | Tee-Object -Append check-out.txt
What does this verification prove?
The command imports MLflow, DVC, scikit-learn, openpyxl, pandas, PyYAML, and skops in one Python process. A printed version confirms that the corresponding import succeeded.
You should see 3.17.1, 3.67.1, 1.9.1, 3.1.5, 3.0.6, 6.0.3, and 0.14.0 beside their package names.
Import or version mismatch?
Confirm that the command uses the active .venv interpreter. Recheck every line in requirements.txt before reinstalling.
Still blocked? Help me diagnose this package import or version mismatch.
Initialize the lineage stores
Git stores code history inside the project. DVC stores data metadata in Git while keeping workbook bytes outside the repository.
Why use a local DVC remote?
A local remote teaches the same cache and restore workflow as remote storage without requiring an account. The clean-clone test later proves that Git alone does not contain the workbook bytes.
- Initialize the Git repository and DVC metadata by running these commands:
git init 2>&1 | Tee-Object -Append check-out.txt
dvc init 2>&1 | Tee-Object -Append check-out.txt
What gets initialized?
Git creates the repository history store inside .git. DVC creates its project metadata inside .dvc.
Both initialization results are preserved in check-out.txt.
DVC enables anonymized usage analytics by default and displays a notice the first time you run dvc init. You can keep the default or disable analytics for this project.
Choose how this project handles DVC analytics.
Keep analytics enabled
No action is needed. DVC continues using its default anonymized analytics setting for this project.
Turn analytics off
- Disable DVC analytics for this project and print the saved value by running:
dvc config core.analytics false
dvc config core.analytics
You should see false. DVC stores this setting in the current project's configuration.
Repository initialization failed?
Confirm that PowerShell is still inside egrid-lineage. Verify that the active environment contains DVC.
Need help? Help me diagnose the Git or DVC initialization failure.
The external remote gives DVC a durable location outside the repository. The remote name local becomes the default destination for later pushes.
- Create the external DVC remote and configure it as the default by running these commands:
New-Item -ItemType Directory -Force "$HOME\egrid-dvc-remote" 2>&1 | Tee-Object -Append check-out.txt
dvc remote add -d local ..\egrid-dvc-remote 2>&1 | Tee-Object -Append check-out.txt
What does the remote configuration do?
The first command creates $HOME\egrid-dvc-remote outside the project repository. The second command records it as the default remote named local in .dvc/config.
🙋♀️ DVC remote not configured?
Confirm that dvc init completed before the remote command. Check that your Windows account can create a directory under $HOME.
Still stuck? Help me configure the external local DVC remote.
The two EPA workbooks are learner-supplied inputs. This check confirms that both revisions exist before the pipeline tries to copy either one.
EPA's Detailed Data page currently surfaces Revision 2 as the latest workbook. Revision 1 no longer appears there as a separate download, so you will use its direct EPA file URL.
Each workbook is about 20 MB, so the downloads may take a minute on a slower connection. PowerShell returns to the prompt when both files are ready.
- Create $HOME\egrid and download both EPA workbooks by running these commands:
New-Item -ItemType Directory -Force "$HOME\egrid"
Invoke-WebRequest "https://www.epa.gov/system/files/documents/2025-01/egrid2023_data_rev1.xlsx" -OutFile "$HOME\egrid\egrid2023_data_rev1.xlsx"
Invoke-WebRequest "https://www.epa.gov/system/files/documents/2025-06/egrid2023_data_rev2.xlsx" -OutFile "$HOME\egrid\egrid2023_data_rev2.xlsx"
What do these downloads do?
- The first command creates $HOME\egrid if the directory does not already exist.
- The second command downloads Revision 1 from its direct EPA URL.
- The final command downloads Revision 2 from its direct EPA URL.
- Confirm both source workbooks exist by running this command:
Get-Item "$HOME\egrid\egrid2023_data_rev1.xlsx","$HOME\egrid\egrid2023_data_rev2.xlsx" 2>&1 | Tee-Object -Append check-out.txt
What should this show?
PowerShell prints one file entry for egrid2023_data_rev1.xlsx and one for egrid2023_data_rev2.xlsx. The same file evidence is appended to check-out.txt.
🙋♀️ Workbook download or path missing?
If a download stops, confirm that your network allows connections to EPA.gov. Run the same download commands again after the connection recovers.
If the file check still fails, confirm that both workbooks are inside $HOME\egrid. Check that their filenames match the verification command exactly.
Still blocked? Help me diagnose the workbook download or path.
Before you run the final check, how many tool and package versions do you expect the terminal to print?
- Run the combined version check and append its output to the evidence log:
python --version 2>&1 | Tee-Object -Append check-out.txt
git --version 2>&1 | Tee-Object -Append check-out.txt
python -c "import mlflow,dvc,sklearn,openpyxl,pandas,yaml,skops; print('mlflow',mlflow.__version__); print('dvc',dvc.__version__); print('sklearn',sklearn.__version__); print('openpyxl',openpyxl.__version__); print('pandas',pandas.__version__); print('PyYAML',yaml.__version__); print('skops',skops.__version__)" 2>&1 | Tee-Object -Append check-out.txt
What does the final check prove?
The first two lines verify the active runtime and source-control tool. The final line verifies every imported package in the same environment.
The visible output is duplicated into check-out.txt. That file now carries the setup evidence needed for the later lineage readout.
You should see your installed Python 3.13 patch release, Git 2.x, MLflow 3.17.1, DVC 3.67.1, scikit-learn 1.9.1, openpyxl 3.1.5, pandas 3.0.6, PyYAML 6.0.3, and skops 0.14.0 printed on screen.
That is the setup complete. Your runtime, repository, remote, workbook inputs, and evidence capture are ready for a reproducible build.
Your local lineage lab now has a fixed runtime and explicit storage boundaries. Next, you will define the evidence contract before writing the training pipeline.
Design the Evidence Chain
Your Git repository is ready. Your DVC foundation can preserve data history from this point forward.
A lineage claim becomes fragile when success is defined after the model already exists. This step writes the contract first so every later tool has a specific piece of evidence to produce.
In this step, get ready to:
- Define the acceptance criteria for the lineage proof.
- Record the architecture through decisions, topology, and a timed build plan.
- Map each design claim to an authoritative source.
Define the contract and decisions
The requirements brief turns the project goal into six checks. The decision records explain why each implementation choice supports that contract.
- Open the egrid-lineage folder in VS Code by running this command from that folder in PowerShell:
code .
- In the Explorer sidebar, click New Folder.
- Enter docs as the folder name.
- Press Enter to create the folder.
You should see docs beside requirements.txt in the project file tree.
Code command not found?
The VS Code command can be missing from your Windows PATH even when the app is installed. Open the folder through the app instead.
- Press the Windows key to open the search bar.
- Enter Visual Studio Code.
- Press Enter to open VS Code.
- Click File in the top menu bar.
- Click Open Folder.
- Select your egrid-lineage folder.
- Click Select Folder.
Still cannot open the project? Help me open the folder in VS Code.
- Select the docs folder in the Explorer sidebar.
- Click New File.
- Enter requirements-brief.md as the file name.
- Press Enter to create the file.
- Add the problem statement through AC-3 by copying this first section:
# Requirements Brief
## Problem
An ML score is not auditable unless a reviewer can identify the exact code and exact data bytes that produced the model. EPA revised eGRID2023 after release, including a Revision 2 change that counted negative plant net generation previously calculated as zero. The model must preserve data revision evidence, not only a filename.
## User outcome
A learner can start from two local eGRID2023 workbook revisions, build and register one classifier per source revision, rebuild the champion from a clean clone, and prove that stale data fails validation.
## Operating principle
One Git commit plus one MD5 data hash identifies one model lineage.
## Acceptance criteria
### AC-1: Design precedes implementation
The repository contains complete `requirements-brief.md`, `decisions.md`, `topology.md`, `build-plan.md`, and `source-map.md` files before `train.py` is committed.
### AC-2: Data and code lineage are captured
Each source training run records current `git rev-parse HEAD` and the MD5 of exact `data/egrid.xlsx` bytes. Revision 1 and Revision 2 produce different hashes.
### AC-3: The data contract is executable
`train.py` asserts `PLFUELCT`, `NAMEPCAP`, `CAPFAC`, `PLNGENAN`, and `PLCO2RTA`; filters approved fuels, positive annual net generation, and complete fields; asserts at least 1,000 rows; and prints `SCHEMA OK` before fitting.
What does this section define?
- The operating principle requires a code identity plus a data identity for every model.
- The first criterion preserves proof that design happened before implementation.
- The next two criteria make code lineage and schema quality executable.
- Save docs/requirements-brief.md.
- Review the file in your editor. You should see the problem statement followed by AC-1 through AC-3.
Missing the first three criteria?
Check that requirements-brief.md sits inside docs. Confirm that your editor did not save it with an extra file extension.
Still stuck? Help me check the first section of my requirements brief.
- Complete docs/requirements-brief.md by adding the remaining criteria and exclusions below the existing content:
### AC-4: The simple model pipeline ships first
One DVC `train` stage reads `params.yaml`, creates a median binary label, one-hot encodes `PLFUELCT`, uses a stratified 25 percent holdout, fits gradient boosting, calculates ROC AUC, logs one MLflow run, and writes `metrics.json`.
### AC-5: Registry and clean-clone lineage work
The registry contains `egrid-rate-class` versions 1 and 2. `champion` points at version 2. A clean clone restores the workbook, rebuilds the stage, and prints three PASS lines plus `ALL CHECKS PASS`.
### AC-6: Validation and presentation evidence are complete
- `git log --oneline -- train.py` prints exactly one line.
- Two source registration lines show different `data_md5` values.
- Source `check-out.txt` contains exactly two source-build `SCHEMA OK` events.
- The clean clone prints three PASS lines and `ALL CHECKS PASS`.
- Revision 1 in the clone makes the data-MD5 check fail with exit code 1.
- DVC checkout, forced reproduction, and final check restore all passes.
- `readout.html` names EPA, shows all three dates, and explains the Revision 2 negative-generation lesson.
## Exclusions
- No MLflow server or serving endpoint.
- No CI workflow.
- No other eGRID years.
- No cloud service.
- No new account, payment card, or paid API.
- No hyperparameter search, feature store, or deployment platform.
- No AI-generated build code. AI is used once at close-out.
How does the contract close?
- The pipeline criterion defines the minimum model workflow that must ship.
- The registry criterion requires a stable champion that survives a clean-clone rebuild.
- The final criterion names the visible evidence required at close-out.
- The exclusions protect the project from unrelated infrastructure work.
- Save docs/requirements-brief.md.
- Count the acceptance-criteria headings. You should see exactly six headings from AC-1 through AC-6.
Acceptance criteria out of order?
Place the second section directly after AC-3. Check that Exclusions remains the final heading.
Need a second pair of eyes? Help me find the ordering problem in my acceptance criteria.
✔️ Awesome, I've got everything!
Your requirements contract now defines the complete proof chain.
ⓧ I'd like to double check the full code
- Compare docs/requirements-brief.md with this complete reference:
# Requirements Brief
## Problem
An ML score is not auditable unless a reviewer can identify the exact code and exact data bytes that produced the model. EPA revised eGRID2023 after release, including a Revision 2 change that counted negative plant net generation previously calculated as zero. The model must preserve data revision evidence, not only a filename.
## User outcome
A learner can start from two local eGRID2023 workbook revisions, build and register one classifier per source revision, rebuild the champion from a clean clone, and prove that stale data fails validation.
## Operating principle
One Git commit plus one MD5 data hash identifies one model lineage.
## Acceptance criteria
### AC-1: Design precedes implementation
The repository contains complete `requirements-brief.md`, `decisions.md`, `topology.md`, `build-plan.md`, and `source-map.md` files before `train.py` is committed.
### AC-2: Data and code lineage are captured
Each source training run records current `git rev-parse HEAD` and the MD5 of exact `data/egrid.xlsx` bytes. Revision 1 and Revision 2 produce different hashes.
### AC-3: The data contract is executable
`train.py` asserts `PLFUELCT`, `NAMEPCAP`, `CAPFAC`, `PLNGENAN`, and `PLCO2RTA`; filters approved fuels, positive annual net generation, and complete fields; asserts at least 1,000 rows; and prints `SCHEMA OK` before fitting.
### AC-4: The simple model pipeline ships first
One DVC `train` stage reads `params.yaml`, creates a median binary label, one-hot encodes `PLFUELCT`, uses a stratified 25 percent holdout, fits gradient boosting, calculates ROC AUC, logs one MLflow run, and writes `metrics.json`.
### AC-5: Registry and clean-clone lineage work
The registry contains `egrid-rate-class` versions 1 and 2. `champion` points at version 2. A clean clone restores the workbook, rebuilds the stage, and prints three PASS lines plus `ALL CHECKS PASS`.
### AC-6: Validation and presentation evidence are complete
- `git log --oneline -- train.py` prints exactly one line.
- Two source registration lines show different `data_md5` values.
- Source `check-out.txt` contains exactly two source-build `SCHEMA OK` events.
- The clean clone prints three PASS lines and `ALL CHECKS PASS`.
- Revision 1 in the clone makes the data-MD5 check fail with exit code 1.
- DVC checkout, forced reproduction, and final check restore all passes.
- `readout.html` names EPA, shows all three dates, and explains the Revision 2 negative-generation lesson.
## Exclusions
- No MLflow server or serving endpoint.
- No CI workflow.
- No other eGRID years.
- No cloud service.
- No new account, payment card, or paid API.
- No hyperparameter search, feature store, or deployment platform.
- No AI-generated build code. AI is used once at close-out.
The requirements describe what must be proven. The decision records now constrain how the project produces that proof.
- Create decisions.md inside the docs folder.
- Add the first two decision records by copying this section:
# Decision Records
## DR-001: Use the pipeline before a fancy model
Status: Accepted
Context: The lesson is about lineage, data contracts, reproducibility, and evidence rather than optimization.
Decision: Use one `GradientBoostingClassifier` with a fixed seed and defaults apart from `random_state`.
Consequence: ROC AUC proves scoring occurred, not that the model is production-optimal.
## DR-002: Put configuration outside Python
Status: Accepted
Context: Seed, holdout, and fuel categories should be reviewable without editing code.
Decision: Store them in `params.yaml` and declare DVC parameter dependencies.
Consequence: Configuration changes invalidate the stage and appear in `dvc.lock`.
Why begin with these decisions?
- The first record keeps the lesson focused on lineage instead of model optimization.
- The second record makes training configuration reviewable outside Python.
- Save docs/decisions.md.
- Review the file. You should see two accepted decisions with context and consequences.
Decision sections look uneven?
Each record needs a status, context, decision, and consequence. Preserve the blank lines between those fields.
Need help comparing the structure? Help me check DR-001 and DR-002.
- Add the schema and data-versioning decisions below DR-002 by copying this section:
## DR-003: Make the schema a code-level gate
Status: Accepted
Context: A workbook can open while required columns disappear or too few rows remain.
Decision: Assert required columns and minimum row count before feature engineering.
Consequence: Bad inputs stop before fitting and leave visible evidence.
## DR-004: Use DVC for workbook bytes and pipeline state
Status: Accepted
Context: Git should version code and metadata, not large Excel workbooks.
Decision: Track `data/egrid.xlsx` with DVC, define training in `dvc.yaml`, retain `dvc.lock`, and use an external local remote.
Consequence: A clean clone needs `dvc pull`, making the remote's value observable.
How do these records protect data?
- The schema gate stops incompatible workbooks before model fitting.
- DVC preserves workbook bytes outside Git while keeping their pointer under version control.
- Save docs/decisions.md.
- Review DR-003 and DR-004. You should see a visible failure policy plus a clean-clone recovery policy.
Missing the recovery consequence?
Check the final line of DR-004. It must explain why the clean clone needs a DVC pull.
Still unsure? Help me verify the DVC decision.
- Add the local registry and registration decisions below DR-004 by copying this section:
## DR-005: Use local SQLite with no MLflow server
Status: Accepted
Context: Model Registry needs a database-backed store, but a server adds unrelated setup.
Decision: Use `$HOME/egrid-registry/registry.db` and `$HOME/egrid-registry/artifacts` directly.
Consequence: Tracking and registry APIs work with no account, port, or service.
## DR-006: Separate tracking runs from registered versions
Status: Accepted
Context: Validation can create diagnostic runs, but only a selected run per revision becomes named.
Decision: `train.py` logs runs. `register.py` explicitly registers current `model_uri` and moves `champion`.
Consequence: The registry has one selected version per source revision even when validation reruns exist.
Why use a local registry?
A local SQLite database gives MLflow a database-backed registry without introducing a server lifecycle.
The separate registration step keeps diagnostic runs out of the curated model-version history.
- Save docs/decisions.md.
- Review DR-005. You should see local registry paths with no account or service requirement.
Registry roles feel mixed together?
Check that train.py owns run logging. Check that register.py owns registration plus the champion alias.
Need help separating the responsibilities? Help me compare tracking runs with registered versions.
- Complete docs/decisions.md by adding the modeling and AI-use decisions:
## DR-007: Use a median label and ROC AUC
Status: Accepted
Context: The project needs a compact binary target and ranking metric.
Decision: Label rates above the filtered median and score positive-class probabilities on a stratified 25 percent holdout.
Consequence: The label is lesson-specific, not an EPA policy threshold.
## DR-008: Limit AI to presentation
Status: Accepted
Context: The learner must own build logic and still practice communication.
Decision: Use no AI prompts before close-out. One final prompt may create the teach-back, AAR, and readout from evidence.
Consequence: AI summarizes observed work but does not author or repair the pipeline.
What boundaries finish the design?
- The median label gives the lesson a compact binary target without claiming an EPA threshold.
- The AI boundary keeps implementation work under learner control.
- Save docs/decisions.md.
- Count the decision headings. You should see exactly eight records from DR-001 through DR-008.
Decision count does not equal eight?
Search the file for headings beginning with DR-. Compare the sequence with the complete reference below.
Need help locating the gap? Help me find the missing decision record.
✔️ Awesome, I've got everything!
Your eight decisions now constrain the model, data, registry, validation, and presentation choices.
ⓧ I'd like to double check the full code
- Compare docs/decisions.md with this complete reference:
# Decision Records
## DR-001: Use the pipeline before a fancy model
Status: Accepted
Context: The lesson is about lineage, data contracts, reproducibility, and evidence rather than optimization.
Decision: Use one `GradientBoostingClassifier` with a fixed seed and defaults apart from `random_state`.
Consequence: ROC AUC proves scoring occurred, not that the model is production-optimal.
## DR-002: Put configuration outside Python
Status: Accepted
Context: Seed, holdout, and fuel categories should be reviewable without editing code.
Decision: Store them in `params.yaml` and declare DVC parameter dependencies.
Consequence: Configuration changes invalidate the stage and appear in `dvc.lock`.
## DR-003: Make the schema a code-level gate
Status: Accepted
Context: A workbook can open while required columns disappear or too few rows remain.
Decision: Assert required columns and minimum row count before feature engineering.
Consequence: Bad inputs stop before fitting and leave visible evidence.
## DR-004: Use DVC for workbook bytes and pipeline state
Status: Accepted
Context: Git should version code and metadata, not large Excel workbooks.
Decision: Track `data/egrid.xlsx` with DVC, define training in `dvc.yaml`, retain `dvc.lock`, and use an external local remote.
Consequence: A clean clone needs `dvc pull`, making the remote's value observable.
## DR-005: Use local SQLite with no MLflow server
Status: Accepted
Context: Model Registry needs a database-backed store, but a server adds unrelated setup.
Decision: Use `$HOME/egrid-registry/registry.db` and `$HOME/egrid-registry/artifacts` directly.
Consequence: Tracking and registry APIs work with no account, port, or service.
## DR-006: Separate tracking runs from registered versions
Status: Accepted
Context: Validation can create diagnostic runs, but only a selected run per revision becomes named.
Decision: `train.py` logs runs. `register.py` explicitly registers current `model_uri` and moves `champion`.
Consequence: The registry has one selected version per source revision even when validation reruns exist.
## DR-007: Use a median label and ROC AUC
Status: Accepted
Context: The project needs a compact binary target and ranking metric.
Decision: Label rates above the filtered median and score positive-class probabilities on a stratified 25 percent holdout.
Consequence: The label is lesson-specific, not an EPA policy threshold.
## DR-008: Limit AI to presentation
Status: Accepted
Context: The learner must own build logic and still practice communication.
Decision: Use no AI prompts before close-out. One final prompt may create the teach-back, AAR, and readout from evidence.
Consequence: AI summarizes observed work but does not author or repair the pipeline.
Map the system and build plan
The decision records explain each choice in isolation. The topology shows how those choices form one evidence chain across separate stores.
- Create topology.md inside the docs folder.
- Add the workbook and DVC section by copying this first topology block:
# Topology
```mermaid
flowchart LR
W1[Local Revision 1 workbook]
W2[Local Revision 2 workbook]
D[data/egrid.xlsx]
P[data/egrid.xlsx.dvc]
C[(DVC cache)]
R[(External local DVC remote)]
W1 -->|copy| D
W2 -->|swap| D
D -->|dvc add records MD5| P
D --> C
C -->|dvc push| R
What does the data path show?
The Mermaid flow separates learner-supplied workbooks from the tracked project path. The DVC pointer records identity while the cache plus remote preserve bytes.
- Save docs/topology.md.
- Inspect the source. You should see both revisions converge on data/egrid.xlsx before DVC records its state.
Data nodes do not connect?
Check the indentation before every node and connection. Preserve each identifier exactly as shown.
Need help tracing the first path? Help me inspect the workbook-to-remote flow.
- Extend docs/topology.md with the training and registry flow below the current diagram content:
Y[params.yaml]
T[train.py]
M[metrics.json]
RUN[MLflow run]
A[(Registry artifacts)]
DB[(SQLite registry.db)]
Y --> T
D --> T
T -->|schema, train, score| M
T -->|params, metrics, tags| RUN
T -->|logged model| A
RUN --> DB
A --> DB
G[register.py]
REG[egrid-rate-class versions]
ALIAS[champion alias]
M --> G
G -->|register model_uri| REG
REG --> DB
G -->|set alias| ALIAS
ALIAS --> REG
How does a run become a champion?
- Training sends metrics to the project file plus the MLflow run.
- The logged model enters the artifact store.
- Registration creates a curated version before the champion alias moves.
- Save docs/topology.md.
- Inspect the new nodes. You should see train.py connect data plus configuration to metrics, runs, and artifacts.
Registry loop looks disconnected?
Confirm that register.py receives metrics.json. Confirm that the champion alias points back to the registered versions.
Need help with the registry path? Help me trace the model-registration flow.
- Complete docs/topology.md with the clean-clone path and trust boundary:
REPO[Git repository]
CLONE[Clean clone]
CHECK[check.py]
REPO -->|git clone| CLONE
R -->|dvc pull| CLONE
CLONE -->|dvc repro| T
CLONE --> CHECK
DB -->|resolve champion and run| CHECK
D -->|recompute MD5| CHECK
M -->|compare ROC AUC| CHECK
REPO -->|git cat-file -e commit| CHECK
```
## Trust boundary
Git, the DVC remote, and MLflow registry are separate stores. The clean clone proves Git alone lacks workbook bytes. `check.py` proves an alias alone is insufficient without a reachable commit, matching data bytes, and matching score.
What does the trust boundary prove?
Git preserves code plus metadata. DVC recovers workbook bytes.
The registry resolves the champion run. The checker joins all three stores into one verifiable claim.
- Save docs/topology.md.
- Open docs/topology.md from the Explorer sidebar in VS Code.
- Press Ctrl+Shift+V to open the built-in Markdown preview.
- Use the diagram's zoom and pan controls until every label is readable.
You should see the clean clone receive code from Git plus workbook bytes from the DVC remote. The diagram is wide, so keep zooming until the labels are readable before you take your screenshot.
Mermaid preview does not render?
VS Code 1.121 and newer renders Mermaid in the built-in Markdown preview. If you still see raw code, confirm that the opening fence says mermaid and that the closing fence appears immediately before the trust-boundary heading.
For an older VS Code version, install Markdown Preview Mermaid Support with extension ID bierner.markdown-mermaid. This legacy extension is deprecated because its features moved into VS Code 1.121.
To skip the extension, open the Mermaid Live Editor. Paste only the lines between the opening and closing Mermaid fences into its Code panel.
Still seeing raw diagram text? Help me render the topology.
✔️ Awesome, I've got everything!
Your rendered topology now connects source data, training, registration, and clean-clone validation.
ⓧ I'd like to double check the full code
- Compare docs/topology.md with this complete reference:
# Topology
```mermaid
flowchart LR
W1[Local Revision 1 workbook]
W2[Local Revision 2 workbook]
D[data/egrid.xlsx]
P[data/egrid.xlsx.dvc]
C[(DVC cache)]
R[(External local DVC remote)]
W1 -->|copy| D
W2 -->|swap| D
D -->|dvc add records MD5| P
D --> C
C -->|dvc push| R
Y[params.yaml]
T[train.py]
M[metrics.json]
RUN[MLflow run]
A[(Registry artifacts)]
DB[(SQLite registry.db)]
Y --> T
D --> T
T -->|schema, train, score| M
T -->|params, metrics, tags| RUN
T -->|logged model| A
RUN --> DB
A --> DB
G[register.py]
REG[egrid-rate-class versions]
ALIAS[champion alias]
M --> G
G -->|register model_uri| REG
REG --> DB
G -->|set alias| ALIAS
ALIAS --> REG
REPO[Git repository]
CLONE[Clean clone]
CHECK[check.py]
REPO -->|git clone| CLONE
R -->|dvc pull| CLONE
CLONE -->|dvc repro| T
CLONE --> CHECK
DB -->|resolve champion and run| CHECK
D -->|recompute MD5| CHECK
M -->|compare ROC AUC| CHECK
REPO -->|git cat-file -e commit| CHECK
```
## Trust boundary
Git, the DVC remote, and MLflow registry are separate stores. The clean clone proves Git alone lacks workbook bytes. `check.py` proves an alias alone is insufficient without a reachable commit, matching data bytes, and matching score.
- Create build-plan.md inside the docs folder.
- Add the setup through build schedule by copying this first section:
# Build Plan
Total time: 45 minutes
## Setup: 6 minutes
- Verify Python and Git.
- Create and activate the virtual environment.
- Install and verify pinned dependencies.
- Initialize Git and DVC and configure the remote.
## Design: 5 minutes
- Write the brief and decisions.
- Write the topology.
- Write the plan and source map.
## Build: 22 minutes
- Data: 2 minutes.
- `train.py`: 8 minutes.
- `dvc.yaml` and first run: 3 minutes.
- `register.py`: 3 minutes.
- Swap and push: 2 minutes.
- `check.py`: 4 minutes.
Build floor: 20 minutes. Never cut below it.
Why protect the build time?
The schedule reserves most of the project for implementation. The build floor prevents documentation or setup from crowding out the working lineage pipeline.
- Save docs/build-plan.md.
- Add the setup, design, and build allocations. You should get 33 minutes before validation begins.
Timing does not add up?
Check the section headings against the supplied durations. Keep the build floor directly after the six build tasks.
Need help checking the schedule? Help me review the first three timing sections.
- Complete docs/build-plan.md with the validation and close-out schedule:
## Validation: 8 minutes
- Confirm one training-code commit, two source schema passes, and different hashes.
- Pull and rebuild from a clean clone.
- Inject Revision 1 and observe exit 1.
- Restore champion and regain all passes.
## Close-out: 4 minutes
- Ask once for the teach-back, AAR, and readout.
- Open the readout and verify AC-6.
How does the plan finish?
Validation reserves time for both success and intentional failure. Close-out turns the captured evidence into the final readout.
- Save docs/build-plan.md.
- Add all five phase durations. You should reach the complete 45-minute project schedule.
Plan does not total 45 minutes?
Confirm the five allocations are 6, 5, 22, 8, and 4 minutes. Check that no section was pasted twice.
Need help finding the mismatch? Help me audit the build-plan timing.
✔️ Awesome, I've got everything!
Your plan now protects implementation time while reserving space for validation and presentation.
ⓧ I'd like to double check the full code
- Compare docs/build-plan.md with this complete reference:
# Build Plan
Total time: 45 minutes
## Setup: 6 minutes
- Verify Python and Git.
- Create and activate the virtual environment.
- Install and verify pinned dependencies.
- Initialize Git and DVC and configure the remote.
## Design: 5 minutes
- Write the brief and decisions.
- Write the topology.
- Write the plan and source map.
## Build: 22 minutes
- Data: 2 minutes.
- `train.py`: 8 minutes.
- `dvc.yaml` and first run: 3 minutes.
- `register.py`: 3 minutes.
- Swap and push: 2 minutes.
- `check.py`: 4 minutes.
Build floor: 20 minutes. Never cut below it.
## Validation: 8 minutes
- Confirm one training-code commit, two source schema passes, and different hashes.
- Pull and rebuild from a clean clone.
- Inject Revision 1 and observe exit 1.
- Restore champion and regain all passes.
## Close-out: 4 minutes
- Ask once for the teach-back, AAR, and readout.
- Open the readout and verify AC-6.
Record sources and review the chain
The final design file links each important claim to its source. This keeps the later implementation review grounded in documented behavior.
- Create source-map.md inside the docs folder.
- Add the method and EPA sources by copying this first section:
# Source Map
## Method and principles
- CRISP-DM: https://www.ibm.com/docs/en/spss-modeler/saas?topic=dm-crisp-help-overview
- ML Test Score: https://research.google/pubs/the-ml-test-score-a-rubric-for-ml-production-readiness-and-technical-debt-reduction/
- Rules of Machine Learning: https://developers.google.com/machine-learning/guides/rules-of-ml
- Data schemas and feature tests: https://developers.google.com/machine-learning/testing-debugging/common/data-errors
## EPA eGRID2023
- Dates: https://www.epa.gov/egrid/detailed-data
- Release filenames: https://www.epa.gov/system/files/other-files/2025-06/egrid2023_release_notes.txt
- Revision details: https://www.epa.gov/egrid/known-issues-data-notes-and-revisions
- Technical guide: https://www.epa.gov/system/files/documents/2025-01/egrid2023_technical_guide.pdf
- Fuel codes: https://www.epa.gov/egrid/code-lookup
- Plant fields: https://geopub.epa.gov/ArcGIS/rest/services/myenv/myenvlayers/MapServer/9?f=pjson
What do these sources support?
The method section anchors the project in CRISP-DM plus executable data-quality practices. The EPA section anchors workbook history and plant-field meaning.
- Save docs/source-map.md.
- Review the two sections. You should see four method sources plus six EPA sources.
Source sections are incomplete?
Keep each source on its own Markdown list line. Check that the EPA section ends with the plant-fields source.
Need help counting the entries? Help me check the method and EPA source groups.
- Add the MLflow and DVC references below the EPA section:
## MLflow
- SQLite change: https://mlflow.org/releases/3.7.0/
- Backend store: https://mlflow.org/docs/latest/self-hosting/architecture/backend-store/
- Local SQLite: https://mlflow.org/docs/latest/ml/tracking/tutorials/local-database/
- MLflow 3 URIs: https://mlflow.org/docs/latest/ml/mlflow-3/
- Registry workflow: https://mlflow.org/docs/latest/ml/model-registry/workflow/
- Python client: https://mlflow.org/docs/latest/api_reference/python_api/mlflow.client.html
## DVC
- Data versioning and remote: https://doc.dvc.org/start
- Pipelines and lock file: https://doc.dvc.org/start/data-pipelines/data-pipelines
- Metrics: https://doc.dvc.org/user-guide/project-structure/dvcyaml-files
- Checkout: https://doc.dvc.org/command-reference/checkout
- Repro: https://doc.dvc.org/command-reference/repro
Why document both tools?
The MLflow references support local tracking plus registry behavior. The DVC references support data recovery plus reproducible pipeline execution.
- Save docs/source-map.md.
- Review the new sections. You should see six MLflow references plus five DVC references.
Tool references are mixed together?
Place every MLflow link under its own heading. Start the DVC heading immediately before the data-versioning source.
Need help sorting the entries? Help me separate the MLflow and DVC references.
- Complete docs/source-map.md with the API and installation references:
## APIs and installation
- pandas Excel: https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html
- pandas dummies: https://pandas.pydata.org/docs/reference/api/pandas.get_dummies.html
- PyYAML: https://pyyaml.org/wiki/PyYAMLDocumentation
- Gradient boosting: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html
- Split: https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html
- ROC AUC: https://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_auc_score.html
- Python: https://www.python.org/downloads/release/python-31316/
- Git: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
- Ollama: https://ollama.com/download/windows
- Gemma model: https://ollama.com/library/gemma3:270m
What does the final source group cover?
These references cover data loading, feature encoding, model fitting, evaluation, tool installation, and the optional close-out fallback.
- Save docs/source-map.md.
- Review the final section. You should see ten API and installation references.
Final source group looks short?
Check that the list begins with the pandas Excel reference. Confirm that it ends with the Gemma model reference.
Need help finding the missing entry? Help me audit the API and installation sources.
✔️ Awesome, I've got everything!
Your source map now supports the method, data, registry, versioning, modeling, and close-out choices.
ⓧ I'd like to double check the full code
- Compare docs/source-map.md with this complete reference:
# Source Map
## Method and principles
- CRISP-DM: https://www.ibm.com/docs/en/spss-modeler/saas?topic=dm-crisp-help-overview
- ML Test Score: https://research.google/pubs/the-ml-test-score-a-rubric-for-ml-production-readiness-and-technical-debt-reduction/
- Rules of Machine Learning: https://developers.google.com/machine-learning/guides/rules-of-ml
- Data schemas and feature tests: https://developers.google.com/machine-learning/testing-debugging/common/data-errors
## EPA eGRID2023
- Dates: https://www.epa.gov/egrid/detailed-data
- Release filenames: https://www.epa.gov/system/files/other-files/2025-06/egrid2023_release_notes.txt
- Revision details: https://www.epa.gov/egrid/known-issues-data-notes-and-revisions
- Technical guide: https://www.epa.gov/system/files/documents/2025-01/egrid2023_technical_guide.pdf
- Fuel codes: https://www.epa.gov/egrid/code-lookup
- Plant fields: https://geopub.epa.gov/ArcGIS/rest/services/myenv/myenvlayers/MapServer/9?f=pjson
## MLflow
- SQLite change: https://mlflow.org/releases/3.7.0/
- Backend store: https://mlflow.org/docs/latest/self-hosting/architecture/backend-store/
- Local SQLite: https://mlflow.org/docs/latest/ml/tracking/tutorials/local-database/
- MLflow 3 URIs: https://mlflow.org/docs/latest/ml/mlflow-3/
- Registry workflow: https://mlflow.org/docs/latest/ml/model-registry/workflow/
- Python client: https://mlflow.org/docs/latest/api_reference/python_api/mlflow.client.html
## DVC
- Data versioning and remote: https://doc.dvc.org/start
- Pipelines and lock file: https://doc.dvc.org/start/data-pipelines/data-pipelines
- Metrics: https://doc.dvc.org/user-guide/project-structure/dvcyaml-files
- Checkout: https://doc.dvc.org/command-reference/checkout
- Repro: https://doc.dvc.org/command-reference/repro
## APIs and installation
- pandas Excel: https://pandas.pydata.org/docs/reference/api/pandas.read_excel.html
- pandas dummies: https://pandas.pydata.org/docs/reference/api/pandas.get_dummies.html
- PyYAML: https://pyyaml.org/wiki/PyYAMLDocumentation
- Gradient boosting: https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html
- Split: https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html
- ROC AUC: https://scikit-learn.org/stable/modules/generated/sklearn.metrics.roc_auc_score.html
- Python: https://www.python.org/downloads/release/python-31316/
- Git: https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
- Ollama: https://ollama.com/download/windows
- Gemma model: https://ollama.com/library/gemma3:270m
Before you perform the final review, which stores do you expect to remain separate in the rendered topology?
- Confirm that docs contains requirements-brief.md, decisions.md, topology.md, build-plan.md, and source-map.md.
- Reopen the rendered docs/topology.md preview in VS Code with Ctrl+Shift+V or return to your Mermaid Live Editor preview.
- Trace AC-1 to the five design files.
- Trace AC-2 to the planned Git commit plus data hash.
- Trace AC-3 to the schema checks plus the visible success line.
- Trace AC-4 to the DVC training stage plus metrics.json.
- Trace AC-5 to the champion registry version plus clean-clone checker.
- Trace AC-6 to the logs plus readout.html.
You should see Git, the DVC remote, and the MLflow registry as separate stores. Their evidence converges at check.py.
Cannot trace all six criteria?
Start in docs/requirements-brief.md. Match each criterion to a named file or visible result in the topology and build plan.
Need help completing the review? Help me trace AC-1 through AC-6.
That is the design gate complete. Next, you will pin Revision 1 and make the first schema-tested model run produce lineage evidence.
Train the Revision 1 Pipeline
Your five design documents now define the evidence contract before implementation. The next proof needs exact workbook bytes tied to a Git commit.
Revision 1 gives you the first model lineage to test. A DVC fingerprint plus an MLflow run creates the first proof chain. The missing registry alias will expose the limitation that you solve next.
In this step, get ready to:
- Pin the Revision 1 workbook with DVC and record its Git commit.
- Build the schema-tested classifier that captures code lineage plus data lineage.
- Run the committed DVC stage and inspect its visible evidence.
Pin Revision 1 with DVC
Git records code plus lightweight metadata. DVC records the content fingerprint for the workbook bytes. Together they distinguish this source revision from later workbook revisions.
- Create the data folder and copy Revision 1 into it by running these commands:
New-Item -ItemType Directory -Force data 2>&1 | Tee-Object -Append check-out.txt
Copy-Item "$HOME\egrid\egrid2023_data_rev1.xlsx" "data\egrid.xlsx" 2>&1 | Tee-Object -Append check-out.txt
What do these commands do?
- The first command creates the repository's data directory.
- The second command copies the learner-supplied Revision 1 bytes to data/egrid.xlsx.
- Both commands append their PowerShell output to check-out.txt for the final evidence review.
- Check the data folder in your repository view.
You should see egrid.xlsx inside the folder.
Workbook missing from the data folder?
Confirm that the learner-supplied file exists at $HOME\egrid\egrid2023_data_rev1.xlsx. Check that the destination is the repository's data folder.
If the copy still fails, help me diagnose the Revision 1 copy command.
- Create the DVC pointer and record the required Git commit by running these commands:
dvc add data/egrid.xlsx 2>&1 | Tee-Object -Append check-out.txt
git add . 2>&1 | Tee-Object -Append check-out.txt
git commit -m "Track eGRID2023 revision 1" 2>&1 | Tee-Object -Append check-out.txt
How is Revision 1 pinned?
- The DVC command hashes the workbook bytes and creates data/egrid.xlsx.dvc.
- The Git staging command collects the DVC pointer plus the repository metadata.
- The commit records the pointer under the message Track eGRID2023 revision 1.
- Read PowerShell's commit summary.
You should see a new commit with the Revision 1 message. Your repository should also contain data/egrid.xlsx.dvc.
DVC add or Git commit failed?
Confirm that the activated virtual environment still contains DVC. Check that PowerShell is still inside the initialized repository.
Git needs an author identity before it can create a commit. Set one for this repository only.
- Set your repository identity by running these commands:
git config user.name '[[GIT_USER_NAME="your real name"]]'
git config user.email '[[GIT_USER_EMAIL="you@example.com"]]'
- Retry the Revision 1 commit by running this command:
git commit -m "Track eGRID2023 revision 1"
For targeted help, help me troubleshoot the Revision 1 DVC pointer or Git commit.
Build the schema-tested training program
The model configuration belongs outside Python so that parameter changes remain reviewable. The training program then turns those values into executable schema checks plus repeatable model behavior.
- Create params.yaml inside the egrid-lineage folder.
- Add the model seed plus holdout size plus approved fuel categories by copying this configuration:
seed: 42
test_size: 0.25
fuels:
- BIOMASS
- COAL
- GAS
- GEOTHERMAL
- HYDRO
- NUCLEAR
- OFSL
- OIL
- OTHF
- SOLAR
- WIND
Why keep these values in YAML?
- The fixed seed makes repeated data splits reproducible.
- The holdout reserves 25 percent of the filtered rows for evaluation.
- The fuel list makes the approved categories visible without editing Python.
- Save params.yaml.
- Confirm that the file lists the seed plus holdout size plus all eleven fuel categories.
YAML values not lining up?
Use spaces before each fuel item. Keep every fuel beneath the fuels key.
For help checking the structure, help me compare my params.yaml file with the required configuration.
✔️ Awesome, I've got everything!
Great. Your model configuration is saved outside Python and ready for the DVC stage.
ⓧ I'd like to double check the full code
seed: 42
test_size: 0.25
fuels:
- BIOMASS
- COAL
- GAS
- GEOTHERMAL
- HYDRO
- NUCLEAR
- OFSL
- OIL
- OTHF
- SOLAR
- WIND
How to use this reference
Compare this full file with your saved params.yaml. Every value plus indentation level should match.
The training program has five responsibilities. It validates the workbook, builds the classifier, scores the holdout, records lineage in SQLite, and writes the visible evidence to metrics.json.
- Create train.py inside the egrid-lineage folder.
- Add the imports plus shared paths plus column contract by copying this first chunk:
import hashlib
import json
import subprocess
from pathlib import Path
import mlflow
import mlflow.sklearn
import pandas as pd
import yaml
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
DATA_PATH = Path("data/egrid.xlsx")
PARAMS_PATH = Path("params.yaml")
METRICS_PATH = Path("metrics.json")
EXPERIMENT_NAME = "egrid-lineage"
REQUIRED_COLUMNS = ["PLFUELCT", "NAMEPCAP", "CAPFAC", "PLNGENAN", "PLCO2RTA"]
FEATURE_COLUMNS = ["NAMEPCAP", "CAPFAC", "PLNGENAN", "PLFUELCT"]
TARGET_COLUMN = "PLCO2RTA"
What does this foundation define?
- The imports provide hashing, JSON output, Git process access, MLflow logging, workbook handling, YAML parsing, model training, and evaluation.
- The three paths keep the workbook input plus parameter input plus metrics output consistent.
- The column lists define the workbook contract plus model features plus target field.
- Save train.py.
- Confirm that the current last line sets TARGET_COLUMN to PLCO2RTA.
Imports or constants look different?
Check each import name against the pinned packages from the setup step. Preserve the capitalization of every EPA column name.
For a focused comparison, help me find a mismatch in my train.py imports or constants.
- Add the hash helper plus Git helper plus local MLflow configuration below TARGET_COLUMN by copying this chunk:
def md5_file(path: Path) -> str:
return hashlib.md5(path.read_bytes()).hexdigest()
def current_commit() -> str:
return subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()
def configure_mlflow() -> None:
registry_dir = Path.home() / "egrid-registry"
artifact_dir = registry_dir / "artifacts"
registry_dir.mkdir(parents=True, exist_ok=True)
artifact_dir.mkdir(parents=True, exist_ok=True)
mlflow.set_tracking_uri("sqlite:///" + (registry_dir / "registry.db").as_posix())
if mlflow.get_experiment_by_name(EXPERIMENT_NAME) is None:
mlflow.create_experiment(EXPERIMENT_NAME, artifact_location=artifact_dir.as_uri())
mlflow.set_experiment(EXPERIMENT_NAME)
How do these helpers preserve lineage?
- The MD5 helper fingerprints the exact bytes in data/egrid.xlsx.
- The Git helper reads the current commit from the repository.
- The MLflow helper creates the local registry directory plus artifact directory.
- The tracking URI points MLflow at $HOME/registry/registry.db.
- Save train.py.
- Confirm that configure_mlflow() ends by selecting the egrid-lineage experiment.
Helper indentation looks uneven?
Keep each function at the left edge of the file. Keep every function body indented by four spaces.
If the SQLite URI looks different, help me check the helper functions in train.py.
- Add the configuration load plus executable schema gate below configure_mlflow() by copying this chunk:
def main() -> None:
params = yaml.safe_load(PARAMS_PATH.read_text(encoding="utf-8"))
seed = int(params["seed"])
test_size = float(params["test_size"])
fuels = list(params["fuels"])
frame = pd.read_excel(DATA_PATH, sheet_name="PLNT23", header=1)
missing = [column for column in REQUIRED_COLUMNS if column not in frame.columns]
assert not missing, f"Missing required columns: {missing}"
plants = frame[
frame["PLFUELCT"].isin(fuels)
& (frame["PLNGENAN"] > 0)
& frame[TARGET_COLUMN].notna()
].dropna(subset=FEATURE_COLUMNS + [TARGET_COLUMN])
assert len(plants) >= 1000, f"Expected at least 1000 plants, found {len(plants)}"
print(f"SCHEMA OK n_plants={len(plants)}")
What does the schema gate protect?
- The YAML load reads the approved seed plus holdout size plus fuel categories.
- The workbook read targets the PLNT23 sheet with its second row as the header.
- The required-column assertion stops training when the expected EPA fields are absent.
- The row filter keeps approved fuels plus positive generation plus complete model fields.
- The final assertion requires at least 1,000 usable plants before fitting.
- Save train.py.
- Confirm that the current final line prints SCHEMA OK with the filtered plant count.
Schema section showing indentation problems?
Keep every line in main() indented by four spaces. Keep the continued filter conditions aligned inside the brackets.
For help preserving the filter logic, help me check the schema section in train.py.
- Add feature engineering plus model fitting plus MLflow logging after the SCHEMA OK line by copying this chunk:
median_rate = float(plants[TARGET_COLUMN].median())
labels = (plants[TARGET_COLUMN] > median_rate).astype(int)
features = pd.get_dummies(plants[FEATURE_COLUMNS], columns=["PLFUELCT"], dtype=float)
X_train, X_test, y_train, y_test = train_test_split(
features, labels, test_size=test_size, random_state=seed, stratify=labels
)
model = GradientBoostingClassifier(random_state=seed)
model.fit(X_train, y_train)
roc_auc = float(roc_auc_score(y_test, model.predict_proba(X_test)[:, 1]))
data_md5 = md5_file(DATA_PATH)
git_commit = current_commit()
configure_mlflow()
with mlflow.start_run():
mlflow.log_params({"seed": seed, "test_size": test_size, "fuels": ",".join(fuels)})
mlflow.log_metrics(
{"roc_auc": roc_auc, "n_plants": float(len(plants)), "median_rate": median_rate}
)
mlflow.set_tags({"data_md5": data_md5, "git_commit": git_commit})
model_info = mlflow.sklearn.log_model(model, name="model")
How does the model run become auditable?
- The median rate creates the binary label used in this lesson.
- One-hot encoding converts PLFUELCT into numeric indicator columns.
- The stratified split reserves the configured holdout while preserving label balance.
- Gradient boosting fits the classifier before ROC AUC scores its positive-class probabilities.
- MLflow stores parameters plus metrics plus the data hash plus the Git commit.
- The logged model returns the model URI used by the next section.
- Save train.py.
- Confirm that this chunk ends with the assignment to model_info.
Model section not nested inside main?
Keep this entire chunk indented by four spaces so it remains inside main(). Keep the MLflow logging statements indented inside the run context.
For help checking the nesting, help me inspect the model and MLflow section in train.py.
- Finish main() with the metrics artifact plus visible evidence by copying this final chunk:
metrics = {
"roc_auc": roc_auc,
"n_plants": int(len(plants)),
"median_rate": median_rate,
"data_md5": data_md5,
"git_commit": git_commit,
"model_uri": model_info.model_uri,
}
METRICS_PATH.write_text(json.dumps(metrics, indent=2), encoding="utf-8")
print(
"METRICS "
f"roc_auc={roc_auc:.6f} n_plants={len(plants)} median_rate={median_rate:.6f} "
f"data_md5={data_md5} git_commit={git_commit} model_uri={model_info.model_uri}"
)
if __name__ == "__main__":
main()
What evidence does the final chunk expose?
- The metrics dictionary combines score plus plant count plus median rate plus both lineage identifiers plus model URI.
- The JSON write saves those values in metrics.json.
- The final print produces one compact METRICS line for the evidence log.
- The module guard runs main() when DVC executes the script.
- Save train.py.
- Confirm that the final line calls main().
Metrics section showing a syntax problem?
Check the commas inside the metrics dictionary. Check the closing parentheses around the final print call.
For help locating a mismatch, help me inspect the final metrics section in train.py.
✔️ Awesome, I've got everything!
Excellent. Save train.py before you define the DVC stage.
ⓧ I'd like to double check the full code
import hashlib
import json
import subprocess
from pathlib import Path
import mlflow
import mlflow.sklearn
import pandas as pd
import yaml
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.metrics import roc_auc_score
from sklearn.model_selection import train_test_split
DATA_PATH = Path("data/egrid.xlsx")
PARAMS_PATH = Path("params.yaml")
METRICS_PATH = Path("metrics.json")
EXPERIMENT_NAME = "egrid-lineage"
REQUIRED_COLUMNS = ["PLFUELCT", "NAMEPCAP", "CAPFAC", "PLNGENAN", "PLCO2RTA"]
FEATURE_COLUMNS = ["NAMEPCAP", "CAPFAC", "PLNGENAN", "PLFUELCT"]
TARGET_COLUMN = "PLCO2RTA"
def md5_file(path: Path) -> str:
return hashlib.md5(path.read_bytes()).hexdigest()
def current_commit() -> str:
return subprocess.check_output(["git", "rev-parse", "HEAD"], text=True).strip()
def configure_mlflow() -> None:
registry_dir = Path.home() / "egrid-registry"
artifact_dir = registry_dir / "artifacts"
registry_dir.mkdir(parents=True, exist_ok=True)
artifact_dir.mkdir(parents=True, exist_ok=True)
mlflow.set_tracking_uri("sqlite:///" + (registry_dir / "registry.db").as_posix())
if mlflow.get_experiment_by_name(EXPERIMENT_NAME) is None:
mlflow.create_experiment(EXPERIMENT_NAME, artifact_location=artifact_dir.as_uri())
mlflow.set_experiment(EXPERIMENT_NAME)
def main() -> None:
params = yaml.safe_load(PARAMS_PATH.read_text(encoding="utf-8"))
seed = int(params["seed"])
test_size = float(params["test_size"])
fuels = list(params["fuels"])
frame = pd.read_excel(DATA_PATH, sheet_name="PLNT23", header=1)
missing = [column for column in REQUIRED_COLUMNS if column not in frame.columns]
assert not missing, f"Missing required columns: {missing}"
plants = frame[
frame["PLFUELCT"].isin(fuels)
& (frame["PLNGENAN"] > 0)
& frame[TARGET_COLUMN].notna()
].dropna(subset=FEATURE_COLUMNS + [TARGET_COLUMN])
assert len(plants) >= 1000, f"Expected at least 1000 plants, found {len(plants)}"
print(f"SCHEMA OK n_plants={len(plants)}")
median_rate = float(plants[TARGET_COLUMN].median())
labels = (plants[TARGET_COLUMN] > median_rate).astype(int)
features = pd.get_dummies(plants[FEATURE_COLUMNS], columns=["PLFUELCT"], dtype=float)
X_train, X_test, y_train, y_test = train_test_split(
features, labels, test_size=test_size, random_state=seed, stratify=labels
)
model = GradientBoostingClassifier(random_state=seed)
model.fit(X_train, y_train)
roc_auc = float(roc_auc_score(y_test, model.predict_proba(X_test)[:, 1]))
data_md5 = md5_file(DATA_PATH)
git_commit = current_commit()
configure_mlflow()
with mlflow.start_run():
mlflow.log_params({"seed": seed, "test_size": test_size, "fuels": ",".join(fuels)})
mlflow.log_metrics(
{"roc_auc": roc_auc, "n_plants": float(len(plants)), "median_rate": median_rate}
)
mlflow.set_tags({"data_md5": data_md5, "git_commit": git_commit})
model_info = mlflow.sklearn.log_model(model, name="model")
metrics = {
"roc_auc": roc_auc,
"n_plants": int(len(plants)),
"median_rate": median_rate,
"data_md5": data_md5,
"git_commit": git_commit,
"model_uri": model_info.model_uri,
}
METRICS_PATH.write_text(json.dumps(metrics, indent=2), encoding="utf-8")
print(
"METRICS "
f"roc_auc={roc_auc:.6f} n_plants={len(plants)} median_rate={median_rate:.6f} "
f"data_md5={data_md5} git_commit={git_commit} model_uri={model_info.model_uri}"
)
if __name__ == "__main__":
main()
How to use this reference
Compare this full file with your saved train.py from top to bottom. The paths plus identifiers plus indentation must match exactly.
Run the committed DVC stage
A DVC stage joins the script, workbook, parameters, and output into one reproducible unit. Committing the stage before execution ensures the recorded Git commit already contains the exact training logic.
- Create dvc.yaml inside the egrid-lineage folder.
- Define the training command plus dependencies plus parameters plus uncached metric by copying this configuration:
stages:
train:
cmd: python train.py
deps:
- train.py
- data/egrid.xlsx
params:
- seed
- test_size
- fuels
metrics:
- metrics.json:
cache: false
What does the stage track?
- The command runs train.py with the activated environment.
- The dependency list invalidates the stage when the script or workbook changes.
- The parameter list invalidates the stage when the configured seed plus holdout plus fuel list changes.
- The metrics entry keeps metrics.json available for Git evidence.
- Save dvc.yaml.
- Confirm that metrics.json has cache: false beneath it.
DVC stage structure looks wrong?
Keep train nested beneath stages. Keep dependencies plus parameters plus metrics nested beneath train.
For help checking the nesting, help me compare my dvc.yaml file with the required stage.
✔️ Awesome, I've got everything!
Your DVC stage now connects the exact inputs to the generated lineage metric.
ⓧ I'd like to double check the full code
stages:
train:
cmd: python train.py
deps:
- train.py
- data/egrid.xlsx
params:
- seed
- test_size
- fuels
metrics:
- metrics.json:
cache: false
How to use this reference
Compare the complete stage with your saved dvc.yaml. DVC depends on the exact nesting shown here.
- Commit the complete training stage before its first run by running these commands:
git add train.py params.yaml dvc.yaml requirements.txt .gitignore 2>&1 | Tee-Object -Append check-out.txt
git commit -m "Add the training stage" 2>&1 | Tee-Object -Append check-out.txt
Why commit before training?
The training run reads the current Git commit into its MLflow tags. This commit must already contain train.py plus params.yaml plus dvc.yaml.
- Read the Git commit summary in PowerShell.
You should see a commit named Add the training stage.
Training-stage commit blocked?
Git needs an author identity before it can create a commit. Set one for this repository only.
- Set your repository identity by running these commands:
git config user.name '[[GIT_USER_NAME="your real name"]]'
git config user.email '[[GIT_USER_EMAIL="you@example.com"]]'
- Retry the training-stage commit by running this command:
git commit -m "Add the training stage"
If Git reports no staged changes, confirm that train.py, params.yaml, and dvc.yaml are saved in the repository.
For targeted help, help me troubleshoot the training-stage commit.
Before you run the stage, do you expect Revision 1 to satisfy the data contract?
- Reproduce the committed training stage and capture its evidence by running this command:
dvc repro 2>&1 | Tee-Object -Append check-out.txt
What should you see?
PowerShell first prints SCHEMA OK with at least 1,000 filtered plants. This proves Revision 1 passed the executable data contract.
PowerShell then prints one METRICS line containing roc_auc, data_md5, git_commit, and model_uri.
The run also creates metrics.json plus dvc.lock plus the local SQLite tracking database plus the model artifacts.
That is the first full lineage run complete. Revision 1 now has a visible schema pass plus score plus workbook fingerprint plus code commit plus model location.
Training stage did not reach METRICS?
If the schema assertion stops the run, confirm that data/egrid.xlsx contains the Revision 1 workbook. Check that the workbook includes the PLNT23 sheet.
If MLflow setup fails, confirm that the virtual environment remains activated. Confirm that the pinned dependencies from requirements.txt remain installed.
For help using the captured output, help me diagnose why dvc repro stopped before the METRICS line.
Your first model run now carries its exact code commit plus Revision 1 data fingerprint. Next, you will register that run, train Revision 2, and create the stable champion reference that this run still lacks.
Register Revision 2 and Add the Check
Your Revision 1 run already records its workbook hash in MLflow. Its source commit also exists in Git. The model still has only a run-local URI.
Registration gives each source run a durable version in the MLflow Model Registry. You will register both source revisions. Then you will point champion at Revision 2. The clean clone carries the DVC pointer through Git. The workbook bytes remain outside the clone.
In this step, get ready to:
- Register Revision 1 as the first version of egrid-rate-class.
- Replace the tracked workbook with Revision 2 before registering version 2 under champion.
- Prove the clean clone lacks workbook bytes by running the committed checker.
Register Revision 1
A registered model version connects a stable model name to one selected MLflow run. The source run keeps the workbook fingerprint plus the training commit.
- Create register.py in the current repository by using your code editor.
- Build the registration program by pasting this complete code into register.py:
import json
from pathlib import Path
import mlflow
from mlflow import MlflowClient
METRICS_PATH = Path("metrics.json")
MODEL_NAME = "egrid-rate-class"
ALIAS = "champion"
def configure_mlflow() -> None:
registry_db = Path.home() / "egrid-registry" / "registry.db"
mlflow.set_tracking_uri("sqlite:///" + registry_db.as_posix())
def main() -> None:
configure_mlflow()
metrics = json.loads(METRICS_PATH.read_text(encoding="utf-8"))
model_version = mlflow.register_model(metrics["model_uri"], MODEL_NAME)
client = MlflowClient()
client.set_registered_model_alias(MODEL_NAME, ALIAS, model_version.version)
assert model_version.run_id, "Registered model version has no source run"
run = client.get_run(model_version.run_id)
print(
f"REGISTERED version={model_version.version} "
f"data_md5={run.data.tags['data_md5']} "
f"roc_auc={run.data.metrics['roc_auc']:.6f}"
)
if __name__ == "__main__":
main()
How Registration Preserves Lineage
- The constants give the registered model a stable name plus the champion alias.
- The configuration function connects to the existing local SQLite database.
- The main function reads the current model URI from metrics.json.
- The source run supplies the registered workbook hash plus the ROC AUC value.
- Save register.py.
✔️ Awesome, I've got everything!
Your registration program is saved. It is ready to promote the current Revision 1 run.
ⓧ I'd like to double check the full code
import json
from pathlib import Path
import mlflow
from mlflow import MlflowClient
METRICS_PATH = Path("metrics.json")
MODEL_NAME = "egrid-rate-class"
ALIAS = "champion"
def configure_mlflow() -> None:
registry_db = Path.home() / "egrid-registry" / "registry.db"
mlflow.set_tracking_uri("sqlite:///" + registry_db.as_posix())
def main() -> None:
configure_mlflow()
metrics = json.loads(METRICS_PATH.read_text(encoding="utf-8"))
model_version = mlflow.register_model(metrics["model_uri"], MODEL_NAME)
client = MlflowClient()
client.set_registered_model_alias(MODEL_NAME, ALIAS, model_version.version)
assert model_version.run_id, "Registered model version has no source run"
run = client.get_run(model_version.run_id)
print(
f"REGISTERED version={model_version.version} "
f"data_md5={run.data.tags['data_md5']} "
f"roc_auc={run.data.metrics['roc_auc']:.6f}"
)
if __name__ == "__main__":
main()
What to Compare
Check each import plus each constant against your file. Then compare both functions through the final call to main().
- Register the current Revision 1 run by running:
python register.py 2>&1 | Tee-Object -Append check-out.txt
What Does This Command Do?
The command registers the model URI currently stored in metrics.json. PowerShell prints the registration evidence on screen while appending it to check-out.txt.
You will see a line beginning with REGISTERED version=1. The line also includes Revision 1's data hash plus its ROC AUC value.
- Record the first registration hash as your Revision 1 MD5.
That is the first registry link complete. Revision 1 now has a stable version with its source hash attached.
Registration Did Not Complete?
Confirm that metrics.json still contains the Revision 1 model URI. Confirm that the activated environment contains MLflow.
If the source run cannot be found, check that $HOME\registry\registry.db is still available. Help me diagnose why register.py cannot register my current MLflow model URI.
Pin and register Revision 2
The registered Revision 1 version now preserves the first source lineage. Changing the DVC pointer before training makes Revision 2 produce its own traceable run.
- Replace the tracked workbook with Revision 2 by running these commands:
Copy-Item "$HOME\egrid\egrid2023_data_rev2.xlsx" "data\egrid.xlsx" 2>&1 | Tee-Object -Append check-out.txt
dvc add data/egrid.xlsx 2>&1 | Tee-Object -Append check-out.txt
git add data/egrid.xlsx.dvc 2>&1 | Tee-Object -Append check-out.txt
git commit -m "Pin eGRID2023 revision 2" 2>&1 | Tee-Object -Append check-out.txt
What Changes Here?
- The copy command places the exact learner-supplied Revision 2 bytes at data/egrid.xlsx.
- The DVC command updates data/egrid.xlsx.dvc with the new content fingerprint.
- The Git commit records the pointer before the training stage reads Revision 2.
You will see a new commit named Pin eGRID2023 revision 2. The training run can now cite the commit that pins the second workbook.
Revision 2 Pointer Did Not Update?
Confirm that the learner-supplied Revision 2 workbook exists under $HOME\egrid. Check that the destination path remains data\egrid.xlsx.
If Git reports no pointer change, confirm that Revision 2 contains different workbook bytes. Help me diagnose why DVC did not update my Revision 2 workbook pointer.
Before you reproduce the stage, do you expect the second registration to retain Revision 1's hash or produce a different one?
- Train Revision 2 before registering its resulting model by running:
dvc repro 2>&1 | Tee-Object -Append check-out.txt
python register.py 2>&1 | Tee-Object -Append check-out.txt
How the Second Version Is Created
DVC reruns the training stage because its workbook dependency changed. The stage updates metrics.json plus dvc.lock.
The registration program promotes the new model URI as version 2. It also moves champion to that version.
You will first see SCHEMA OK for at least 1,000 filtered plants. A new METRICS line follows. The registration output then begins with REGISTERED version=2.
- Record the second registration hash as your Revision 2 MD5.
- Compare your Revision 1 MD5 with your Revision 2 MD5.
The two values should differ. That difference proves each registered version points back to different workbook bytes.
Second Registration Missing?
Confirm that dvc repro printed a fresh schema result plus a new metrics line. Check that metrics.json contains Revision 2's model URI.
If version 2 exists with the first hash, confirm that the Revision 2 copy completed before dvc add. Help me trace why my second registered model has the wrong data hash.
- Record the Revision 2 run state before pushing its workbook object by running:
git add register.py metrics.json dvc.lock 2>&1 | Tee-Object -Append check-out.txt
git commit -m "Record both runs" 2>&1 | Tee-Object -Append check-out.txt
dvc push 2>&1 | Tee-Object -Append check-out.txt
Why Push the Current Data?
The Git commit preserves the registration program plus the current metrics and lock state. The DVC push copies Revision 2's workbook object into $HOME\egrid-dvc-remote.
A later clean clone can use the pointer from Git to retrieve those exact bytes from the remote.
You will see the Git commit complete. DVC will report that the current data object has been pushed or is already present.
Data Push Failed?
Confirm that the default local remote still points to $HOME\egrid-dvc-remote. Check that the directory is writable.
If DVC cannot find the workbook object, confirm that dvc add completed after the Revision 2 copy. Help me diagnose why DVC cannot push my Revision 2 workbook to the local remote.
- Filter the saved registration evidence into two screenshot-ready lines by running this command:
Select-String -Path check-out.txt -Pattern 'REGISTERED version' -CaseSensitive
You should see REGISTERED version=1 and REGISTERED version=2 with different data_md5 values.
Add the lineage checker
The registry now has a stable champion. A trustworthy consumer still needs to verify the champion's commit plus its workbook bytes and score.
- Create check.py in the current repository by using your code editor.
- Add the checker configuration plus its helper functions by pasting this first section:
import hashlib
import json
import subprocess
import sys
from pathlib import Path
import mlflow
from mlflow import MlflowClient
DATA_PATH = Path("data/egrid.xlsx")
METRICS_PATH = Path("metrics.json")
MODEL_NAME = "egrid-rate-class"
ALIAS = "champion"
def configure_mlflow() -> None:
registry_db = Path.home() / "egrid-registry" / "registry.db"
mlflow.set_tracking_uri("sqlite:///" + registry_db.as_posix())
def md5_if_present(path: Path) -> str | None:
if not path.exists():
return None
return hashlib.md5(path.read_bytes()).hexdigest()
def check(name: str, ok: bool) -> bool:
print(f"{'PASS' if ok else 'FAIL'} {name}")
return ok
What Do These Helpers Check?
- The constants identify the local workbook plus the current metrics file.
- The MLflow helper connects the checker to the same local registry.
- The hash helper returns the workbook fingerprint when the file exists.
- The check helper prints one named result for each lineage condition.
- Complete check.py by adding this code below the check() function:
def main() -> None:
configure_mlflow()
client = MlflowClient()
champion = client.get_model_version_by_alias(MODEL_NAME, ALIAS)
assert champion.run_id, "Champion has no source run"
run = client.get_run(champion.run_id)
metrics = json.loads(METRICS_PATH.read_text(encoding="utf-8"))
registered_commit = run.data.tags["git_commit"]
commit_exists = subprocess.run(
["git", "cat-file", "-e", registered_commit],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
).returncode == 0
data_matches = run.data.tags["data_md5"] == md5_if_present(DATA_PATH)
score_matches = abs(
float(run.data.metrics["roc_auc"]) - float(metrics["roc_auc"])
) <= 0.005
results = [
check("registered git_commit exists here", commit_exists),
check("data_md5 matches data/egrid.xlsx", data_matches),
check("roc_auc within 0.005", score_matches),
]
if all(results):
print("ALL CHECKS PASS")
return
sys.exit(1)
if __name__ == "__main__":
main()
How the Checker Rebuilds Trust
- The alias lookup resolves the source run behind champion.
- The commit check confirms that the registered training commit exists in the current clone.
- The data check compares the registered fingerprint with the local workbook bytes.
- The score check allows a maximum ROC AUC difference of 0.005.
- Save check.py.
✔️ Awesome, I've got everything!
Your checker is complete. It can now test the registered commit plus the local workbook fingerprint and current score.
ⓧ I'd like to double check the full code
import hashlib
import json
import subprocess
import sys
from pathlib import Path
import mlflow
from mlflow import MlflowClient
DATA_PATH = Path("data/egrid.xlsx")
METRICS_PATH = Path("metrics.json")
MODEL_NAME = "egrid-rate-class"
ALIAS = "champion"
def configure_mlflow() -> None:
registry_db = Path.home() / "egrid-registry" / "registry.db"
mlflow.set_tracking_uri("sqlite:///" + registry_db.as_posix())
def md5_if_present(path: Path) -> str | None:
if not path.exists():
return None
return hashlib.md5(path.read_bytes()).hexdigest()
def check(name: str, ok: bool) -> bool:
print(f"{'PASS' if ok else 'FAIL'} {name}")
return ok
def main() -> None:
configure_mlflow()
client = MlflowClient()
champion = client.get_model_version_by_alias(MODEL_NAME, ALIAS)
assert champion.run_id, "Champion has no source run"
run = client.get_run(champion.run_id)
metrics = json.loads(METRICS_PATH.read_text(encoding="utf-8"))
registered_commit = run.data.tags["git_commit"]
commit_exists = subprocess.run(
["git", "cat-file", "-e", registered_commit],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
check=False,
).returncode == 0
data_matches = run.data.tags["data_md5"] == md5_if_present(DATA_PATH)
score_matches = abs(
float(run.data.metrics["roc_auc"]) - float(metrics["roc_auc"])
) <= 0.005
results = [
check("registered git_commit exists here", commit_exists),
check("data_md5 matches data/egrid.xlsx", data_matches),
check("roc_auc within 0.005", score_matches),
]
if all(results):
print("ALL CHECKS PASS")
return
sys.exit(1)
if __name__ == "__main__":
main()
What to Compare
Confirm that your file contains all four constants plus all four functions. Check that the three result names match this reference exactly.
- Commit the checker before creating the clean clone by running these commands:
git add check.py 2>&1 | Tee-Object -Append check-out.txt
git commit -m "Add the check" 2>&1 | Tee-Object -Append check-out.txt
git log --oneline -- train.py 2>&1 | Tee-Object -Append check-out.txt
git clone . "$HOME\egrid-lineage-clean" 2>&1 | Tee-Object -Append check-out.txt
Push-Location "$HOME\egrid-lineage-clean"
What Does the Clone Contain?
The history command shows the commits that changed train.py. You should see exactly one line.
The clone receives Git history plus the DVC pointer. PowerShell then moves into $HOME\egrid-lineage-clean for the planned check.
You will see the clean clone complete. Its files include data/egrid.xlsx.dvc. The actual data/egrid.xlsx workbook is absent.
Clean Clone Was Not Created?
Confirm that $HOME\egrid-lineage-clean does not contain an older clone. Confirm that the source repository has no uncommitted checker changes.
If the clone exists but lacks check.py, confirm that the Add the check commit completed. Help me diagnose why my local clean clone is missing the committed checker.
Before you run the checker, do you think Git metadata alone can satisfy all three lineage checks?
- Test the clean clone before pulling its workbook by running:
python check.py 2>&1 | Tee-Object -Append check-out.txt
What the Checker Tests
The checker resolves version 2 through champion. It tests the registered commit plus the local workbook hash and current ROC AUC.
🚨 This Failure Is Planned
The first line is PASS registered git_commit exists here because the clone contains the source commit. The second line is FAIL data_md5 matches data/egrid.xlsx because the workbook bytes are absent.
The third line is PASS roc_auc within 0.005 because committed metrics.json still matches the registered score. You will not see ALL CHECKS PASS.
- Display the checker's exit code by running this command:
$LASTEXITCODE
You should see 1, which confirms that the missing workbook caused the planned failure.
- Return to the source repository by running:
Pop-Location
Where Are You Now?
PowerShell restores your previous repository location. The clean clone remains at $HOME\egrid-lineage-clean with its failed check preserved in check-out.txt.
That planned failure is useful evidence. You have proved that a clean Git clone contains lineage metadata while the source workbook still requires DVC recovery.
Your registry now has two source versions. The champion alias resolves to Revision 2. Next up, you will pull its exact workbook bytes into the clean clone before rebuilding the pipeline and turning every check into a pass.
Rebuild the Champion in a Clean Clone
The clean Git clone has already proved the gap in a code-only copy. Its workbook bytes are missing even though the repository metadata is present.
The DVC remote holds the exact bytes pinned by the pointer. The rebuilt workspace must prove it still matches the champion in your local MLflow Model Registry.
In this step, get ready to:
- Restore the pinned workbook bytes inside the existing clean clone.
- Force the training stage to rebuild from the restored workbook.
- Verify the champion lineage against the reconstructed workspace.
Recover the missing workbook
The clone's data/egrid.xlsx.dvc pointer identifies the workbook object stored in the external remote. Pulling that object turns the repository metadata back into a complete workspace.
- Enter the existing clone before restoring its tracked workbook by running these commands:
Push-Location "$HOME\egrid-lineage-clean"
dvc pull 2>&1 | Tee-Object -Append check-out.txt
What do these commands do?
- The first line enters the existing clean clone while remembering your source-repository location.
- The second line retrieves the object identified by data/egrid.xlsx.dvc from the configured local remote.
- The output is displayed in PowerShell while the same evidence is appended to check-out.txt.
You'll see DVC finish without an error. The clone now contains data/egrid.xlsx with the exact pinned workbook bytes.
Workbook not restored?
- Confirm $HOME\egrid-dvc-remote still exists on your machine.
- Check that .dvc/config still names the local remote as the default.
Still stuck? Help me diagnose why the clean clone cannot restore its pinned workbook.
Rebuild and verify the champion
A normal reproduction can skip a stage whose inputs appear unchanged. The --force flag makes the clone execute the training stage from the restored workbook.
- Force the existing training stage to rebuild by running this command:
dvc repro --force 2>&1 | Tee-Object -Append check-out.txt
Keep the source environment active
The clone has no virtual environment of its own. Run the rebuild in the same PowerShell window where (.venv) is active so the clone can use the source project's environment.
- If you opened a new PowerShell window, activate the source project's virtual environment first by running:
.venv\Scripts\Activate.ps1
DVC may suggest adding dvc.lock to Git after the rebuild. Leave dvc.lock and metrics.json uncommitted because this clone exists only to verify the rebuild.
💡 What does this rebuild prove?
- The forced stage rereads the restored workbook before running the schema checks.
- The training program creates a new metrics.json from the reconstructed workspace.
- This rebuild logs a new MLflow run.
- The champion alias stays on the registered model because this step never runs register.py.
This rebuild can pause while the workbook is read and the classifier is fitted. That wait means the clone is doing real work from the restored bytes.
You'll see SCHEMA OK followed later by METRICS.
Rebuild not completing?
- Confirm data/egrid.xlsx exists inside the clone.
- Confirm the activated virtual environment is still active if Python reports a missing package.
- Check that params.yaml remains unchanged from the committed clone.
Need another pair of eyes? Help me troubleshoot the forced DVC rebuild.
Before you run the checker, do you expect the restored workbook to match the registered champion this time?
- Test the reconstructed workspace against the champion by running this command:
python check.py 2>&1 | Tee-Object -Append check-out.txt
What does the checker compare?
- The first check proves that the registered Git commit is reachable from the clone.
- The second check compares the champion's recorded MD5 value with the restored workbook bytes.
- The third check compares the rebuilt ROC AUC with the registered score tolerance.
You'll see PASS registered git_commit exists here first. Next, you'll see PASS data_md5 matches data/egrid.xlsx.
Then you'll see PASS roc_auc within 0.005. The final line is ALL CHECKS PASS.
That's the core proof complete. The clean clone now matches the registered champion's lineage.
One of the checks still fails?
- Confirm the clone contains the registered commit if the commit check reports a failure.
- Repeat the remote pull if the workbook check reports a failure.
- Force the DVC stage again if the score check reports a failure.
Need help reading the failed check? Help me trace the mismatch to its lineage source.
Confirm the training-code history
Reproducibility also depends on stable training code. The file-specific history should show one commit for train.py.
Before the history check, how many commit lines do you expect to see for the training program?
- Confirm the training history from inside the clone by running these commands:
git log --oneline -- train.py 2>&1 | Tee-Object -Append check-out.txt
Pop-Location
What does this final check show?
- The history query scopes Git to train.py so unrelated commits are excluded.
- The output is appended to the clone's check-out.txt evidence log.
- The location command returns PowerShell to the source repository.
You'll see exactly one abbreviated commit with its subject line for train.py. This confirms the rebuild used the single committed training implementation.
Seeing more than one history line?
- Confirm the history query is scoped to train.py.
- Compare the clone branch with the source repository branch if the file history still differs.
History still looks unexpected? Help me compare the clean clone's training-code history with the expected single commit.
You have now rebuilt the champion's lineage from a clean clone. The source repository remains unchanged while the registered alias stays on the curated model.
Secret mission
Break, Restore, and Present the Evidence
A passing lineage check proves the clean clone matches the champion once. This challenge tests whether the checker rejects stale workbook bytes, recovers the accepted lineage, and turns the complete evidence trail into a reviewer-ready readout.
Clean Up Your Resources
Clean Up Your Resources
Your lineage lab runs entirely on your Windows machine, so keeping it has no ongoing cost. Choose whether to preserve the evidence, pause by closing PowerShell, or remove the local resources.
Resources you used:
- The source repository from earlier contains the Git history plus the final DVC metadata and Revision 2 evidence.
- The clean clone at $HOME\egrid-lineage-clean contains restored Revision 2 data plus preserved failure and recovery evidence.
- The external DVC remote at $HOME\egrid-dvc-remote stores the exact Revision 2 workbook object.
- The local MLflow registry under $HOME\registry contains SQLite metadata plus model artifacts for registered and diagnostic runs.
Keep everything running
No action is needed. Choose this option if you want to preserve the complete proof chain for future demonstrations or experiments.
- The source repository retains its code history, DVC metadata, design documents, evidence log, and metrics.
- The clean clone retains docs/TEACHBACK.md, docs/AAR.md, readout.html, the intentional stale-data failure, and the successful recovery evidence.
- The external DVC remote keeps the exact Revision 2 workbook object available for another clean rebuild.
- The registry retains both registered model versions.
- The champion alias still resolves to version 2.
- The registry artifacts preserve the unregistered diagnostic run alongside the selected model runs.
Pause - I'll come back to this later
Close the active shell to pause your work. Every repository, data object, registry record, and evidence artifact stays available for your return.
- Close the PowerShell window that has .venv activated.
- Keep the source repository unchanged with Revision 2 pinned.
- Keep $HOME\egrid-lineage-clean for clean-clone validation.
- Keep $HOME\egrid-dvc-remote for deterministic data restoration.
- Keep $HOME\registry for model lineage and registry evidence.
No background MLflow server was created, so pausing requires no additional shutdown.
Delete - I don't want to use this again
Remove the repositories, DVC remote, registry metadata, and model artifacts when you no longer need the lineage lab. Your original learner-supplied workbooks remain outside this cleanup.
Preserve Your Evidence First
Deleting the lab is the irreversible option, so preserve the evidence before you continue. The cleanup removes the Git history, clone evidence, remote data object, registry records, and model artifacts.
- Copy $HOME\egrid-lineage-clean\readout.html to an archive location outside every folder listed above.
- Copy check-out.txt from the source repository to the same archive location.
- Copy $HOME\egrid-lineage-clean\check-out.txt to the same archive location.
- Open each archived copy to confirm that its contents are readable.
- Confirm that train.py is present in the current source repository.
- Remove the complete local lineage lab by running this command:
if (-not (Test-Path -LiteralPath "train.py")) {
throw "Run this from the source repository folder that contains train.py."
}
$sourceRepository = (Get-Location).Path
Set-Location $HOME
Remove-Item -LiteralPath $sourceRepository -Recurse -Force
Remove-Item -LiteralPath "$HOME\egrid-lineage-clean" -Recurse -Force
Remove-Item -LiteralPath "$HOME\egrid-dvc-remote" -Recurse -Force
Remove-Item -LiteralPath "$HOME\egrid-registry" -Recurse -Force
@(
$sourceRepository
"$HOME\egrid-lineage-clean"
"$HOME\egrid-dvc-remote"
"$HOME\egrid-registry"
) | ForEach-Object {
"$_ exists: $(Test-Path -LiteralPath $_)"
}
What Does This Cleanup Remove?
- The first check requires train.py in the current folder before deletion begins.
- The $sourceRepository value captures the current repository path before PowerShell moves to your home folder.
- The first removal deletes the source repository plus its local DVC cache and virtual environment.
- The second removal deletes the clean clone plus its diagnostic evidence.
- The third removal deletes the external DVC remote.
- The fourth removal deletes the MLflow database plus every local model artifact.
- The final check reports whether each deleted path still exists.
You should see four lines ending with exists: False.
Does Any Path Still Exist?
A line ending with True identifies a folder that PowerShell could not remove.
- Close any editor using the remaining folder.
- Close any other PowerShell window using the remaining folder.
- Delete the exact remaining path from File Explorer.
If the folder still cannot be removed, help me identify which process is using this local project folder.
That completes the teardown. Your learner-supplied workbooks at $HOME\egrid stay untouched.
Nice Work!
Nice Work!
You did it! You built a local MLOps lineage lab that connects every eGRID model to verifiable code and data evidence. Your clean-clone rebuild proves the Revision 2 champion can be recovered without a server or cloud account.
You've learned how to:
- Turned five design-first documents into an executable data contract aligned with CRISP-DM. Required-column checks plus a minimum-row gate stop unsuitable workbook data before training.
- Tracked two eGRID revisions through DVC MD5 fingerprints plus Git commits. Logged each schema-tested classifier run with ROC AUC plus lineage values in MLflow Tracking.
- Registered model versions 1 and 2 in the MLflow Model Registry with champion pointing to Revision 2. Pulled the pinned workbook into a clean clone. Rebuilt the model until all three lineage checks passed.
- Secret Mission: Proved that stale Revision 1 bytes fail the MD5 lineage check with exit code 1. Restored Revision 2 through DVC. Produced docs/TEACHBACK.md, docs/AAR.md, plus readout.html from the recorded evidence.
Ready to quiz yourself?