Debug a Sensor-Data Pipeline

EE1021 Programming for Engineers · Lecture 6 Lab · 2026–2027

Basri Erdogan

Lab Outcomes

By the end of the lab, you should be able to:

  1. Reproduce and classify a defect
  2. Extract useful evidence from a traceback
  3. State expected and actual behavior before editing
  4. Repair syntax, runtime, and logic errors
  5. Use focused validation and exception handling
  6. Verify each repair with a regression test
  7. Explain one repair during an individual checkoff

Your Debugging Record

For every defect, record:

Input Expected Actual evidence Hypothesis Fix Retest

Do not edit until you can state a hypothesis supported by evidence.

Open starter/lecture6_lab.py and keep the supplied function names unchanged.

Pre-Lab Diagnostic

def celsius_to_fahrenheit(celsius):
    return celsius * 9 / 5 - 32

For an input of 20, the expected result is 68. The function returns 4.

  1. Is this syntax, runtime, or logic?
  2. What is the smallest focused repair?

Check: logic error; change - 32 to + 32, then test 20, 0, and a negative value.

Stage 1: Make the Program Parsable

Task 1: Repair the Syntax

Open starter/stage1_syntax_bug.txt.

  1. Copy it to a Python file.
  2. Run it without editing and record the complete final traceback line.
  3. Inspect the reported line and the line before it.
  4. Make one focused correction.
  5. Retest with 85 and 50.

Expected classifications: alarm and normal.

Check-Off A: Syntax Evidence

Show your partner:

  • the original SyntaxError message;
  • the exact grammar rule that was violated;
  • the corrected program running with both inputs.

State why exception handling would not be the appropriate repair.

Stage 2: Repair Record Parsing

Task 2: Reproduce the Conversion Failure

Call parse_reading with:

"T1,23.5"
"T2,error"
"missing-comma"

For each record, write the expected behavior before running. Record the exception type, message, and failing operation for invalid input.

Task 3: Define the Parser Contract

The repaired function must:

  • return (sensor_id, numeric_value) for SENSOR,VALUE;
  • raise ValueError("expected SENSOR,VALUE") for the wrong number of fields;
  • raise ValueError("value must be numeric") when conversion fails;
  • never print or request input.

Use validation for structure and a narrow try block for numeric conversion.

Parser Checkpoint

Where should the try block begin and end?

Place only float(value_text) in the try block. Structure validation is clearer before conversion, and unrelated statements should remain visible if they fail.

Stage 3: Handle Missing Data Intentionally

Task 4: Diagnose the Missing Sensor

limits = {"T1": 80.0, "T2": 75.0}
limit = limits["T9"]

Decide which contract applies:

  1. T9 must exist, so KeyError reveals invalid program data.
  2. T9 is optional, so a documented default is meaningful.
  3. The caller should report “unknown sensor” and continue.

Implement option 3 in classify_reading without catching unrelated exceptions.

Task 5: Keep Unknown Separate from Normal

Required results:

classify_reading("T1", 70.0, limits)  # "normal"
classify_reading("T1", 85.0, limits)  # "alarm"
classify_reading("T9", 70.0, limits)  # "unknown"

Do not use a default limit that silently classifies an unknown sensor as normal.

Stage 4: Make the Empty Case Explicit

Task 6: Reproduce the Empty Average

Call average_reading([]) and read the traceback from the final line upward.

The contract for this lab is:

Return the average of a non-empty sequence; raise ValueError for an empty sequence.

Repair the function without catching ZeroDivisionError after it happens.

Validate or Catch?

Why is an explicit empty-sequence check clearer here than catching ZeroDivisionError?

The contract directly forbids empty input. Validation names that condition and avoids confusing it with another possible division defect.

Stage 5: Find the Silent Logic Error

Task 7: Correct the Average

The current function divides by len(values) - 1.

Use this evidence table before editing:

Input Expected
[20.0] 20.0
[20.0, 22.0, 24.0] 22.0
[] ValueError

Repair the formula and retain all three cases as regression tests.

Task 8: Do Not Hide Unexpected Defects

Search your file for:

  • bare except:;
  • except Exception:;
  • handlers containing only pass;
  • try blocks containing unrelated operations.

Replace each with a specific design or let the unexpected exception propagate.

Stage 6: Integrate the Pipeline

Task 9: Process Several Records

Implement process_records(records, limits) so it:

  1. parses each record;
  2. skips malformed records after recording an error message;
  3. classifies known and unknown sensors;
  4. returns (accepted, errors);
  5. keeps processing after an expected bad record.

The starter includes starter/sensor_readings.txt as optional data.

Task 10: Regression Tests

Your test set must include:

  • one ordinary valid record;
  • one boundary reading equal to its limit;
  • one nonnumeric value;
  • one record with a missing comma;
  • one unknown sensor;
  • an empty list passed to average_reading.

Every repaired defect needs a test that would fail if the defect returned.

Final Checkpoint

For one repaired function, explain:

  1. the smallest input that reproduced the problem;
  2. the traceback or wrong result that provided evidence;
  3. your hypothesis;
  4. the focused correction;
  5. the regression test.

Individual Checkoff

Instructor-Selected Demonstration

Each student independently completes one action:

  • classify a new defect;
  • annotate a traceback;
  • narrow a try block;
  • select a specific handler;
  • raise ValueError for a violated contract;
  • write a boundary or invalid-input regression test.

You may retry once after focused feedback.

Submission Checklist

  • The debugging record contains evidence for every stage.
  • All supplied function names remain unchanged.
  • No bare except, silent pass, or broad except Exception remains.
  • Parsing uses a narrow conversion handler.
  • Empty input raises the documented ValueError.
  • The logic defect is repaired.
  • Normal, boundary, and invalid cases are tested.
  • Both returned collections from process_records are explained.