Session M2-06 trainer guide: Repair and refactor a broken Python application¶
Delivery objective¶
Teach learners to repair a deliberately broken inventory-reconciliation application, then improve its design without changing its output. Keep each repair small enough to explain and check.
Check that each learner finishes with a working command-line application, added regression tests, and a completed repair log. Use file comparisons to check that the refactor preserved the supplied text and JSON outputs.
Important
Check GitHub Copilot access before the session. If Copilot access is missing or unavailable in the training environment, stop and do not continue.
One-hour plan¶
| Time | Segment |
|---|---|
| 0:00-0:08 | Start repairs from the failure output |
| 0:08-0:18 | Read Python failures in execution order |
| 0:18-0:30 | Demonstrate syntax and import recovery |
| 0:30-0:42 | Use fixtures to recover behavior |
| 0:42-0:52 | Add regression tests before refactoring |
| 0:52-1:00 | Refactor boundaries and hand off to the lab |
Trainer preparation¶
Before delivery:
- Open the repository in its Dev Container or a Codespace.
- Check Python 3.11 or later.
- Check that GitHub Copilot Chat and inline suggestions work.
- Create a private working copy of
lab/starter/. - Install
requirements-dev.txtin that copy. - Run the starter command and preserve the first failure.
- Run the solution tests.
- Keep
lab/solution/closed until the lab review. - Read the supplied
.github/lsp.json, preparepyright-langserver, and check CLI/planand/lsp test pythonin the copied repository.
Use these commands from the session directory:
cd lab/starter
python -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-dev.txt
python -m inventory_reconciliation \
--warehouse fixtures/input/warehouse.csv \
--ledger fixtures/input/ledger.csv \
--output-dir build
The expected first failure is SYN-01 in inventory_reconciliation/cli.py.
Do not fix later faults during this demonstration.
Teaching model¶
The repair ladder¶
Start at the first layer that prevents you from checking the next:
| Layer | Question | Check |
|---|---|---|
| Parse | Can Python read every imported file? | python -m compileall inventory_reconciliation |
| Import | Can the package and entry point load? | python -c "import inventory_reconciliation.cli" |
| Start | Can the command show help? | python -m inventory_reconciliation --help |
| Behavior | Does the output match the contract? | pytest -q and fixture comparison |
| Structure | Can the code change safely? | regression tests stay green |
Skipping a layer obscures the current failure. A test suite cannot check business logic while test collection is blocked by a syntax error.
One failure at a time¶
Teach this loop:
- Capture the exact command and failure.
- State one hypothesis.
- Ask Copilot for the smallest relevant explanation or change.
- Inspect the proposed edit.
- Run the narrowest useful check.
- Record the result in the repair log.
Have learners make the diagnosis rather than accept Copilot's suggestion without checking it. Use command output to test each hypothesis.
Controlled starter sequence¶
The starter uses named faults so every learner can compare progress without sharing source-level answers.
| Order | Fault | Surface | Intended signal |
|---|---|---|---|
| 1 | SYN-01 |
cli.py |
Parser stops at the first malformed function definition |
| 2 | SYN-02 |
service.py |
The next imported module cannot parse |
| 3 | IMP-01 |
cli.py |
Startup imports a module that does not exist |
| 4 | IMP-02 |
reporting.py |
Startup imports a symbol that does not exist |
| 5 | LOG-01 |
parsing | Duplicate SKU rows overwrite earlier quantities |
| 6 | LOG-02 |
reconciliation | SKUs present only in the ledger disappear |
| 7 | LOG-03 |
rules | Severity thresholds do not match the contract |
| 8 | STATE-01 |
service | Repeated runs leak results through global state |
| 9 | DESIGN-01 |
service/reporting | Rules are duplicated and control flow is tangled |
The first four faults should appear in order when learners use the checks in the lab. The behavior defects are deterministic and exposed by the supplied fixtures.
Prepared demonstration¶
1. Capture the first failure¶
Run:
python -m inventory_reconciliation \
--warehouse fixtures/input/warehouse.csv \
--ledger fixtures/input/ledger.csv \
--output-dir build
Read the traceback from the bottom. Point out the file, line, exception type, and source line. Ignore every later concern until Python can parse that file.
2. Ask Copilot about one failure¶
Use a prompt such as:
Explain this SyntaxError using only the shown file and traceback. Identify the
smallest syntax repair. Do not refactor or change behavior.
Show that a good repair prompt includes the traceback and names what may change.
3. Check the repair¶
Run:
The command should expose SYN-02, the next startup fault.
4. Stop before solving the lab¶
Show how to add an entry to repair-log.md:
| Field | Example |
|---|---|
| Failure | SYN-01 |
| Failure output | SyntaxError in cli.py |
| Hypothesis | Function header is incomplete |
| Change | Small syntax-only edit |
| Check | Re-run compile command |
| Result | Parser advances to SYN-02 |
Do not demonstrate the behavior fixes.
Behavioral contract¶
The fixture pair represents two views of stock:
warehouse.csvcontains physical counts;ledger.csvcontains expected counts.
The intended rules are:
- Trim surrounding whitespace and uppercase each SKU.
- Sum duplicate rows after normalization.
- Reject negative quantities with a row-specific error.
- Reconcile the union of SKUs from both files.
- Calculate
variance = warehouse - ledger. - Use
MATCHfor zero,SHORTbelow zero, andOVERabove zero. - Use severity
nonefor zero,reviewfor absolute variance 1-5, andurgentfor absolute variance 6 or more. - Sort report rows by SKU.
- Keep repeated runs isolated.
Show learners how the expected text and JSON files define the result. Do not let them change those files to make broken code pass.
Regression tests before refactoring¶
The starter tests cover the fixture-level contract and repeated-run isolation. Learners must add at least two focused tests:
- the boundary between
reviewandurgent; - rejection of a negative quantity with file and row context.
They may add more. Each test should name one rule and fail for one reason.
Refactoring targets¶
Keep these five jobs in separate files:
| Boundary | Responsibility |
|---|---|
parsing.py |
Read, validate, normalize, and aggregate CSV data |
rules.py |
Classify variance status and severity |
service.py |
Reconcile two quantity maps |
reporting.py |
Render stable text and JSON output |
cli.py |
Parse arguments, call the service, write files |
Explain that the starter mixes these responsibilities on purpose. Have learners remove the module-level result list, call one rule implementation, and turn nested branches into named functions.
Do not ask for a large rewrite. First add tests for the expected behavior. Then make one structural change at a time.
Spend five minutes of the refactor lab block on an actual Python LSP lookup.
Ask for the definition and references of severity_for; inspect the operation
or /lsp logs and check the returned locations. Then inspect service.py for
inline copies of the same rule. Those copies need not appear as symbol references.
Record both before approving /plan. Do not use LSP diagnostics to skip the
ordered syntax and import repairs.
Stop if the required server or lookup cannot run. A startup check or text search does not replace the operation. Keep the lab at 120 minutes by using this lookup inside the existing 25-minute refactor block.
Useful Copilot prompts¶
Repair prompt:
Use the traceback and this file only. Suggest the smallest change that resolves
the current failure. Preserve behavior and do not refactor.
Test prompt:
Write one pytest test for the severity boundary at absolute variance 6. Follow
the existing test style. Do not change production code.
Refactor prompt:
/plan The tests are green. Plan removal of global state and duplicated
classification logic using the checked references and inline duplicates.
Preserve public signatures and output order. Name protecting tests. Do not edit.
Review prompt:
Review the diff for behavior changes. Compare it with the supplied fixtures,
expected outputs, and tests. Call out any unproved assumption.
Facilitation notes¶
- Keep learners on the current failure. Later defects are distractions until the program reaches them.
- Require repair-log entries before broad edits.
- Use a new failure after a repair to decide what to investigate next.
- Reject changes to expected outputs unless the class first proves the contract is wrong.
- Ask learners to explain each Copilot suggestion before they accept it.
- Pair learners when one is stuck for more than eight minutes.
- Keep the solution hidden until the review.
Common wrong turns¶
| Wrong turn | Coaching response |
|---|---|
| Rewrite the application from scratch | Return to the first failing command and preserve the supplied interface |
| Ask Copilot to "fix everything" | Narrow the prompt to one failure and one file |
| Change fixtures to match broken output | Restate that fixtures are the contract |
| Refactor while imports still fail | Restore startup first |
| Remove the repeated-run test | Explain that global state often passes once and fails in a process |
| Mock all file reads | Keep at least one fixture-level test through the real parser |
Lab handoff¶
Learners submit:
- a completed repair log;
- a working command-line application;
- at least two added regression tests;
- unchanged expected-output fixtures;
- a refactored design with no process-wide result state;
- passing pytest output and generated reports.
Success means the same inputs produce the supplied outputs before and after the refactor.