Factory Quality Sentinel

Build a local ML tool that ranks unusual factory test results for human review.

Introduction

30 Second Summary

On a production line, most units look normal. The difficult part is finding the few that need closer human inspection before they move on.

In this project, you will build Factory Quality Sentinel in Python using synthetic test-bench data. It compares fixed-limit rules with anomaly detection to rank suspicious automotive units for human review.

What You'll Build

You demo a local quality check that places all five hidden defects missed by broad rules at the top of an inspector's review list.

By the end of this project, you'll have:

  • A repeatable dataset you can regenerate to show 40 reference results plus 15 evaluation results every time.
  • A measured comparison showing broad fixed limits missing five hidden defects. Your detector then ranks all five for review.
  • An auditable human-review case study you can show through scored decisions, plain-language reasons, benchmark labels, and documented limitations.
  • Secret Mission: Compare review capacities of 3, 5, or 7 units to recommend an operational threshold from the measured trade-offs.

Are there any prerequisites?

You need a Windows computer with Visual Studio Code installed. The guide sets up Python 3.14.10 plus scikit-learn 1.9.1 locally at no cost.

Before We Start

Before any hands-on work begins, this step locks in Factory Quality Sentinel's role as a decision-support tool. It ranks suspicious synthetic automotive end-of-line test-bench results for qualified human inspectors, who remain responsible for every final decision.

Set Up Python and the Project

You have Visual Studio Code, but Factory Quality Sentinel has no confirmed Python environment yet. A consistent runtime keeps every later script on the same foundation.

This step installs Python 3.14.10. It creates an isolated virtual environment containing scikit-learn version 1.9.1.

In this step, get ready to:
  • Install Python 3.14.10 through the Python install manager.
  • Create the factory-quality-sentinel workspace with a pinned dependency file.
  • Prepare an activated .venv environment containing scikit-learn 1.9.1.
Install Python 3.14.10

The Python install manager handles Windows runtime installation. You will use it if the version check shows that the pinned runtime is unavailable.

  • Press the Windows key to open Windows search.
  • Type Visual Studio Code into the search field.
  • Press Enter to open Visual Studio Code.
  • Select Terminal in the top menu bar.
  • Select New Terminal.
  • Open the terminal profile menu beside the plus icon.
  • Select Command Prompt to open a fresh Windows Command Prompt session.

Before you check, do you expect this computer to have the pinned Python version already?

  • Check the available Python version by running this command:
python --version

What does this check?

This command reports the Python interpreter available through python. The result determines whether you can continue or need the install manager.

✔️ I see the pinned version

Your terminal shows Python 3.14.10. That runtime is ready for the project.

ⓧ I see an older version

The project uses Python 3.14.10 for reproducible results. The Python install manager can install the required runtime family.

  • Visit the official Python Windows downloads page in your browser.
  • Follow the page's Python install manager link.
  • Select Install on the page that opens.
  • Return to the Windows Command Prompt terminal in Visual Studio Code.
  • Install the Python 3.14 runtime family by running this command:
py install 3.14

What does this command do?

The py install 3.14 command asks the install manager for the PythonCore 3.14 runtime family. The current stable release in that family is Python 3.14.10.

  • Close the active terminal using its trash icon.
  • Open the terminal profile menu beside the plus icon.
  • Select Command Prompt to start a fresh session.
  • Confirm the installed version by running this command:
python --version

What should the version check prove?

The fresh terminal reloads the Python commands available to Windows. The check should now report Python 3.14.10.

You should see Python 3.14.10 in the terminal.

ⓧ Command not found

Windows cannot find a Python runtime yet. The official Python install manager provides the runtime needed by this project.

  • Visit the official Python Windows downloads page in your browser.
  • Follow the page's Python install manager link.
  • Select Install on the page that opens.
  • Return to the Windows Command Prompt terminal in Visual Studio Code.
  • Install the Python 3.14 runtime family by running this command:
py install 3.14

What does this command do?

The install manager selects the PythonCore 3.14 runtime family. This provides the pinned Python release for the project.

  • Close the active terminal using its trash icon.
  • Open the terminal profile menu beside the plus icon.
  • Select Command Prompt to start a fresh session.
  • Confirm the installed version by running this command:
python --version

What should the version check prove?

The new terminal can access the Python runtime installed by the manager. The check should report Python 3.14.10.

You should see Python 3.14.10 in the terminal.

Still seeing the wrong version?

Close every existing terminal session in Visual Studio Code. Open a fresh Command Prompt session so Windows reloads the available Python commands.

If the result still looks wrong, ask for help checking which Python runtime Windows is using.

Create the project workspace

The workspace keeps the dependency pin beside every future analysis script. Creating it on your Desktop makes the folder easy to find outside Visual Studio Code.

  • Select File in Visual Studio Code's top menu bar.
  • Select Open Folder.
  • Choose Desktop in the folder picker.
  • Click the new-folder icon in the folder picker.
  • Enter factory-quality-sentinel as the folder name.
  • Press Enter to create the folder.
  • Select the new factory-quality-sentinel folder.
  • Click Select Folder to open the workspace.

You will see the empty factory-quality-sentinel workspace in the Explorer sidebar.

  • Click the new-file icon in the Explorer sidebar.
  • Enter requirements.txt as the file name.
  • Press Enter to create the file.
  • Pin the project dependency by pasting this line into requirements.txt:
scikit-learn==1.9.1

What does this dependency pin do?

  • The package name tells Python's package installer to install scikit-learn.
  • The double equals sign pins the project to version 1.9.1.
  • The pin keeps the project reproducible across future installations.
  • Save requirements.txt by pressing Ctrl+S.
  • Confirm that requirements.txt appears in the Explorer sidebar.

Your workspace now has a saved dependency definition for the machine-learning library.

✔️ Awesome, I've got everything!

Your factory-quality-sentinel workspace should contain the saved requirements.txt file.

ⓧ I'd like to double check the full code

Compare your dependency file with this complete version.

scikit-learn==1.9.1
Prepare the virtual environment

A virtual environment keeps this project's packages separate from other Python projects on your computer. Activating .venv makes later commands use the isolated interpreter.

  • Create the .venv environment by running this command in the Windows Command Prompt terminal:
python -m venv .venv

What does this command do?

Python creates an isolated environment inside the .venv folder. The environment contains its own interpreter plus package installation location.

  • Confirm that the .venv folder appears in the Explorer sidebar.

The new .venv folder proves that the isolated environment exists.

Don't see the .venv folder?

Confirm that the terminal path ends with factory-quality-sentinel. The command creates .venv inside the terminal's current folder.

If the folder is still missing, ask for help creating the virtual environment in the correct workspace.

  • Activate the virtual environment by running this command:
.venv\Scripts\activate.bat

What does activation change?

Activation points python at the interpreter inside .venv. Packages installed now stay inside this project environment.

You should see (.venv) at the start of the terminal prompt.

  • Install the pinned dependency from requirements.txt by running this command:
python -m pip install -r requirements.txt

What does this installation command do?

The command asks the activated Python interpreter to run pip. Pip reads requirements.txt before installing the pinned scikit-learn package into .venv.

The terminal should complete the installation without reporting an error.

Package installation failed?

Check that the terminal prompt begins with (.venv). Confirm that requirements.txt contains the exact pinned dependency.

If the installation still fails, ask for help diagnosing the pip output.

Before you run the final checks, which Python version plus scikit-learn version do you expect the activated environment to report?

  • Verify both pinned versions by running these commands:
python --version
python -c "import sklearn; sklearn.show_versions()"

What do these checks verify?

  • The first command reports the Python interpreter used by the activated environment.
  • The second command imports scikit-learn before printing its installed version information.
  • A successful import proves the dependency is available inside .venv.

You should see Python 3.14.10 plus scikit-learn 1.9.1 in the output. That confirms the project is using the intended runtime plus dependency.

Your Python foundation is ready. Next, you will generate the synthetic test-bench dataset that Factory Quality Sentinel inspects.

Generate the Test-Bench Data

Your Python environment is ready. The sentinel now needs a controlled benchmark that represents end-of-line test results.

A visible dataset gives you an immediate working result before any machine learning enters the project. You will generate deterministic CSV data so every later comparison uses the same records.

In this step, get ready to:
  • Build deterministic reference records with three synthetic measurements.
  • Add evaluation records with labels reserved for benchmark scoring.
  • Inspect the generated dataset to locate five hidden defects.
Create the reference dataset

The reference split represents known-good units from the synthetic test bench. Each row records temperature, vibration, torque, its split, plus a benchmark label.

  • Select the factory-quality-sentinel folder in the Visual Studio Code Explorer sidebar.
  • Click the New File button in the Explorer toolbar.
  • Enter generate_test_data.py as the file name.
  • Press Enter to create the file.
  • Define the dataset fields by pasting this code into generate_test_data.py:
import csv

FIELDNAMES = [
    "unit_id",
    "temperature_c",
    "vibration_mm_s",
    "torque_nm",
    "data_split",
    "known_status",
]

rows = []

What does this code do?

  • The csv module provides dictionary-based CSV writing.
  • FIELDNAMES fixes the column order for every generated record.
  • rows stores each synthetic unit before the file is written.
  • Save generate_test_data.py by pressing Ctrl+S.
  • Confirm generate_test_data.py appears beside requirements.txt in the Explorer sidebar.

File missing from the Explorer?

Check that the Explorer sidebar has the factory-quality-sentinel folder selected. The file must sit directly inside that folder.

Help me find the Python file I created in Visual Studio Code.

The first generation pass creates 40 reference units. Small changes across batches make the rows realistic enough to form a repeatable reference pattern.

  • Append the reference-row generator plus the initial CSV writer by pasting this code below rows = []:
for index in range(40):
    batch = index // 10
    cycle = index % 10
    rows.append(
        {
            "unit_id": f"TB{index + 1:03d}",
            "temperature_c": f"{75.0 + cycle * 0.9 + batch * 0.05:.2f}",
            "vibration_mm_s": f"{1.08 + cycle * 0.025 + batch * 0.003:.3f}",
            "torque_nm": f"{230.0 + cycle * 3.0 + batch * 0.4:.2f}",
            "data_split": "reference",
            "known_status": "OK",
        }
    )

with open("test_bench_data.csv", "w", newline="", encoding="utf-8") as data_file:
    writer = csv.DictWriter(data_file, fieldnames=FIELDNAMES)
    writer.writeheader()
    writer.writerows(rows)

print(f"Created test_bench_data.csv with {len(rows)} rows.")
print("Reference rows: 40")

How are the reference rows built?

  • The loop creates unit IDs from TB001 through TB040.
  • The measurement formulas create controlled variation across ten cycles.
  • data_split identifies these rows as the model-training reference set.
  • known_status records the synthetic benchmark outcome as OK.
  • DictWriter writes the fixed header plus every dictionary in rows.
  • Save generate_test_data.py by pressing Ctrl+S.

Before you run the generator, how many records do you expect this first version to create?

  • Generate the first version of the dataset by running this command in the activated terminal:
python generate_test_data.py

What does this command prove?

The command runs the generator inside your activated virtual environment. A successful run proves the script can write the dataset into the current project folder.

You should see Created test_bench_data.csv with 40 rows. followed by Reference rows: 40.

  • Confirm test_bench_data.csv appears in the Explorer sidebar.

Dataset file not created?

Confirm the terminal prompt points to the factory-quality-sentinel folder. Check that the file name ends with .py.

Help me debug why the generator did not create the CSV file.

Add the evaluation records

The evaluation split represents units that the later checks must inspect. The first ten evaluation units follow the reference pattern.

  • In generate_test_data.py, locate the line beginning with with open(.
  • Insert the normal evaluation-row generator directly above that line by pasting this code:
for index in range(10):
    cycle = (index * 3) % 10
    rows.append(
        {
            "unit_id": f"TB{index + 41:03d}",
            "temperature_c": f"{75.1 + cycle * 0.9:.2f}",
            "vibration_mm_s": f"{1.085 + cycle * 0.025:.3f}",
            "torque_nm": f"{230.5 + cycle * 3.0:.2f}",
            "data_split": "evaluation",
            "known_status": "OK",
        }
    )

What changes in the evaluation split?

  • The loop creates TB041 through TB050.
  • The cycle formula changes the order of the measurement combinations.
  • data_split marks these rows as evaluation.
  • known_status confirms that these ten benchmark units are OK.
  • Save generate_test_data.py by pressing Ctrl+S.
  • Regenerate the dataset with the new evaluation rows by running:
python generate_test_data.py

Why rerun the generator?

Each run rewrites test_bench_data.csv from the current contents of rows. This keeps the generated dataset deterministic.

You should now see Created test_bench_data.csv with 50 rows. in the terminal.

  • Select test_bench_data.csv in the Explorer sidebar.
  • Inspect the records from TB041 through TB050.

You should see evaluation in the data_split column for those rows. Their known_status values should all be OK.

Still seeing only 40 rows?

Check that the evaluation loop sits above the with open( line. Save the Python file before rerunning the generator.

Help me find why my evaluation rows are missing.

Plant and verify the hidden defects

Five final units contain unusual combinations across the three measurements. Their individual values still sit inside deliberately broad limits used in the next step.

The known_status label lets you measure later results against the planted benchmark. It stays separate from the measurement features used for model training.

  • In generate_test_data.py, locate the line beginning with with open(.
  • Insert the hidden-defect records directly above that line by pasting this code:
hidden_defects = [
    (71.0, 1.45, 266.0),
    (89.0, 0.92, 217.0),
    (72.0, 0.94, 268.0),
    (88.0, 1.48, 218.0),
    (71.5, 1.47, 219.0),
]

for index, measurements in enumerate(hidden_defects, start=51):
    temperature, vibration, torque = measurements
    rows.append(
        {
            "unit_id": f"TB{index:03d}",
            "temperature_c": f"{temperature:.2f}",
            "vibration_mm_s": f"{vibration:.3f}",
            "torque_nm": f"{torque:.2f}",
            "data_split": "evaluation",
            "known_status": "NOK",
        }
    )

Why are these defects hidden?

Each tuple holds temperature, vibration, plus torque for one planted defect. The unusual signal comes from the combined measurement pattern.

The loop assigns IDs from TB051 through TB055. Their NOK labels remain available only for benchmark evaluation.

  • Save generate_test_data.py by pressing Ctrl+S.
  • Locate the two summary lines at the bottom of generate_test_data.py.
  • Find this current output section:
print(f"Created test_bench_data.csv with {len(rows)} rows.")
print("Reference rows: 40")

What does the current summary cover?

The dynamic first line reports the complete row count. The second line reports only the reference split.

  • Replace those two lines with this three-line summary:
print(f"Created test_bench_data.csv with {len(rows)} rows.")
print("Reference rows: 40")
print("Evaluation rows: 15 (10 OK, 5 hidden NOK)")

What does the final summary show?

The added line separates the 15 evaluation units into ten normal records plus five planted defects. This gives every later check an auditable benchmark.

  • Save generate_test_data.py by pressing Ctrl+S.

Before you run the final generator, what total row count should the three groups produce?

  • Generate the complete test-bench dataset by running:
python generate_test_data.py

What does the final run confirm?

The generator rebuilds the complete CSV from deterministic formulas plus the fixed defect tuples. Repeated runs produce the same 55 records.

You should see Created test_bench_data.csv with 55 rows. followed by the reference summary plus the evaluation summary.

  • Return to test_bench_data.csv in the Explorer sidebar.
  • Scroll to the final five records.

You should see unit IDs TB051 through TB055. Each row should show evaluation under data_split plus NOK under known_status.

Good work. Your first project artifact now captures a controlled benchmark with five defects that later checks must find.

Final row count looks wrong?

Confirm both evaluation loops sit above the with open( line. Check that the hidden-defect loop starts at 51.

Help me debug an incorrect dataset row count.

✔️ Awesome, I've got everything!

Your saved generator creates the same 55-row dataset every time it runs.

ⓧ I'd like to double check the full code

Compare your saved generate_test_data.py with this cumulative version.

import csv

FIELDNAMES = [
    "unit_id",
    "temperature_c",
    "vibration_mm_s",
    "torque_nm",
    "data_split",
    "known_status",
]

rows = []

for index in range(40):
    batch = index // 10
    cycle = index % 10
    rows.append(
        {
            "unit_id": f"TB{index + 1:03d}",
            "temperature_c": f"{75.0 + cycle * 0.9 + batch * 0.05:.2f}",
            "vibration_mm_s": f"{1.08 + cycle * 0.025 + batch * 0.003:.3f}",
            "torque_nm": f"{230.0 + cycle * 3.0 + batch * 0.4:.2f}",
            "data_split": "reference",
            "known_status": "OK",
        }
    )

for index in range(10):
    cycle = (index * 3) % 10
    rows.append(
        {
            "unit_id": f"TB{index + 41:03d}",
            "temperature_c": f"{75.1 + cycle * 0.9:.2f}",
            "vibration_mm_s": f"{1.085 + cycle * 0.025:.3f}",
            "torque_nm": f"{230.5 + cycle * 3.0:.2f}",
            "data_split": "evaluation",
            "known_status": "OK",
        }
    )

hidden_defects = [
    (71.0, 1.45, 266.0),
    (89.0, 0.92, 217.0),
    (72.0, 0.94, 268.0),
    (88.0, 1.48, 218.0),
    (71.5, 1.47, 219.0),
]

for index, measurements in enumerate(hidden_defects, start=51):
    temperature, vibration, torque = measurements
    rows.append(
        {
            "unit_id": f"TB{index:03d}",
            "temperature_c": f"{temperature:.2f}",
            "vibration_mm_s": f"{vibration:.3f}",
            "torque_nm": f"{torque:.2f}",
            "data_split": "evaluation",
            "known_status": "NOK",
        }
    )

with open("test_bench_data.csv", "w", newline="", encoding="utf-8") as data_file:
    writer = csv.DictWriter(data_file, fieldnames=FIELDNAMES)
    writer.writeheader()
    writer.writerows(rows)

print(f"Created test_bench_data.csv with {len(rows)} rows.")
print("Reference rows: 40")
print("Evaluation rows: 15 (10 OK, 5 hidden NOK)")

How is the complete file organized?

The file defines one shared schema. It builds the reference rows before adding normal evaluation records plus hidden defects.

The final section writes every record to test_bench_data.csv before printing the dataset summary.

Your benchmark is ready. Next, you will test whether independent fixed limits can catch the five planted defects.

Prove Fixed Limits Miss Hidden Defects

The dataset you generated gives Factory Quality Sentinel a controlled benchmark. Now you can test the simplest inspection strategy against all 15 evaluation units.

Fixed limits compare each measurement with an allowed range. They cannot evaluate the combined pattern across all three measurements.

In this step, get ready to:
  • Define broad independent ranges for each measurement.
  • Apply the same pass or fail rule to every evaluation row.
  • Measure escaped defects with the reserved benchmark labels.
Define the fixed-limit baseline

A fixed-limit baseline needs one allowed interval per measurement. These broad intervals test whether independent checks can spot the hidden pattern.

  • Create a new rule_check.py file inside the factory-quality-sentinel workspace with the file sidebar in Visual Studio Code.
  • Add the baseline ranges and reusable check by pasting this code:
import csv

LIMITS = {
    "temperature_c": (70.0, 90.0),
    "vibration_mm_s": (0.9, 1.5),
    "torque_nm": (215.0, 270.0),
}


def passes_fixed_limits(row):
    return all(
        lower <= float(row[feature]) <= upper
        for feature, (lower, upper) in LIMITS.items()
    )

What does this code do?

  • LIMITS stores the allowed lower and upper value for each measurement.
  • passes_fixed_limits() converts each measurement to a number before checking its interval.
  • all() returns a passing result only when every independent check succeeds.
  • Save rule_check.py by pressing Ctrl+S.
  • Confirm rule_check.py appears beside generate_test_data.py in your workspace.

Seeing a problem in the rule function?

  • Check that each measurement name matches the corresponding column in test_bench_data.csv.
  • Check that the return expression remains indented inside passes_fixed_limits().

Help me compare my fixed-limit function with the project code..

Apply the rules to evaluation rows

The decision should use only evaluation rows because those are the units awaiting inspection. The reserved labels stay outside the rule and measure its result afterward.

  • Place the following block two lines below the closing parenthesis of passes_fixed_limits() in rule_check.py:
with open("test_bench_data.csv", newline="", encoding="utf-8") as data_file:
    rows = list(csv.DictReader(data_file))

evaluation_rows = [row for row in rows if row["data_split"] == "evaluation"]
flagged_by_rules = [row for row in evaluation_rows if not passes_fixed_limits(row)]
missed_defects = [
    row
    for row in evaluation_rows
    if passes_fixed_limits(row) and row["known_status"] == "NOK"
]

print(f"Evaluation units checked: {len(evaluation_rows)}")
print(f"Rule system flagged: {len(flagged_by_rules)}")
print(f"Hidden defects missed by rules: {len(missed_defects)}")
print("Missed unit IDs:", ", ".join(row["unit_id"] for row in missed_defects))

How does the baseline get measured?

  • csv.DictReader turns each CSV row into a dictionary keyed by the column names.
  • evaluation_rows selects the 15 units awaiting a decision.
  • flagged_by_rules collects units that fail at least one independent range check.
  • missed_defects compares completed rule decisions with known_status for benchmark evaluation.
  • Save rule_check.py by pressing Ctrl+S.
  • Confirm the final line of rule_check.py prints the escaped unit IDs.

Seeing indentation warnings?

  • Check that the line beginning with with open starts at the left edge of the file.
  • Check that the body of missed_defects stays inside its square brackets.

Help me fix the structure of the CSV processing code..

Verify the escaped defects

The complete baseline is ready for a controlled test. The result tells you whether independent ranges are enough.

Before you run this, do you expect individually allowed measurements to guarantee a trustworthy unit?

  • Execute the fixed-limit baseline in the activated terminal by running this command:
python rule_check.py

What did the baseline prove?

  • Evaluation units checked: 15 confirms that the rule processed every evaluation row.
  • Rule system flagged: 0 shows that no unit exceeded an individual limit.
  • Hidden defects missed by rules: 5 confirms that every hidden defect escaped inspection.
  • Missed unit IDs: TB051, TB052, TB053, TB054, TB055 identifies the five escaped units.

You have captured the intended shortfall: every hidden defect stayed inside the broad individual ranges. This measured failure gives the machine learning detector a clear benchmark to beat.

Seeing different counts?

  • Confirm test_bench_data.csv remains beside rule_check.py in the workspace.
  • Confirm the three ranges in LIMITS match the full code below.
  • Confirm you saved rule_check.py before running it.

Help me find why my fixed-limit result counts are different..

✔️ Awesome, I've got everything!

Your baseline is complete. Make sure rule_check.py is saved before continuing.

ⓧ I'd like to double check the full code

Compare your rule_check.py file with this complete version.

import csv

LIMITS = {
    "temperature_c": (70.0, 90.0),
    "vibration_mm_s": (0.9, 1.5),
    "torque_nm": (215.0, 270.0),
}


def passes_fixed_limits(row):
    return all(
        lower <= float(row[feature]) <= upper
        for feature, (lower, upper) in LIMITS.items()
    )


with open("test_bench_data.csv", newline="", encoding="utf-8") as data_file:
    rows = list(csv.DictReader(data_file))

evaluation_rows = [row for row in rows if row["data_split"] == "evaluation"]
flagged_by_rules = [row for row in evaluation_rows if not passes_fixed_limits(row)]
missed_defects = [
    row
    for row in evaluation_rows
    if passes_fixed_limits(row) and row["known_status"] == "NOK"
]

print(f"Evaluation units checked: {len(evaluation_rows)}")
print(f"Rule system flagged: {len(flagged_by_rules)}")
print(f"Hidden defects missed by rules: {len(missed_defects)}")
print("Missed unit IDs:", ", ".join(row["unit_id"] for row in missed_defects))

How does the complete file work?

The file defines independent measurement limits before applying them to every evaluation row. It then uses the reserved labels to count defects that escaped the baseline.

The baseline's designed failure is now measured at five missed defects. Next, you will use an Isolation Forest to rank combined measurement patterns for human review.

Detect Anomalies with Machine Learning

Your fixed-limit baseline checked each measurement alone. All five hidden defects passed those broad limits.

This step uses an Isolation Forest from scikit-learn to learn the joint reference pattern across temperature, vibration, and torque. Lower anomaly scores point to more unusual combinations.

The detector ranks the five lowest-scoring units for a human inspector. The final quality decision stays with that inspector.

In this step, get ready to:
  • Convert the three measurement columns into numeric feature rows.
  • Score all 15 evaluation rows with a model fitted on 40 reference rows.
  • Rank the five lowest-scoring units for human review.
Build the reference-only scoring pipeline

A model feature is a measurement the detector uses to learn patterns. The FEATURE_NAMES list creates a clear boundary around those inputs.

  • In the Visual Studio Code file sidebar from earlier, create detect_anomalies.py inside the factory-quality-sentinel workspace.
  • Add the reference-only scoring pipeline by pasting this code into detect_anomalies.py:
import csv

from sklearn.ensemble import IsolationForest

FEATURE_NAMES = ["temperature_c", "vibration_mm_s", "torque_nm"]
FLAG_COUNT = 5


def to_features(rows):
    return [[float(row[name]) for name in FEATURE_NAMES] for row in rows]


with open("test_bench_data.csv", newline="", encoding="utf-8") as data_file:
    rows = list(csv.DictReader(data_file))

reference_rows = [row for row in rows if row["data_split"] == "reference"]
evaluation_rows = [row for row in rows if row["data_split"] == "evaluation"]

model = IsolationForest(
    n_estimators=200,
    contamination="auto",
    random_state=42,
)
model.fit(to_features(reference_rows))
scores = model.score_samples(to_features(evaluation_rows))

What does this code do?

  • The FEATURE_NAMES list limits model inputs to temperature, vibration, and torque.
  • The to_features() function converts CSV strings into numeric feature rows.
  • The data split keeps 40 reference rows separate from 15 evaluation rows.
  • The model learns only from reference_rows with reproducible settings.
  • The score_samples call assigns one score to every evaluation row. Lower scores identify more unusual units.
  • Save detect_anomalies.py by pressing Ctrl+S.
  • Check the scoring pipeline in the Visual Studio Code terminal from earlier by running this command:
python detect_anomalies.py

What did this prove?

The terminal returns to the prompt without reporting a Python error. That clean return confirms the CSV loaded successfully.

It also confirms that the model fitted the reference rows before scoring every evaluation row.

Good progress. Your reference-only scoring pipeline now executes from start to finish.

Does the scoring pipeline stop?

  • Confirm the Visual Studio Code terminal from earlier still shows (.venv) in its prompt.
  • Confirm test_bench_data.csv remains inside the factory-quality-sentinel workspace.

Help me diagnose why the reference-only scoring pipeline stops.

Rank the lowest anomaly scores

Sorting the scores from lower to higher turns the model output into an inspection queue. The first five positions become the units sent for review.

  • In detect_anomalies.py, find the line containing scores = model.score_samples(to_features(evaluation_rows)).
  • Add the ranking output directly below that line by pasting this code:
ranked_rows = sorted(
    zip(evaluation_rows, scores),
    key=lambda item: item[1],
)

print("Most suspicious evaluation units:")
for rank, (row, score) in enumerate(ranked_rows[:FLAG_COUNT], start=1):
    print(f"{rank}. {row['unit_id']} | score={score:.6f} | REVIEW")

How does the ranking work?

  • The zip call pairs each evaluation row with its anomaly score.
  • The sorted call orders those pairs from lower score to higher score.
  • The ranked_rows[:FLAG_COUNT] slice selects the five most unusual units for review.
  • The printed rank keeps each unit ID beside its score. That makes the queue easy for an inspector to follow.
  • Save detect_anomalies.py by pressing Ctrl+S.
Verify the review queue and feature boundary

The known_status column is a benchmark label for checking results. Keeping it outside FEATURE_NAMES prevents the model from seeing the answer.

  • Return to the top of detect_anomalies.py.
  • Confirm FEATURE_NAMES contains temperature_c, vibration_mm_s, and torque_nm.
  • Confirm known_status is absent from FEATURE_NAMES.

Before you run the completed detector, predict which five units will occupy the review queue.

  • Run the completed detector in the activated terminal by using this command:
python detect_anomalies.py

What should I see?

You should see Most suspicious evaluation units: followed by five ranked lines. Those lines contain TB051, TB052, TB053, TB054, and TB055.

Every ranked line ends with REVIEW. The scores run from lower to higher.

That is the key recovery. Your detector finds all five units that escaped the independent fixed limits.

Are different units in the top five?

  • Check that key=lambda item: item[1] uses score position 1 for sorting.
  • Check that model.fit(to_features(reference_rows)) still fits the model with reference rows.
  • Check that FEATURE_NAMES contains only the three measurement columns.

Help me debug an unexpected anomaly ranking.

✔️ Awesome, I've got everything!

Your detector is complete. It trains on reference measurements before ranking the evaluation units for human review.

ⓧ I'd like to double check the full code

Compare your saved detect_anomalies.py with this complete version.

import csv

from sklearn.ensemble import IsolationForest

FEATURE_NAMES = ["temperature_c", "vibration_mm_s", "torque_nm"]
FLAG_COUNT = 5


def to_features(rows):
    return [[float(row[name]) for name in FEATURE_NAMES] for row in rows]


with open("test_bench_data.csv", newline="", encoding="utf-8") as data_file:
    rows = list(csv.DictReader(data_file))

reference_rows = [row for row in rows if row["data_split"] == "reference"]
evaluation_rows = [row for row in rows if row["data_split"] == "evaluation"]

model = IsolationForest(
    n_estimators=200,
    contamination="auto",
    random_state=42,
)
model.fit(to_features(reference_rows))
scores = model.score_samples(to_features(evaluation_rows))

ranked_rows = sorted(
    zip(evaluation_rows, scores),
    key=lambda item: item[1],
)

print("Most suspicious evaluation units:")
for rank, (row, score) in enumerate(ranked_rows[:FLAG_COUNT], start=1):
    print(f"{rank}. {row['unit_id']} | score={score:.6f} | REVIEW")

How to use this reference

This file shows the complete detector for this step. Use it to compare the feature boundary, reference-only training, score ordering, and printed review queue.

Your model now turns reference measurements into a ranked human-review queue. Next, you will add traceable reasons, benchmark metrics, and an audit report.

Produce a Human-Review Audit Report

Your Isolation Forest now ranks the five hidden defects for review. A ranking alone does not explain each decision or preserve the result for an inspector.

This step adds plain-language reasons to every review decision. It also measures the detector before documenting the human-review controls.

In this step, get ready to:
  • Create a score-ordered audit report with plain-language review reasons.
  • Calculate the detector's benchmark outcomes.
  • Document the workflow's limitations plus its human-review control.
Add traceable review reasons

A CSV audit report preserves the score that produced each review decision. A readable reason gives the inspector context without turning the model into an automated reject system.

  • In detect_anomalies.py, find the feature list near the top of the file:
FEATURE_NAMES = ["temperature_c", "vibration_mm_s", "torque_nm"]
FLAG_COUNT = 5

What Are You Looking At?

  • The FEATURE_NAMES list keeps the model inputs limited to the three measurements.
  • The FLAG_COUNT value sets the inspection capacity to five units.
  • Replace those lines with the feature labels plus the existing review capacity:
FEATURE_NAMES = ["temperature_c", "vibration_mm_s", "torque_nm"]
FEATURE_LABELS = {
    "temperature_c": "temperature",
    "vibration_mm_s": "vibration",
    "torque_nm": "torque",
}
FLAG_COUNT = 5

Why Add Feature Labels?

The dictionary converts column names such as vibration_mm_s into words an inspector can read quickly. The model still receives only the values listed in FEATURE_NAMES.

  • Add the reference-range helper below to_features by pasting this function:
def build_reference_ranges(reference_rows):
    return {
        name: (
            min(float(row[name]) for row in reference_rows),
            max(float(row[name]) for row in reference_rows),
        )
        for name in FEATURE_NAMES
    }

What Does This Helper Do?

The helper calculates the lowest reference value plus the highest reference value for each measurement. These ranges support readable explanations without changing the model's scores.

  • Press Ctrl+S to save detect_anomalies.py.
  • Confirm the detector still runs by executing:
python detect_anomalies.py

What Does This Check Prove?

This run confirms that the new labels plus the reference-range helper have not disturbed the existing detector. The terminal should still rank five units for review.

You should still see TB051 through TB055 represented in the five suspicious results.

Did the Detector Stop Running?

Check that FEATURE_LABELS closes with a brace. Confirm that build_reference_ranges starts at the left edge of the file.

Ask for help with the new configuration or helper: help me check the feature labels and reference-range helper in my detector.

  • Add the explanation helper below build_reference_ranges by pasting this function:
def explain_review(row, reference_ranges):
    reasons = []
    for name in FEATURE_NAMES:
        value = float(row[name])
        lower, upper = reference_ranges[name]
        label = FEATURE_LABELS[name]
        if value < lower:
            reasons.append(f"{label} below reference range")
        elif value > upper:
            reasons.append(f"{label} above reference range")

    if reasons:
        return "; ".join(reasons)
    return "Unusual combination inside individual reference ranges"

How Are Reasons Created?

  • The loop compares each reviewed measurement with the matching reference range.
  • A value outside the range produces a plain-language reason.
  • A unit inside every individual range receives a combination-based explanation.
  • Find the completed ranked_rows = sorted( block.
  • Add the review IDs plus the reference ranges immediately below that block:
review_ids = {row["unit_id"] for row, _ in ranked_rows[:FLAG_COUNT]}
reference_ranges = build_reference_ranges(reference_rows)

How Does This Connect the Report?

The review_ids set records the five units selected by score. The reference_ranges dictionary supplies the context for each review reason.

  • Press Ctrl+S to save detect_anomalies.py.
  • Check the connected helpers by running:
python detect_anomalies.py

What Does This Run Confirm?

The run confirms that the selected IDs can be paired with the calculated ranges. You should still see the five ranked review results in the terminal.

  • Add the report field order above print("Most suspicious evaluation units:") by pasting this list:
report_fields = [
    "unit_id",
    "temperature_c",
    "vibration_mm_s",
    "torque_nm",
    "anomaly_score",
    "review_decision",
    "reason",
    "known_status",
]

Why Fix the Field Order?

The list gives every report the same column order. Measurements come before the score so an inspector can connect the model result to the original test-bench values.

  • Add the report writer below report_fields by pasting this block:
with open("audit_report.csv", "w", newline="", encoding="utf-8") as report_file:
    writer = csv.DictWriter(report_file, fieldnames=report_fields)
    writer.writeheader()

    for row, score in ranked_rows:
        needs_review = row["unit_id"] in review_ids
        writer.writerow(
            {
                "unit_id": row["unit_id"],
                "temperature_c": row["temperature_c"],
                "vibration_mm_s": row["vibration_mm_s"],
                "torque_nm": row["torque_nm"],
                "anomaly_score": f"{score:.6f}",
                "review_decision": "REVIEW" if needs_review else "PASS",
                "reason": (
                    explain_review(row, reference_ranges)
                    if needs_review
                    else "No review trigger"
                ),
                "known_status": row["known_status"],
            }
        )

What Does the Report Writer Do?

  • The writer follows ranked_rows so the most suspicious units appear first.
  • Each anomaly score is stored with six decimal places.
  • Every row receives a REVIEW or PASS decision.
  • The benchmark label stays available for evaluation without entering the model features.
  • Press Ctrl+S to save detect_anomalies.py.

Before you run the detector, do you expect the report to preserve the same score order as the terminal ranking?

  • Generate the first audit report by running:
python detect_anomalies.py

What Does This Run Create?

The detector now writes all 15 evaluation units to audit_report.csv. Its existing terminal ranking still shows the five units selected for review.

  • In the Visual Studio Code Explorer sidebar, select audit_report.csv.
  • Confirm that the file contains 15 data rows below its header.
  • Check that TB051 through TB055 appear among the first five data rows.

You should see REVIEW for those five units. The remaining ten units should show PASS.

Missing the Audit Report?

Confirm that your terminal is still inside the factory-quality-sentinel folder. Check that the report writer appears before the ranking print statements.

Ask for targeted help: help me find why audit_report.csv is not being created by my detector.

Measure the benchmark results

The known_status column provides benchmark truth after scoring. Keeping it outside FEATURE_NAMES prevents the model from learning the answer it is supposed to predict.

  • Add the benchmark calculations below the reference_ranges assignment:
true_positives = sum(
    row["known_status"] == "NOK" and row["unit_id"] in review_ids
    for row in evaluation_rows
)
false_positives = sum(
    row["known_status"] == "OK" and row["unit_id"] in review_ids
    for row in evaluation_rows
)
false_negatives = sum(
    row["known_status"] == "NOK" and row["unit_id"] not in review_ids
    for row in evaluation_rows
)

What Do These Counts Measure?

  • A true positive is a hidden defect selected for review.
  • A false positive is an OK unit selected for review.
  • A false negative is a hidden defect left outside the review queue.
  • Find the loop that prints the five suspicious units.
  • Add the metric output immediately below that loop:
print(f"True positives: {true_positives}")
print(f"False positives: {false_positives}")
print(f"False negatives: {false_negatives}")
print("Created audit_report.csv with 15 scored evaluation rows.")

Why Print the Metrics?

The terminal summary makes the detector's benchmark performance visible on every run. The final line also confirms the number of evaluation rows written to the report.

  • Press Ctrl+S to save detect_anomalies.py.

Before you run the completed detector, how many planted defects do you think its five review slots will recover?

  • Run the completed detector by executing:
python detect_anomalies.py

What Does the Final Run Check?

This run scores the evaluation rows again. It rewrites the audit report before printing the benchmark results.

You should see True positives: 5, False positives: 0, plus False negatives: 0.

That is the audit loop working end to end. Every planted defect reaches human review without adding an unnecessary review on this synthetic benchmark.

Do Your Counts Differ?

Confirm that known_status is absent from FEATURE_NAMES. Check that the model still fits only reference_rows.

Ask for help comparing the calculations: help me debug why my benchmark counts differ from the expected result.

✔️ Awesome, I've got everything!

Your detector now produces the complete audit report plus the expected benchmark counts.

ⓧ I'd like to double check the full code

Compare your detector with this completed version.

import csv

from sklearn.ensemble import IsolationForest

FEATURE_NAMES = ["temperature_c", "vibration_mm_s", "torque_nm"]
FEATURE_LABELS = {
    "temperature_c": "temperature",
    "vibration_mm_s": "vibration",
    "torque_nm": "torque",
}
FLAG_COUNT = 5


def to_features(rows):
    return [[float(row[name]) for name in FEATURE_NAMES] for row in rows]


def build_reference_ranges(reference_rows):
    return {
        name: (
            min(float(row[name]) for row in reference_rows),
            max(float(row[name]) for row in reference_rows),
        )
        for name in FEATURE_NAMES
    }


def explain_review(row, reference_ranges):
    reasons = []
    for name in FEATURE_NAMES:
        value = float(row[name])
        lower, upper = reference_ranges[name]
        label = FEATURE_LABELS[name]
        if value < lower:
            reasons.append(f"{label} below reference range")
        elif value > upper:
            reasons.append(f"{label} above reference range")

    if reasons:
        return "; ".join(reasons)
    return "Unusual combination inside individual reference ranges"


with open("test_bench_data.csv", newline="", encoding="utf-8") as data_file:
    rows = list(csv.DictReader(data_file))

reference_rows = [row for row in rows if row["data_split"] == "reference"]
evaluation_rows = [row for row in rows if row["data_split"] == "evaluation"]

model = IsolationForest(
    n_estimators=200,
    contamination="auto",
    random_state=42,
)
model.fit(to_features(reference_rows))
scores = model.score_samples(to_features(evaluation_rows))

ranked_rows = sorted(
    zip(evaluation_rows, scores),
    key=lambda item: item[1],
)
review_ids = {row["unit_id"] for row, _ in ranked_rows[:FLAG_COUNT]}
reference_ranges = build_reference_ranges(reference_rows)

true_positives = sum(
    row["known_status"] == "NOK" and row["unit_id"] in review_ids
    for row in evaluation_rows
)
false_positives = sum(
    row["known_status"] == "OK" and row["unit_id"] in review_ids
    for row in evaluation_rows
)
false_negatives = sum(
    row["known_status"] == "NOK" and row["unit_id"] not in review_ids
    for row in evaluation_rows
)

report_fields = [
    "unit_id",
    "temperature_c",
    "vibration_mm_s",
    "torque_nm",
    "anomaly_score",
    "review_decision",
    "reason",
    "known_status",
]

with open("audit_report.csv", "w", newline="", encoding="utf-8") as report_file:
    writer = csv.DictWriter(report_file, fieldnames=report_fields)
    writer.writeheader()

    for row, score in ranked_rows:
        needs_review = row["unit_id"] in review_ids
        writer.writerow(
            {
                "unit_id": row["unit_id"],
                "temperature_c": row["temperature_c"],
                "vibration_mm_s": row["vibration_mm_s"],
                "torque_nm": row["torque_nm"],
                "anomaly_score": f"{score:.6f}",
                "review_decision": "REVIEW" if needs_review else "PASS",
                "reason": (
                    explain_review(row, reference_ranges)
                    if needs_review
                    else "No review trigger"
                ),
                "known_status": row["known_status"],
            }
        )

print("Most suspicious evaluation units:")
for rank, (row, score) in enumerate(ranked_rows[:FLAG_COUNT], start=1):
    print(f"{rank}. {row['unit_id']} | score={score:.6f} | REVIEW")

print(f"True positives: {true_positives}")
print(f"False positives: {false_positives}")
print(f"False negatives: {false_negatives}")
print("Created audit_report.csv with 15 scored evaluation rows.")

How to Compare the File

Check each section in order from the feature configuration to the final print statements. Your saved file should match this reference exactly.

Document the human-review workflow

The report shows what the detector produced. A Markdown project guide explains what the result means plus where its limits begin.

Your documentation must keep the final decision with a qualified human inspector. It must also state that this synthetic benchmark is not production evidence.

  • In the Visual Studio Code Explorer sidebar, use the file creation control to add README.md inside the factory-quality-sentinel folder.
  • Paste the first part of the project guide into README.md from the top of the empty file:
# Factory Quality Sentinel

Factory Quality Sentinel is a local machine-learning demonstration for manufacturing quality inspection. It uses synthetic end-of-line test-bench data and does not contain ZF data, production limits, or safety specifications.

## Business problem

Independent fixed limits can miss unusual units when the chosen limits are deliberately broad. This project compares that baseline with a model that learns the pattern of known-good reference rows across temperature, vibration, and torque.

## How it works

1. `generate_test_data.py` creates 40 reference rows and 15 evaluation rows.
2. `rule_check.py` applies independent fixed limits.
3. `detect_anomalies.py` trains an Isolation Forest on only the reference rows.
4. The detector ranks evaluation rows by anomaly score and sends the five lowest-scoring units for human review.
5. `audit_report.csv` records scores, decisions, reasons, and benchmark labels.

## Run locally

```bat
python -m venv .venv
.venv\Scripts\activate.bat
python -m pip install -r requirements.txt
python generate_test_data.py
python rule_check.py
python detect_anomalies.py
```

What Does This First Part Document?

The opening identifies the synthetic scope. The remaining sections explain the business problem plus the repeatable execution path.

  • Press Ctrl+S to save README.md.
  • Use the preview control in the editor toolbar to render the Markdown file.

You should see a formatted title followed by the business problem. The execution commands should appear together in a code block.

  • Return to the README.md editor.
  • Paste the remaining sections below the existing code block:
## Demonstration result

On the included synthetic benchmark, the fixed-limit system flags zero units and misses five hidden defects. The anomaly detector ranks all five hidden defects for review with zero false positives at a review capacity of five units.

## Responsible AI

This model is decision support, not an automated reject system. The final disposition remains with a qualified human inspector. The `known_status` field exists only to evaluate this synthetic benchmark and is never used as a model feature. A real deployment would require representative production data, validated thresholds, drift monitoring, security controls, documented failure handling, and domain-expert approval.

## ZF relevance

ZF Test Systems describes production analytics for end-of-line testing, unknown-anomaly detection, and scoring based on trained models. ZF also states that trustworthy AI should support human oversight, transparency, robustness, and accountability.

Sources:

- https://www.zf.com/products/en/testsystems/data_analytics/testsystems_data_analytics.html
- https://www.zf.com/master/media/corporate/m_zf_com/company/zf_ethics_guidelines/ZF_Ethics_Guidelines_for_Trustworthy_AI.pdf

Why End With Responsible AI?

The result section records the measured synthetic outcome. The responsible-AI section limits the model to decision support while naming the controls a real deployment would require.

The ZF relevance section connects the project to documented manufacturing analytics plus trustworthy-AI principles. The project still makes no claim about ZF production data or limits.

  • Press Ctrl+S to save the completed README.md.

Before you check the finished preview, do you expect a reader to understand why the model supports inspection without controlling the final disposition?

  • Return to the Markdown preview using the preview control in the editor toolbar.
  • Confirm that the preview includes the Responsible AI section.
  • Confirm that the preview names the qualified human inspector as the final decision-maker.

You should see a formatted project guide covering the business problem, execution commands, measured result, limitations, ZF relevance, plus human oversight.

Does the Preview Look Incomplete?

Confirm that the file is named README.md. Check that every heading begins on its own line.

Ask for help with the document structure: help me check why my README preview is missing sections or formatting.

✔️ Awesome, I've got everything!

Your project guide now documents the workflow plus its human-review boundary.

ⓧ I'd like to double check the full code

Compare your project guide with this completed version.

# Factory Quality Sentinel

Factory Quality Sentinel is a local machine-learning demonstration for manufacturing quality inspection. It uses synthetic end-of-line test-bench data and does not contain ZF data, production limits, or safety specifications.

## Business problem

Independent fixed limits can miss unusual units when the chosen limits are deliberately broad. This project compares that baseline with a model that learns the pattern of known-good reference rows across temperature, vibration, and torque.

## How it works

1. `generate_test_data.py` creates 40 reference rows and 15 evaluation rows.
2. `rule_check.py` applies independent fixed limits.
3. `detect_anomalies.py` trains an Isolation Forest on only the reference rows.
4. The detector ranks evaluation rows by anomaly score and sends the five lowest-scoring units for human review.
5. `audit_report.csv` records scores, decisions, reasons, and benchmark labels.

## Run locally

```bat
python -m venv .venv
.venv\Scripts\activate.bat
python -m pip install -r requirements.txt
python generate_test_data.py
python rule_check.py
python detect_anomalies.py
```

## Demonstration result

On the included synthetic benchmark, the fixed-limit system flags zero units and misses five hidden defects. The anomaly detector ranks all five hidden defects for review with zero false positives at a review capacity of five units.

## Responsible AI

This model is decision support, not an automated reject system. The final disposition remains with a qualified human inspector. The `known_status` field exists only to evaluate this synthetic benchmark and is never used as a model feature. A real deployment would require representative production data, validated thresholds, drift monitoring, security controls, documented failure handling, and domain-expert approval.

## ZF relevance

ZF Test Systems describes production analytics for end-of-line testing, unknown-anomaly detection, and scoring based on trained models. ZF also states that trustworthy AI should support human oversight, transparency, robustness, and accountability.

Sources:

- https://www.zf.com/products/en/testsystems/data_analytics/testsystems_data_analytics.html
- https://www.zf.com/master/media/corporate/m_zf_com/company/zf_ethics_guidelines/ZF_Ethics_Guidelines_for_Trustworthy_AI.pdf

How to Compare the Guide

Check each heading plus the text beneath it. Your saved README.md should match this reference exactly.

Your quality sentinel now turns anomaly scores into an auditable human-review workflow. The finished project preserves decisions, measures the synthetic benchmark, plus documents where human responsibility begins.

Secret mission

Choose an Operational Review Threshold

Your anomaly detector ranks suspicious units, but inspectors still need a practical review capacity. Compare three capacities and recommend a threshold that balances missed defects with unnecessary reviews.

Clean Up Your Resources

Clean Up Your Resources

Factory Quality Sentinel runs entirely on your Windows computer, with no cloud resources or ongoing service costs. Decide whether to keep the workspace available, pause your work, or delete it entirely.

Resources you used:

  • The local factory-quality-sentinel workspace currently open in Visual Studio Code.
  • The .venv virtual environment inside the workspace.
  • The generated test_bench_data.csv and audit_report.csv files inside the workspace.

Keep everything running

No action is needed. Choose this option if you want to rerun the quality checks or continue refining your threshold decision.

  • Keep the factory-quality-sentinel workspace with its scripts and documentation.
  • Keep .venv so the pinned Python packages remain available for this project.
  • Keep test_bench_data.csv and audit_report.csv so you can demonstrate the baseline and anomaly-detection results.

Pause - I'll come back to this later

Pausing ends the current editor and terminal session while preserving every project file for later.

  • Save any open changes by pressing Ctrl+S.
  • Close Visual Studio Code to end the current Windows Command Prompt session.
  • Keep the factory-quality-sentinel workspace in its current location.
  • Return later by reopening the same workspace in Visual Studio Code.

Delete - I don't want to use this again

This option permanently removes the workspace and every generated result inside it. The project has no remote resources left behind.

  • Copy README.md to another folder if you want to keep your project write-up.
  • Copy audit_report.csv to another folder if you want to keep the scored evaluation results.
  • Move out of the workspace and delete factory-quality-sentinel from the current Windows Command Prompt by running these commands:
cd ..
rmdir /s /q factory-quality-sentinel

What Do These Commands Do?

  • The first line moves the terminal to the folder above the workspace.
  • The second line permanently removes the workspace and everything stored inside it.
  • Return to the Explorer sidebar in Visual Studio Code.
  • Confirm that requirements.txt and the other project files no longer appear.

Your clean slate is ready, with the workspace and generated reports removed from your computer.

Still See the Project Files?

  • Confirm that the terminal moved to the folder above factory-quality-sentinel before the deletion command ran.
  • Close any program that still has a file from the workspace open before running the deletion command again.
  • Help me remove the remaining Factory Quality Sentinel workspace.

Nice Work!

Nice Work!

You did it! Factory Quality Sentinel now turns synthetic test-bench data into a ranked anomaly detection workflow for human inspection.

You've learned how to:

  • Created a deterministic quality benchmark in test_bench_data.csv with 40 reference rows plus 15 evaluation rows.
  • Proved that the fixed-limit baseline missed five hidden synthetic defects. You used an Isolation Forest to rank all five defects for review.
  • Produced an auditable human-review workflow in audit_report.csv. You documented responsible-AI limitations plus human oversight controls in README.md.
  • Completed the Secret Mission by comparing review capacities of 3, 5, and 7 units. You defended a five-unit operational threshold without presenting the synthetic benchmark as production evidence.

Ready to quiz yourself?