Skip to content

Final assignment

  • After Module 6
  • about 4 hr

What you will be able to do

Show that you can change a real Python project safely on your own. The change has new data, new rules and new output. The tests, logs and README make it trustworthy. You work on your ticket-cleaner project, and you keep evidence of each part.

Before you startWhat to know and to have readyAbout 240 minutes
  • You completed Modules 1 to 6. Each part below names the lessons that it uses.
  • ticket-cleaner is in ~/projects/ticket-cleaner, as at the end of Quality and handoff: it runs with python -m ticket_cleaner, and python -m pytest and ruff check . pass.
  • You can do the assignment in more than one session. Work in small steps, and run the tests after each one.
  • If you use Git, commit before you start, so you can compare and go back. It is not required.

The help desk changed its system. The new export has two more columns:

Column Meaning Allowed values
channel How the customer contacted the help desk email, phone, chat
subject The short title of the ticket Any text that is not empty

The help desk lead asks for four changes:

  1. Check the new columns. A ticket without a known channel, or without a subject, is not valid. The old rules stay.
  2. Report by channel. The report has a new field, by_channel: the number of selected tickets for each channel, like by_category.
  3. A strict mode. A nightly job runs ticket-cleaner and must know when the data has problems. With the option --strict, the program writes the report as usual, but ends with exit code 3 if any record was rejected.
  4. A short log summary. At the level INFO, the program also logs one line with the counts, for example INFO ticket_cleaner: 20 rows: 12 valid, 8 rejected.

Files in the old format, without the new columns, are no longer supported. The program rejects their records, which is expected.

Download the two files of the new export into your data folder. They have the same 20 tickets: one is a spreadsheet export (CSV), and one is what the help desk’s web service sends (JSON).

Terminal window
curl -o data/tickets-v2.csv https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C02/data/tickets-v2.csv
curl -o data/tickets-v2.json https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C02/data/tickets-v2.json

Read tickets-v2.csv before you change any code. Predict which rows are valid, and why the others are not. Write your prediction down: you compare it with your program’s result in Part 2.

  • Make one change at a time. After each change, run python -m pytest. Do not start the next change while a test fails.
  • For each new rule, first write a test that fails. Then change the code until the test passes, as for the regression test in Focused tests.
  • Before you finish a part, run ruff format . and ruff check .. Both must pass.
  • Keep the structure: validation in parsing.py, types in records.py, calculations in report.py, the command in cli.py.
  • Make a file evidence.md outside the project folder, for example ~/projects/c02-evidence.md. After each part, copy the outputs that the part asks for into it.

Uses Structured records, Function contracts and Focused tests.

  1. In records.py, add the allowed channels, add channel and subject to the required fields, and add the two fields to Ticket.
  2. Run the tests. Many fail now, because the tests make records and tickets without the new fields. This is expected: see the common mistake at the end. Update the test data.
  3. Write tests for the new rules. A known channel is valid, also with capital letters and spaces. An unknown channel, such as fax, is a problem. An empty subject is a problem. Include the boundaries: a subject with only spaces, and a missing channel key.
  4. Change find_problems and to_ticket until all the tests pass. The problem text for an unknown channel follows the pattern of the status: unknown channel 'fax'.

Evidence for part 1:

  • The new tests (copy the test functions).
  • The output of python -m pytest, with all tests passing.

Uses Reading and writing files and Command-line interface.

  1. Add a function count_by_channel to report.py, with a test, and add by_channel to the summary.

  2. Add the --strict option to cli.py, with its help text. Add tests for exit code 3 with rejected records, and 0 with --strict and clean data.

  3. Run the program on the CSV file:

    Terminal window
    python -m ticket_cleaner data/tickets-v2.csv --output reports/v2.json --strict
  4. Check your result against these numbers. The output is 20 rows: 12 valid, 8 rejected. The rejected rows are 5, 6, 8, 9, 10, 11, 16 and 17. The exit code is 3. For the open tickets, selected is 7. If a number is different, use the method from Read failures before you change any code.

  5. Compare with your prediction from Get the data. For each row where you were wrong, write one sentence: what did the program see that you did not?

  6. Run the program on the JSON file, with a different output file. The two reports must be the same. Add a test that proves it.

Evidence for part 2:

  • reports/v2.json from step 3.
  • The terminal output of step 3, and the exit code.
  • Your prediction, and the sentences from step 5.

Uses Exceptions and logging and Configuration and secrets.

  1. Add the INFO summary line from the lead’s request 4. Run with LOG_LEVEL=info, and check that it appears. Run without it, and check that it does not.
  2. Run the program in each of these situations. Before each run, predict the output and the exit code. Record both, and the real result:
    1. A file that does not exist.
    2. A JSON file that is cut short (make one, as in Exceptions and logging).
    3. remote with a TICKETS_URL that gives 404.
    4. --status unknown.
    5. data/tickets-v2.csv without --strict.
  3. Check that no output, log line or file in the project contains a secret. If you tested with a token, it was only in an environment variable.

Evidence for part 3:

  • A table with the five situations: your prediction, the real last line, and the exit code.
  • The output with LOG_LEVEL=info for data/tickets-v2.json.

Uses Isolate dependencies and Quality and handoff.

  1. Update the README: the new columns, the --strict option, exit code 3, and the example command and output with data/tickets-v2.csv.
  2. If you changed requirements.txt, make the lock file again in a new environment.
  3. Do the fresh-copy test: a copy without .venv and reports, a new terminal, and only the README. Add every missing step to the README, and do it again until it works.
  4. In the fresh copy, run the program as the README says, python -m pytest, and ruff check ..

Evidence for part 4:

  • The README.
  • The output of the three commands in the fresh copy.
  • A list of the steps that you added to the README in step 3. “None” is a valid answer only if it was true on the first try.

There is no reviewer for this course yet. Review your own evidence with this table, or ask a colleague to use it. Mark each item as demonstrated, partially demonstrated or not yet demonstrated, with a reason. The course is complete when every item is demonstrated. You can improve an item and review it again as often as you need.

Required evidence Demonstrated when
Reproducible setup A fresh copy works with the README only. The lock file has only the packages from requirements.txt and what they need. No secret is in the project.
Correct handling of valid and invalid records The results match the numbers in Part 2 for both files. Every rejected record has its row number and every reason. Valid + rejected = all rows. Nothing is dropped silently.
Modular code and focused tests Each new rule and function has a test that compares an exact value, including boundaries and error paths. Each test was seen to fail before its code existed. The code stays in the module for its job. ruff check . passes.
Helpful logs and a runnable README Expected errors give one clear line and exit code 1, and wrong commands 2. --strict gives 3. A defect still shows its traceback. The README answers the questions from Quality and handoff, with the commands and the expected output.

A working happy path is not enough. Invalid records can be lost, or a failure path from Part 3 can give a wrong exit code. Then the item is not yet demonstrated, even if the report looks correct.

Common mistake

Many tests fail after one small change

Symptom: after you add channel and subject to Ticket, a large number of tests fail with:

TypeError: Ticket.__init__() missing 2 required positional arguments: 'channel' and 'subject'

Cause: the tests make Ticket objects and raw records with the old fields. The contract of Ticket changed, so every place that makes one must change too. The tests found every one of these places for you. That is their job.

Fix:

  1. Update the shared test data first: the valid_raw fixture, and the tickets fixture in test_report.py. Many tests use them, so many failures go away together.
  2. Run the tests again, and fix the remaining ones one at a time, from the top of the output.
  3. Do not delete a failing test to make the output green. If a test checks something that is no longer true, change what it checks, and say why in your evidence.

Recap

In this course, you went from a terminal and a first script to a project that another person can set up and trust:

  • Environment: a project folder, a virtual environment, and a lock file that makes the same environment anywhere.
  • Code: values, collections, decisions and loops, organised into functions with contracts, modules and dataclasses.
  • Data: CSV and JSON files and a web service, with validation that rejects bad records with a reason and drops nothing silently.
  • Reliability: tracebacks and breakpoints to find causes, exceptions for expected errors, logs with context, and tests for normal cases, boundaries and error paths.
  • Handoff: a command-line interface with exit codes, formatting and linting, and a README tested on a fresh copy.

These are the habits that every later course in the curriculum uses: SQL and data preparation, building an API, and the AI application courses.