GL
GRANITE LIFELINE
v3.1 · Viva Synced Draft · 2 Aug 2026

University of Bristol MSc CS · IBM-Sponsored

Granite Lifeline

OBD-II Anomaly Analysis & Diagnostic Reports

2026

Lei Pei · Qiuting Fu · Ray Wang · Lucca Zhou · Charlotte Yu · Jintong He

A Driver’s Experience

What happens when the Check Engine light comes on?

Why has my Check Engine light come on?
Car dashboard with the amber check-engine warning light illuminated
CHECK ENGINE · ONSource · John Hughes, 2025
Car owner holding a BlueDriver OBD-II scanner and its phone app
CONSUMER OBD-II SCANNERSource · BlueDriver
EOBD Facile trouble-code screen showing P0670 and P0401 descriptions
Source · EOBD Facile, Google Play product listing · accessed July 2026
A mechanic inspecting a vehicle in a repair workshop
Visit a repair workshopTime and cost
A driver continuing their journey to work
Continue driving to workUncertain risk
Images: Acorn Tyres Aylesbury · Fleet News
OBD-II — On-Board Diagnostics, Second Generation: provides standardised access to real-time vehicle performance data and Diagnostic Trouble Codes (DTCs).
DTCs — Diagnostic Trouble Codes: standard codes used by technicians to identify where a vehicle detected a problem.

Our Motivation

Fleet operators use vehicle data to plan maintenance earlier.

Continuous OBD-II data
↓
Predictive maintenance
Live Fault Alerts Dashboard
CRITICAL
Overheating pattern detectedcooling system
Ground before next dispatch
HIGH
Brake pressure degradationbraking system
Schedule within 3 days
Adapted from the OxMaint fleet-maintenance software website · accessed July 2026
For private owners: an earlier warning, a clear explanation, and guidance on what to do next.

Building the architecture · 01

We begin with data.

01 · NEXT
Data
Raw signals → contextual features
→
02
Model
Features → risk evidence
→
03
Report
Risk → owner language
→
04
Dashboard
Report → visible action
Our real inputKIT · CC BY 4.0
Automotive OBD-II Dataset
81 ROAD TRIPS70 HOURS10 OBD-II SIGNALS
SIGNALScoolant · MAP · RPM · speed · intake temp · MAF · throttle · ambient temp · pedal D · pedal E
TRAFFIC56 normal · 14 free-flow · 11 congestion
Why this dataset?
Single vehicle — comparable across trips
Enough continuous driving history
Establishes a normal baseline
KIT AUTOMOTIVE OBD-II DATASET

Challenge 1 of 3

How Do You Define Failure Without Verified Fault Labels?

Lei Pei · Qiuting Fu

The Problem · 1 of 3 — Zero Fault Instances
01
Zero Fault Instances
0 / 81 trips
02
No Fixed “Normal”
The same reading can mean the opposite
03
Noisy & Incomplete Evidence
1 Hz · 12–135 min trips
Ordinary-driving trips only
→
Ground proxy evidence in real OBD-II codes, instead of training a supervised fault classifier.
Normal changes with context
→
Identify operating state first, then apply a threshold calibrated for that physical situation.
1 Hz, quantized, short trips
→
Require persistence; when evidence is missing or unclear, return not_evaluable.

Source: docs/viva/data_challenge.md · data_layer/proxy_failure/

2017-07-05_Seat_Leon_RT_S_Stau.csv
TimeCoolant [°C]RPMSpeedMAP [kPa]Fault Code
07:16:31.257310096.0does not exist
07:49:20.958911718102140.0does not exist
0 / 81 trips have a fault

Source: data/raw/OBD-II-Dataset/2017-07-05_Seat_Leon_RT_S_Stau.csv (1 of 81 real trip files)

Real trip · coolant temperature, engine start → steady cruise
58°C · still warming · normal 90°C · plateaued · normal 90°C here → thermostat stuck

Source: data/raw/OBD-II-Dataset/2017-07-05_Seat_Leon_RT_S_Stau.csv — coolant column, full 65-min trip

1 Hz · integer-only readings

Source: same file, coolant column, first 3 min (31°C→42°C)

81 real trips, sorted by length

Source: all 81 files in data/raw/OBD-II-Dataset/ — 12.2–134.7 min, 3 end before the 16.5 min warm-up budget

Worked example · P0217 (overheating), evaluated only in Post-Warmup
Post-Warmup
state confirmed · 77.85% of data
≥105°C?
Threshold: 105°C
baseline maximum observed: 101°C
≥180s?
Persists 180s
not just a one-second spike
yes
P0217
real OBD-II code
data missing or state unclear, at any point →
not_evaluable

Source: docs/viva/data_challenge.md (Deep Dive: Proxy Label Design) · data_layer/proxy_failure/

96.5%
contextual-state coverage
0 / 66
positive decisions on baseline trips
5
proxy-fault families grounded in real DTCs
PROVEN HERE · baseline-trip specificity
NOT YET MEASURED · real-fault recall

Building the architecture · 02

Raw OBD-II readings are now clean, contextualised, and linked to fault evidence.

01 · COMPLETE
Data
46-column feature contract
→
02 · NEXT
Model
Features → risk evidence
→
03
Report
Risk → owner language
→
04
Dashboard
Report → visible action
Challenge 1 inputRaw CSV
2018-02-28_Seat_Leon_S_RT_Normal.csv
coolant 85°C · MAP 96 kPa · RPM 0

Sparse readings
No operating context
No fault labels
→DATA
LAYER
Data outputModel input
46 columns
production_features.csv · 46 cols
trip_0061 · row 2739
operating_state engine_off
coolant_temp 85°C · schema feature_schema.v1
proxy_decisions.csv · fault evidence
proxy · sub_check · result · DTC · emitted
Without faulty training examples, how could we know that drift meant risk?

Challenge 2 of 3

How can we detect change without fault examples?

Ray Wang · Lucca Zhou

What the model receives
Supervised fault classifier

Learns from verified examples

A classifier learns labelled examples, then places new data into one of those known categories.

TRAIN WITHverified normal + faulty examples
TEST WITHindependently verified faults
≠
Our dataset

Contains no verified fault outcomes

WE HAVEordinary driving without verified fault outcomes
WE DO NOT HAVEconfirmed faults or repair outcomes

The detector needed evidence independent of the stand-in labels.

READ HISTORYFORECASTCOMPARENORMALISEREPEAT
TTM

Tiny Time Mixers

IBM Granite’s pre-trained time-series forecasting model. It predicts future sensor values from their recent history.

512 s historywhat the car just did
→
TTMfine-tuned forecast
→
96 s futurewhat the model predicts next
Before TTM Impossible sensor values are repaired or rejected. Unusual but possible values remain.

One window is one 608-second analysis: 512 seconds in, then a 96-second forecast.

READ HISTORYFORECASTCOMPARENORMALISEREPEAT
Baseline behaviour
prediction ≈ reality
same
comparison
Behaviour begins to change
prediction gap grows
predictedactual

The mean prediction gap across 96 seconds is one source of unexpected-behaviour evidence.

READ HISTORYFORECASTCOMPARENORMALISEREPEAT
Make different signals comparable

Convert each gap to the same 0-1 scale

Compare the gap with that signal's baseline reference width.

2°C ÷ 5°C0.40
200 RPM ÷ 36820.05
→
Repeat across eligible windows

Retain change over time

Each window is scored retrospectively. Trip averages form the longer-term risk history.

WINDOW 1
WINDOW 2
TRIP MEAN
HISTORY

Normalisation makes gaps comparable. Repetition builds history; it does not confirm persistence.

How the highest-scoring system risk is selected
Coolingcoolant forecast gap + thermal features
→0.5021
Air intakeairflow and pressure evidence
→lower
Accelerator pedalpedal-channel disagreement
→lower
Highest proxy-anomaly score: cooling degradation
→
JSON excerpt · structured machine-readable output
{
  "timestamp": "2017-07-28T06:46:23Z",
  "anomaly_type": "cooling_degradation",
  "risk_score": 0.5021,
  "risk_level": "Medium",
  "prediction_confidence": 0.842,
  "key_signals": [
    { "feature": "coolant_temp", "value": 85.0, "unit": "°C" },
    { "feature": "ect_rate_180s", "value": 5.0486, "unit": "°C/min" }
  ]
}

For each system, the strongest available forecast or physical evidence sets its score.

Limits: 1 / 11 unchanged controls still alarmed. Baseline and synthetic tests do not prove real-breakdown detection.

1. Does it identify a known change?

154 controlled cases across 11 validation segments.

Baseline segmentpreserve real driving context
→
coolant riseairflow under-readpedal mismatch
→
Expected answername the change we planted
1.00 / 1.00synthetic cooling P / R
1.00 / 0.325synthetic pedal P / R
0.20 / 0.045synthetic air-intake P / R

2. Does extra training improve the forecast?

Compare both models on 250 validation windows from 12 trip-disjoint segments.

Zero-shot58.00
Fine-tuned54.97
5.23%lower mean error
5 / 6signals improved

Building the architecture · 03

We can now see what changed, how strong the evidence is, and which system ranks highest.

01 · COMPLETE
Data
Contextual features
→
02 · COMPLETE
Model
Risk + key signals
→
03 · NEXT
Report
Evidence → explanation
→
04
Dashboard
Owner-facing result
Challenge 2 inputData output
production_features.csv
46-column feature contract
context + cleaned signals
engineered behaviour
schema + calibration provenance
proxy_decisions.csv
Data-scored verdicts for two forwarded types
→GRANITE
TTM
Model outputReport input
Real run JSON
real_case_input.json
anomaly_type cooling_degradation
risk_score 0.5021 · Medium
confidence 84.2%
key signals coolant 85°C (reference 90–95), warming rate 5.05°C/min (reference 0–2)
risk-threshold projection 5 trips · 75.02% within 10 trips
How can we turn technical evidence into clear, reliable guidance for vehicle owners?

Challenge 3 of 3

How can we turn technical evidence into clear, reliable guidance for vehicle owners?

Charlotte Yu · Jintong He

Use knowledge only where the report needs it
+
Our design choice

Selective RAG

RAG · Retrieval-Augmented Generation
Relevant information is retrieved first and supplied to the LLM as additional context.
Call 1

What happened?

Describe the unusual pattern found in the uploaded journey.

InputModel evidence only · no RAG
Call 2

What might it mean?

Explain possible causes while keeping uncertainty visible.

InputModel evidence + reviewed cause knowledge
Call 3

What should I do next?

Give practical guidance suited to the current component and risk level.

InputModel evidence + reviewed action knowledge
01

Role

Define who Granite is helping.

“Write for a vehicle owner without automotive training.”
02

Task

Ask one question in this call.

“What should I do next?”
03

Evidence

Limit which facts may be used.

“Use the current risk state and matched action knowledge.”
04

Boundaries

Keep technical work with a qualified mechanic.

“Do not instruct the owner to test wiring or replace a part.”
05

Fixed output

Return the same four decision fields in JSON.

“Now · Service timing · Stop driving and seek help if · Tell the mechanic”
First output
“coolant_temp is high and the cooling fan has failed.”
RAW FIELD · coolant_tempCONFIRMED CLAIM · has failedINCOMPLETE EXPLANATION
Rule-based quality score
Start1.00
Raw field−0.20
“has failed”−0.20
Too short−0.20
Blocked0.40
One targeted correction
Original task + failed output + exact warnings
“The cooling system shows a high-risk pattern because the coolant temperature is higher than expected. This pattern needs prompt professional attention.”
1.00 · RELEASE
Below 0.80 · block and correct once. Only the failed section is regenerated.
01

Automated technical checks

Required JSON structure
Use of supplied evidence
No confirmed-fault wording
Technical actions kept with a mechanic
→THEN
02

Manual report review

Relevance of retrieved knowledge
Connection to the displayed signals
Clarity for a vehicle owner
Appropriate level of certainty

Building the architecture · 04

The evidence is now a clear action plan.

01 · COMPLETE
Data
Clean vehicle history
→
02 · COMPLETE
Detection
Change + likely system
→
03 · COMPLETE
Explanation
Meaning + next action
→
04 · NEXT
Dashboard
See it · understand it · act
Evidence + verified guidanceInput
cooling_degradation · Medium
confidence 84.2%
coolant 85°C · rising 5.05°C/min
possible systems sensor · fan · coolant flow
risk-threshold projection 5 trips · 75.02% within 10 trips
→GROUNDED
EXPLANATION
Report outputDashboard input
Owner-facing result
What this means for you
What changed · coolant is low for this reference and rising too quickly
What it may mean · a sensor or cooling-fan issue may be developing
What to do · check coolant and leaks; arrange an inspection within days
How does the owner see the warning, understand it, and take action?

Dashboard

From local setup to owner action.

Charlotte Yu · Jintong He

What can the user see
How to Run Locally

Four steps to reproduce the local pipeline.

1
Prepare project
2
Install tools
3
Run pipeline
4
Open dashboard
Commands

Each step has a matching command group.

1git clone https://github.com/granite-lifeline/granite-lifeline.git cd granite-lifeline
2./setup.sh
3ollama serve ollama pull granite4.1:8b uv run python -m report_layer.rag.knowledge_indexer uv run python -m report_layer.rag.symptom_knowledge_indexer
4uv run streamlit run dashboard/app.py
Upload OBD-II CSV File ?
Upload
200MB per file • CSV
Cooling System
HighRisk level
Air Intake System
MediumRisk level
Cooling System
HighRisk level
Failure Prediction This component has already reached the High-risk threshold based on the current pattern. Arrange a professional inspection soon.
This is a risk-pattern estimate, not a confirmed mechanical fault.
Risk Trend
08-0208-0308-0408-0508-07
Last updated: 2017-08-02 05:40
Key Signals
SignalReadingNormal RangeUnitStatus
Coolant Temperature84.090.0–95.0°CAbnormal
Coolant Temperature Rise Rate5.50690.0–2.0°C/minAbnormal
Vehicle Speed70.00.0–218.0km/hNormal
Manifold Air Pressure132.036.0–237.0kPaNormal
Diagnostic Report Confidence: 68%
What's happening
Why this matters
What you should do

Conclusion

From OBD-II data to guidance an owner can act on.

01 · INPUT
Raw OBD-II readings
→
02 · DATA
Clean, contextualised data
→
03 · MODEL
Early risk evidence
→
04 · REPORT
Grounded explanation
→
05 · OWNER
Visible action
We built and ran the complete pipeline on the available Seat Leon dataset.

Conclusion

What have we demonstrated—and what remains unproven?

DEMONSTRATED
✓
The complete pipeline runs
✓
Baseline history establishes context
✓
Unexpected change becomes guidance
✓
Uncertainty remains visible
NOT YET DEMONSTRATED
?
Confirmed mechanical failures
?
How early a real fault appears
?
Other vehicles and models
?
Impact on owner decisions
Our results validate the architecture—not universal fault prediction.

Future work

How do we move from one vehicle dataset to broader use?

01 · DEEPEN
More data from this vehicle
→
02 · EXPAND
Recognise and explain more changes
→
03 · VALIDATE
More vehicles of the same model
→
04 · GENERALISE
Other vehicle models
First understand one vehicle well. Then validate the same model. Only then expand to other vehicles.

Thank you — questions welcome.

Charlotte Yu · Jintong He · Lei Pei · Qiuting Fu · Ray Wang · Lucca Zhou

TECHNICAL PIPELINE REFERENCE One continuous data flow · implementation, evolution, evidence and limitations
07

End-to-end integration · inputs, checks and failure paths

ORCHESTRATION · DASHBOARD controls the sequence and checks that each handoff is available. Each receiving layer then validates its own input contract before processing. The layers do not call one another directly.
DASHBOARD UPLOAD
  • 1 raw CSV, or ≥5 dated CSVs
  • Original filenames retained
→
DATA INPUT GATE
  • Non-empty; valid KIT filename
  • 11 required columns; minimum rows
  • History: each file valid, names unique, date ordered
PASS →FAIL →
DATA RUNS
  • Single file or one joint history run
  • Trip boundaries preserved
STOP
  • Specific upload error
  • No downstream run
DATA OUTPUT · ONE PAIR
  • production_features.csv · 46-column sample evidence
  • proxy_decisions.csv · 21-column rule decisions
→
MODEL INPUT GATE
  • Feature file exists; proxy file exists when reported
  • Feature schema, calibration and 1 Hz timing
  • Usable ≥700-row segment; proxy schema valid
PASS →FAIL →
MODEL RUNS
  • One isolated batch subprocess
  • Writes one model_output.json
STOP
  • Non-zero ERROR: or timeout
  • Dashboard shows failure
MODEL OUTPUT · ONE JSON
  • summary · highest primary-risk window
  • windows · every eligible window
→
REPORT INPUT GATE
  • Output file exists and parses as JSON
  • Select affected components
  • Pydantic checks fields, values and relationships
PASS →FAIL →
REPORT RUNS
  • One report per affected component
  • Each section validated before release
STOP
  • Invalid Model output
  • No report generation
REPORT OUTPUT · IN MEMORY
  • Model evidence retained
  • What was observed
  • Possible causes
  • Recommended actions
  • Component-keyed result + source label
→
DASHBOARD ACCEPTANCE GATE
  • At least one affected component
  • Detect all-empty Report fallback
  • Only accepted result updates session state
PASS →FAIL →
DISPLAY
  • Overview, detail and risk history
  • Key signals, report and export
STOP
  • Report unavailable card
  • Previous state not overwritten
IF ANY BOUNDARY CHECK FAILS · Processing stops before the next layer and the Dashboard shows that the upload could not be completed.
UPLOADOriginal KIT CSV file
DATAClean signals, features and rule evidence
MODELBatch anomaly and component risk JSON
REPORTValidated report for each affected component
DASHBOARDOverview, detail evidence and export
TEAMWORK EVIDENCE

Teamwork across four connected components

IBM supplied the project direction. The team converted it into Sprint work, reviewed code through GitHub and used regular records and updates to keep parallel development aligned.

01

IBM brief and MoSCoW priorities

PriorityOriginal IBM brief
MustAnalyse temperature, RPM and load data; train a model to predict component failure; use Granite to explain predictions.
ShouldVisualise failure risk over time.
CouldSimulate different driving conditions.
Won'tInclude real-time in-vehicle integration.
WHAT THE TEAM REFINED

The dataset contained no verified failures, so the final system reports anomaly risk and leaves fault confirmation to a mechanic.

The team investigated the What-If page, then removed it because the available data could not support a reliable driving-condition simulation.

IBM required Granite and substantial use of BoB. The student team remained responsible for the architecture, implementation and tests.

02

From the delivery plan to a completed Jira task

Project Gantt chart showing seven Sprints and five major Epics
PROJECT GANTT · SEVEN SPRINTS AND THE PLANNED DURATION OF EACH MAJOR EPIC
Jira task GL-129 with four completed subtasks
SPRINT 4 EVIDENCE · GL-129 CONNECTED THE FIRST THREE AVAILABLE MODEL OUTPUTS TO THE DASHBOARD; THE FINAL PIPELINE LATER SUPPORTED FIVE TYPES

How the work was organised

EPICA major functional area, such as System Integration & Pipeline.
→
USER STORY OR TASKA user-facing outcome or a defined piece of engineering work, such as GL-129.
→
SUBTASKA smaller action required to complete its parent. GL-129 contained GL-130 to GL-133.
SPRINTA two-week period containing selected Stories, Tasks and Subtasks. It is a timebox, not another level in the hierarchy.
03

Sprint meetings recorded plans, progress and improvements

Confluence folders containing Sprint planning, stand-up, progress update and viva dry-run records
CONFLUENCE RECORD INDEX · MEETING DOCUMENTS ORGANISED BY SPRINT
PLANNINGRecorded each group's intended work and the next action items.
STAND-UPRecorded completed work, current work and blockers for each member.
RETROSPECTIVERecorded what worked and what the team needed to change in the next Sprint.
S3 PLANNINGPlans, cross-layer interface work and action itemsOpen evidence
Sprint 3 planning record page 1
PAGE 1 OF 1
S5.1 STAND-UPCompleted work, work in progress and blockers by team memberOpen evidence
Sprint 5 week 1 stand-up record page 1
PAGE 1 OF 5
Sprint 5 week 1 stand-up record page 2
PAGE 2 OF 5
Sprint 5 week 1 stand-up record page 3
PAGE 3 OF 5
Sprint 5 week 1 stand-up record page 4
PAGE 4 OF 5
Sprint 5 week 1 stand-up record page 5
PAGE 5 OF 5
S3 RETROSPECTIVECommunication, documentation and interface improvementsOpen evidence
Sprint 3 retrospective record page 1
PAGE 1 OF 1
04

Jira automation kept task and code-review status in step

Jira automation that creates a GitHub branch when a Task or Story moves to In Progress
PROJECT EVIDENCE · JIRA AUTOMATION FOR THE IN PROGRESS TRANSITION
IN PROGRESSCreate a named GitHub branch from develop.
IN REVIEWOpening a pull request updates the Jira work item.
DONEMerging the approved pull request completes the Jira workflow.

The automation made progress easier to follow. Acceptance criteria, review and tests still determined whether the work was complete.

05

The CI workflow checked each proposed code change

TRIGGERPush or pull requestdevelop or main
→
ENVIRONMENTUbuntu and Python 3.11Checkout code, restore the uv cache and install uv.lock.
→
STATIC CHECKSpy_compile then flake8Find syntax errors, style problems and common code issues.
→
BLOCKING TESTSpytest -m "not model_download"Run the core suite without external model downloads.
→
EXTERNAL TESTSpytest -m model_downloadCheck the real Hugging Face download path. Failure warns but does not block unrelated work.
WHY THE LAST STEP IS SEPARATENetwork availability and a cold model cache can fail independently of the repository code, so the workflow records this result without failing every pull request.
06

Regular updates made cross-layer progress visible

Discord Sprint 2 project and team update sent to IBM
PROJECT EVIDENCE · TEAM, CONTRACT AND CI UPDATE TO IBM, 22 JUNE 2026
SUPERVISORMeeting approximately once a week.
IBMProgress update approximately once per Sprint.
TEAMJira, Discord and meeting records used between scheduled meetings.

Shared interfaces allowed each group to develop against the same field definitions before the complete pipeline was connected.

01 · DATA LAYER

Raw recording → governed evidence

DATA LAYER TEAM
D0

Upload intake and run isolation

Reject recordings that cannot satisfy the Data or Model contract before expensive processing begins, then preserve every accepted run as a traceable set of artefacts.

6 REJECT CODES
User-uploaded KIT CSVOriginal filename carries the recording date.
Pre-run intake checksFilename, readable CSV, required columns, ≥700 rows, ≥700 s duration; multi-file uploads also reject duplicate file names.
Isolated processingA temporary copy is processed; the original file is never touched; each run gets its own run ID.
Post-cleaning acceptanceLongest cleaned segment must reach ≥700 contiguous rows, or no_usable_segment is raised (run kept for inspection).
Intake rejection codeTriggered when
bad_filenameThe recording date cannot be recovered from the file name.
missing_columnsThe CSV does not carry the required KIT source columns.
too_few_rowsFewer than 700 raw rows, or under 700 seconds of recording.
unreadable_csvThe file cannot be parsed as CSV at all.
duplicate_upload_filenamesA multi-file upload history repeats the same KIT file name.
These two “700”s are both intake-time, but they check different things: 700 raw rows is a cheap row-count screen, and 700 seconds checks real elapsed time separately, because KIT’s raw sampling rate is uneven (roughly 6–12 Hz), so rows do not equal seconds. A third “700” — ≥700 contiguous cleaned rows — is not an intake check at all: it can only be known after cleaning has produced segment_id, so it is judged after the pipeline runs (see the last flow step above), not before.
CODE PATHdata_layer/pipeline_data/upload_contract.py :: validate_upload_csv(), validate_usable_segment()data_layer/run_pipeline.py :: run_data_pipeline_for_upload(), run_data_pipeline_for_uploads()data_layer/pipeline_data/paths.py :: RunLayout117 selected Data contract tests passed in 9.46s
D1

Continuity-aware cleaning

Turn irregular OBD-II recordings into 1 Hz evidence without inventing continuity across missing time, invalid samples or recording boundaries.

249,694 ROWS · 81→118
Raw source identityFilename date + in-file time; stable chronological file order.
UTC timelineLocalise in Europe/Berlin, sort timestamps and keep the last duplicate.
Hard-invalid maskingWhole-file pass: values outside physical bounds are set to missing, before any segment split.
Trip → segmentsA gap greater than 3 seconds on the masked timeline starts a new continuous segment.
Segment-safe 1 Hz gridResample each segment independently; fill only complete gaps of at most 2 seconds.
RuleThreshold / behaviour
New segmentRaw timestamp gap >3 s starts a new segment_id.
Short-gap fillGaps of ≤2 s are filled on the 1 Hz grid; longer gaps are left missing, never bridged.
Hard-invalid valuesOutside physical bounds → set to missing, but flagged (had_hard_invalid_source).
Suspicious valuesPhysically possible but unusual → kept as-is, flagged (is_suspicious); never silently rewritten.
Cross-field checksFlags contradictions, e.g. RPM=0 with speed>0, MAF=0 with RPM>0, sub-zero coolant in warm ambient.
IdentityBreaks whenGranularity
trip_idNever breaks within one source file.Coarsest — file-level provenance.
segment_idRaw timestamp gap >3 s — a genuine recording break.Medium — “was recording paused or restarted”.
continuity_block_idNot a D1 output — computed on demand by downstream feature-engineering (D3) and proxy (D4) scripts, each with its own required-valid-signal set. Breaks whenever any row in that set fails the quality-valid mask (missing / imputed / suspicious / hard-invalid) or the row/timestamp is not perfectly consecutive — on both sides, even with no time gap at all.Finest, but caller-dependent — no single block count for the whole corpus.
Same segment ≠ same block: a segment only breaks on a genuine recording gap (>3 s). A single bad or flagged reading inside an otherwise continuous segment still splits the block on both sides of it — so a 700-row segment can still fail to contain any 700-row block. Rolling features and Model windows are gated on continuity_block_id, the strictest of the three identities.
249,694cleaned rows, full corpus
81 → 118trips → segments
>3s / ≤2sbreak / fill thresholds
12 / 12continuity tests passed
CODE PATHdata_layer/data_cleaning/src/cleaning_core.pydata_layer/pipeline_data/continuity.py :: build_quality_valid_mask(), build_continuity_blocks()data_layer/data_cleaning/src/cleaning_config.yaml (v3.0)
D2

Hierarchical operating-condition stratification

Give every 1 Hz sample physically meaningful context, so normal warm-up, idle and high-load behaviour are not mistaken for faults—and real deviations are not hidden by global averages.

77.85% POST-WARMUP
Segment-bounded signalsSpeed smoothing, differencing and cumulative MAF reset at every segment.
Thermal stateengine_off / warmup / post_warmup / unknown.
Kinematic child stateidle / high_load / acceleration / deceleration / steady_driving.
Auditable contextCombined state + confidence + explicit missing-field flags.
Thermal state (parent)Determined by
engine_offRPM <50.
warmupEngine on, post-warmup condition not yet met.
post_warmupEngine on, coolant ≥75°C, plus hot-idle RPM <850, cumulative intake air ≥1,500 g, or IAT−ambient >8°C.
unknownRPM missing; forward-filled from the prior state within the same segment.
Child state (kinematic)Determined by
idleSpeed <1 km/h and |acceleration| <0.15 m/s².
high_loadMoving and VSP ≥20 kW/t or acceleration ≥1.2 m/s².
accelerationMoving, acceleration ≥0.15 m/s², not already high load.
decelerationMoving, acceleration ≤−0.15 m/s², not already high load or accelerating.
steady_drivingMoving, none of the above conditions met.
Context labels, not fault labels: thermal and child state only describe what the vehicle was doing, not whether anything was wrong. Precedence is Idle → High Load → Acceleration → Deceleration → Steady Driving, and fragments shorter than 3 s are cleaned up within their segment. post_warmup itself is inferred, not measured: the dataset has no catalyst-temperature channel, so coolant ≥75°C plus one corroborating signal (hot idle, cumulative intake air, or IAT−ambient heat soak) stands in for true catalyst warm-up.
77.85%post-warmup, 194,394 / 249,694 rows
35.39%steady driving, largest child state
99.99%high confidence, 249,669 rows
3 ssmoothing + fragment cleanup window
CODE PATHdata_layer/operating_condition_statistics/src/operating_condition_analysis.py :: add_operating_conditions(), build_counts(), build_rules()Outputs: operating_condition_enriched.csv, operating_condition_counts_overall.csv, operating_condition_signal_summary.csv, operating_condition_rules.csvdocs/adr/102-operating-condition-stratification.md
D3

Frozen feature contract and production assembly

Convert governed 1 Hz evidence into one deterministic, versioned Data–Model handoff whose fields, order, ownership and provenance cannot drift silently.

46 COLUMNS · FROZEN
00 · Contract gateValidate authoritative inputs, keys, quality tables and frozen manifest.
10 / 20 / 30 / 40Build atomic, engine-start, window and calibrated feature families separately.
41 · Strict assemblerOne-to-one joins on identical sample keys; no extra columns and no imputation.
Production handoff46 ordered columns plus an adjacent checksum-bearing stage manifest.
LayerWhat it computesWhy kept separate
B1a · Atomic (8)Per-row deterministic values: segment gap, engine state, thermal deltas, pedal/RPM slopes.Cheapest tier — no calibration input, no cross-row window state.
B1b · Calibrated residuals (2)Deviation from an offline physics baseline: speed-density MAF residual, pedal-mapping residual.Depends on the frozen calibration registry — a separately audited failure surface.
B2 · Engine-start context (6)Facts about the most recent engine start: episode ID, elapsed time, starting ECT/AAT/IAT.Needs its own episode-boundary logic, distinct from per-row or windowed builders.
B3 · Window features (8)60–180 s rolling aggregates: MAF integral, ECT rate, intake stability, rolling std-devs, MAP range.Gated on continuity blocks — a window can never be built across an invalid or discontinuous stretch.
Handoff to Model: Script 41 assembles exactly 46 ordered columns — 4 sample keys + 16 context fields + 24 engineered features (8+2+6+8 above) + 2 provenance fields — checked against a frozen manifest (feature_schema.v1, v1.2.0). Unexpected columns are rejected, key order and dtypes are validated, and the assembler performs no imputation: missingness stays visible rather than being silently filled. A new feature requires an explicit contract/version change, so it can never appear silently in the production CSV.
CODE PATHdata_layer/contracts/feature_manifest.v1.jsondata_layer/feature_engineering/src/00_input_contract_validator.py, 10_atomic_feature_builder.py, 20_engine_start_context_builder.py, 30_window_feature_builder.py, 40_calibrated_feature_builder.py, 41_production_feature_assembler.pyOutputs: features/41_production/production_features.csv, production_features_manifest.json
D4

Executable proxy failure evidence

Screen physically plausible fault symptoms into a deliberately small set of runtime checks, preserving the difference between a verdict, supporting evidence, a pending precursor and a design that was never executable.

14 CHECKS / 5 FAMILIES
DTC candidate catalogueSAE J2012 codes collected before assuming any are executable.
Standards-side screeningFiltered on domain visibility, ownership, signal coverage, reconstructability and 1 Hz sampling observability.
Observability + baseline censusRequires an enable window plus stable baseline evidence for a margin/persistence rule.
19 Jul freeze · ADR 10314 executable sub-checks across 5 families frozen; everything else stays research-only.
StageWhat it does
50 · Rule stateChecks each sub-check's guards/thresholds against the frozen calibration baseline; produces per-row eligibility and raw rule evidence.
60 · Event evidenceDetects driver-demand pedal-step events, for the one sub-check (5-S1) that needs a transient trigger.
61 · Duration evidenceBuilds bounded persistence episodes from Stage 50's rule state, for every sub-check whose verdict depends on a sustained condition.
70 · DecisionReads only Stages 50/60/61 — never the raw feature table again. Assigns decision_role (verdict / pending_precursor / support / arbitration_evidence) and result_state (pass / triggered / not_evaluable / pending), attributes a candidate DTC and whether it is actually emitted, and records confidence, margin and calibration version. Writes proxy_decisions.csv — the artefact handed to the Model layer.
Role ≠ result ≠ DTC emission: a candidate DTC label only identifies the diagnostic family — only a permitted verdict row with dtc_emitted=true actually authorises a code, and support/arbitration rows always carry dtc_emitted=false. not_evaluable means a live check lacked a valid opportunity, not that a rejected design ran. These are controlled proxy-screening decisions, not confirmed real faults — ADR 201 keeps the Model layer from training on them as ground truth.
CODE PATHdata_layer/proxy_failure/src/50_rule_state_builder.py, 60_event_evidence_builder.py, 61_duration_evidence_builder.py, 70_proxy_decision_builder.pyproxy_failure_definition.md (authoritative) · proxy_support.md (research companion) · calibration_registry.v1.json (frozen baseline)Outputs: proxy/70_decisions/proxy_decisions.csv, proxy_decision_manifest.jsondocs/adr/103-proxy-screening-and-theoretical-basis.md
D5

Offline calibration and governed residual features

Keep the baseline reference fixed: user uploads may apply approved transformations, but can never redefine “normal” by fitting on the vehicle currently being diagnosed.

48/48 PASS · 66-TRIP COHORT
Approved baseline cohortOffline/admin evidence—not the current user upload.
90 · Reproduction auditRefit approved methods only to compare against frozen values.
Checksum-bound releaseRegistry + feature contract + evidence identities frozen together.
40 · Online applicationRead-only transforms produce two residual features; no fit path exists.
InvariantWhat it guarantees
Offline-only fittingOnline scripts 00–70 forbid fitting, quantile search and threshold selection (online_fit_paths_forbidden=true; Script 40 exposes no fit function) — a slowly failing upload can never teach the system its own decline is "normal".
Frozen 66-trip reference cohortEvery proxy family (cooling, MAF, MAP, pedal, intake temperature) calibrates against the same fixed cohort_trip_count: 66 cohort — no family cherry-picks its own subset.
SHA-256 checksum bindingRegistry (calibration_registry.v1.json) and feature-manifest checksums are embedded and verified before online use; Script 40 fails closed on any drift.
Null on invalid inputQuality-invalid required inputs yield null residuals rather than a fabricated estimate.
What 48/48 PASS proves — and what it doesn't: the audit (recalibrate_20260723) proves the frozen release can be reproduced from its declared evidence within tolerance. It does not prove the calibration generalises to every make, engine or sensor package — broader external-vehicle validation remains future work.
CODE PATHdata_layer/calibration/calibration_registry.v1.json, calibration_registry.v1.manifest.jsondata_layer/feature_engineering/src/90_calibration_registry_builder.py :: run_calibration_audit()data_layer/feature_engineering/src/40_calibrated_feature_builder.py :: _validate_release_bundle(), build_calibrated_features()calibration_audit_manifest.json — 48 PASS / 0 FAIL
D6

Graded synthetic fault-injection validation

Challenge every executable proxy end to end with paired, single-signal interventions—testing severity response, independent contexts and diagnostic semantics without claiming synthetic detection is field recall.

14 / 14 ACCEPTED
Eligible baseline windowOriginal state, quality, confidence and continuity guards already satisfied.
Target signal onlyMild, moderate and strong intervention; guard signals remain unchanged.
Recompute dependenciesAffected features rebuilt; frozen stages 50/60/61/70 rerun unchanged.
Paired adjudicationCompare baseline/injected result, DTC identity, role and emission semantics.
Design factorValue / rule
Severities3 ordered levels (mild / moderate / strong); detection rate must not decrease as severity rises.
Independent trips per severity3, so one favourable trip can't be counted as independent evidence.
Scoped observations14 cases × 3 severities × 3 trips = 126.
Unique injected runs42 — one copied run hosts all 3 severity points for a case/trip.
Acceptance rulePer case curve, not per observation: non-decreasing detection rate, ≥0.8 at strongest severity, and the equivalent baseline must not already be positive.
87/126 vs 14/14 is not a contradiction: 87 of the 126 individual observations hit their expected outcome — mild/boundary injections are intentionally allowed not to trigger (e.g. 0/3 → 2/3 → 3/3 across severities still counts as coherent). The registered unit of acceptance is the case curve, not the single observation: all 14 curves passed and every strongest-severity point reached 3/3. Validated: implementation reachability, monotonic severity response, repeatability. Not validated: real-world prevalence, physical causation or workshop diagnostic accuracy.
126scoped observations
42unique injected runs
14 / 14case curves accepted
148 testsfull Data-layer verification
CODE PATHdata_layer/fault_injection/configs/fault_injection_cases.v1.jsondata_layer/fault_injection/src/run_fault_injection.pyOutputs: fault_injection_summary_20260903T051231Z.csv/.jsontest_fault_injection_campaign.py
D7

Two deliveries, one traceable Data handoff

Hand Model both sample-grain forecasting evidence and decision-grain proxy evidence from the same isolated run, while preserving their different meanings, consumers and claim boundaries.

2 DELIVERIES · SAME RUN
One RunLayoutCleaning through stage 70 shares run ID, paths and source identity.
Sample-grain delivery46-column production_features.csv for forecasting and context.
Decision-grain deliveryproxy_decisions.csv for governed verdict forwarding and evaluation.
Absolute handoff pathsSummary exposes both artefacts plus ordered stage manifests.
Boundary questionA · production_features.csvB · proxy_decisions.csv
Upstream inputStage 41 reads only the four feature-family tables (10/20/30/40), joined on identical sample keys.Stage 70 reads only Stages 50/60/61's evidence outputs plus the frozen calibration registry — never the raw feature table again.
FormatCSV, sample grain — one row per 1 Hz sample, exact 46 ordered columns (DataLayerOutput contract).CSV, decision grain — one row per sub-check × evaluation unit.
Required fieldsEvery column is a required key — nullable fields stay as required keys rather than being dropped.trip_id, segment_id (nullable), evidence start/end timestamps, decision_role, result_state, dtc_candidate_label, dtc_emitted, margin, confidence, calibration version.
Can Data redefine field meaning here?No — Stage 41 only joins already-computed fields verbatim, with strict type/order checks.No — Stage 70 only routes existing evidence into a decision; it doesn't redefine what the evidence means.
Invalid / missing inputNo imputation at the interface — missingness stays visible through explicit null semantics rather than being silently filled.not_evaluable when a live check lacks a valid opportunity — never forced into pass or triggered.
What Model (next layer) receives512-context / 96-forecast windows and residual scoring — measurement/features, not a diagnostic conclusion.Governed verdict forwarding and evaluation/filtering — must not be merged column-by-column into the 46-column feature table (different cardinality).
Tests covering this boundaryField contract: valid/optional/nullable fields, missing required field, wrong type.Decision semantics: role/result/DTC attribution. Shared with A: both deliveries come from one run (default stops at 41, proxy stages opt-in, all stages share one run).
CODE PATHdata_layer/run_pipeline.py :: run_data_pipeline(), run_data_pipeline_for_upload()shared/interface_models.py :: DataLayerOutputtests/test_interface.py, data_layer/tests/proxy_test/test_70_proxy_decisions.py, data_layer/tests/pipeline_data_test/test_public_pipeline_contract.pydocs/INTERFACE.md v1.3–v1.4
02 · MODEL LAYER

Forecast evidence → bounded risk

MODEL PIPELINE · VIVA BACKUP
M0

Input checks and forecast windows

The model only forecasts when the input data is complete, valid and continuous.

46 COLUMNS · 512s INPUT · 96s FORECAST
512 secondsPast data from six signals at 1 Hz.
96 secondsForecast for the same six signals.
INPUT MUST PASS
  • Required fields must be present and numeric.
  • At least 700 continuous rows from one driving section.
  • Correct sampling rate, file format and calibration version.
INVALID DATA STOPS THE FORECAST
  • Impossible values are repaired when safe or rejected.
  • More than 5% invalid values stops the run.
  • Separate driving sections are never joined.
The forecast uses 608 rows: 512 for the past and 96 for the future. The 700-row requirement leaves room to find one complete window. After checks, 61 of 118 sections across 59 trips were usable.
CODE PATHmodel_layer/ttm-related/src/model/input_validation.pymodel_layer/ttm-related/src/model/kit_residual_detector.py::select_segment()model_layer/ttm-related/src/model/prepare_finetune_split.py
M1

Forecasting normal behaviour without fault labels

TTM learns normal driving patterns without using fault labels.

NORMAL DATA · NO FAULT LABELS
1 · Read history512 seconds of six signals: RPM, speed, coolant, MAP, MAF and throttle.
2 · Scale signalsEach signal is placed on a comparable scale before forecasting.
3 · ForecastTTM predicts the next 96 seconds of all six signals.
4 · ComparePredictions return to real units and are compared with recorded values.
ApproachTraining targetDecision
Fault classifierNeeds verified examples of each faultNot used: the dataset has no confirmed fault labels.
TTM forecastingNormal signal behaviourSelected: no proxy type or DTC is used as a target.
WHY THIS AVOIDS CIRCULAR LABELS

Using the same proxy rules for training and testing would mean that the rules created both the training answers and the test answers.

ROLE OF PHYSICAL RULES

Rules are added after the forecast to help explain the result. They are not TTM training labels.

The selected TTM model uses 512 seconds of input and a 96-second forecast. Other window sizes would require new training, calibration and testing; this project did not compare them.
CODE PATHmodel_layer/ttm-related/src/model/kit_residual_detector.py::run_ttm_forecast()model_layer/ttm-related/src/model/finetune_ttm.pydocs/adr/201-residual-detection-over-classification.md
M2

Risk scores and ranked results

We turn forecast errors into risk scores and rank the five supported fault types.

3 MODEL · 2 DATA
Fault typeResult comes fromMain evidence
Cooling degradationModel LayerForecast error + temperature checks
Reduced airflowModel LayerForecast error + airflow checks
Accelerator pedalModel LayerForecast result + pedal agreement
IAT sensor faultData LayerNo response, stuck signal and cold start
MAP/load plausibilityData LayerMAP and MAF disagreement
Forecast errorAverage absolute difference across the 96-second forecast.
Normal rangeDivide by the signal's normal range so different units can be compared.
Physical checksCombine the forecast result with component-specific rules.
Risk scoreRank all five fault types on a 0–1 evidence scale.
RANKED OUTPUT

The highest score is the main candidate, not a diagnosis. The second candidate appears in secondary_risk.

Only the top two are shown; real multi-fault cases were not available.

SCORE ≠ PROBABILITY

A risk score of 0.8 means strong unusual modelled behaviour. It does not mean an 80% probability of mechanical failure.

CONFIDENCE

For Model results, confidence measures how stable the forecast errors are. It is not diagnostic probability.

Data results keep confidence values of 0.9, 0.6 or 0.35.

FORWARDED SCORE

Emitted DTC: 0.9 · triggered rule: 0.6 · supporting evidence: 0.5 · otherwise: 0.

Forwarding does not mean TTM detected them. The Model Layer checks and passes on the IAT and MAP/load results without running the Data Layer rules again. Related codes: cooling P0128/P0217; airflow P0101/P0102; pedal P2138; IAT P0111–P0113; MAP/load P0106.
CODE PATHmodel_layer/ttm-related/src/model/kit_residual_detector.py::calculate_residuals()model_layer/ttm-related/src/model/kit_residual_detector.py::calculate_ranked_risks()model_layer/ttm-related/src/model/proxy_decision_forwarding.pyshared/interface_models.py::ModelLayerOutput
M3

Training and final model

We trained on normal driving data and selected one final model for evaluation and runtime use.

EPOCH 5 · SELECTED
Model setupObserved resultFinal use
Original pretrained TTMComparison starting pointComparison only
Epoch-five fine-tuned TTM5.23% lower forecast MAE on 250 validation windowsSelected for evaluation and runtime
Signal-mixing versionOverall MAE worsened from 54.9666 to 55.6532Rejected
5training epochs
47training trips
250validation windows
+0.15%MAP MAE change
WHY NORMAL DATA ONLY?

The model must learn normal behaviour. Training on faults could teach it to predict the fault and make the forecast error smaller.

WHAT DID NOT IMPROVE?

MAP MAE changed from 21.63 to 21.66. Relationships with other signals may explain this, but the project did not prove the cause.

The result uses one data split, one random seed and no external benchmark. Both formal evaluation and the final pipeline load OFFICIAL_DETECTOR_MODEL_PATH.
CODE PATHmodel_layer/ttm-related/src/model/finetune_ttm.pymodel_layer/ttm-related/src/model/compare_finetune_residuals.pymodel_layer/ttm-related/outputs/finetune_residual_comparison_e5_lr5e-5.json
M4

Testing and alarm threshold

We tested artificial faults, measured each fault type separately and set a threshold to limit false alarms.

154 INJECTIONS · 11 CONTROLS
154artificial-fault cases
11unchanged controls
11MAP checks
0.5214macro F1
Fault typeMeasured resultWhat it tells us
CoolingRecall 1.0000Artificial cooling changes were caught consistently.
Accelerator pedalPrecision 1.0000 · recall 0.3247Few false positives, but many injected cases were missed.
MAF / airflowPrecision 0.2000 · recall 0.0455Only 2 of 44 artificial cases were detected correctly.
Clean dataStart with normal-driving sections that pass the input checks.
Add a faultApply one of 14 controlled fault settings.
Run detectorCompare the ranked result with the known artificial fault.
Check controlsMeasure alarms on unchanged data separately.
Threshold stageNormal-data alarmsDecision
Before calibration3 of 11Too many false alarms
Selected threshold: 0.41291 of 11Best macro F1 with false alarms ≤10%
High-risk threshold: 0.9—Unchanged
Overall macro F1 0.5214 shows that performance is uneven across fault types. A 5.23% lower forecast MAE means better normal forecasting, but it does not prove better fault detection. Eight of 11 sections helped choose the threshold and also appeared in the final results, so the evaluation is not fully independent.
CODE PATHmodel_layer/ttm-related/src/model/fault_injection.pymodel_layer/ttm-related/src/model/risk_threshold_calibration.pymodel_layer/ttm-related/src/model/run_synthetic_evaluation.pymodel_layer/ttm-related/outputs/synthetic_eval_metrics_e5_lr5e-5_calibrated.json
M5

Risk trends across trips

We use risk scores from several trips to estimate when the score may reach 0.9. This is not a failure prediction.

≥5 TRIPS · HIGH = 0.9
1 · Collect tripsAt least five chronological trips are required.
2 · Average riskCalculate one average score per component and trip.
3 · Fit trendCheck whether the component risk is rising over time.
4 · Project 0.9Estimate trips to High and the chance of reaching it within ten trips.
6Seat Leon trips
22real Model windows
≈5trips to risk 0.9
75.02%chance within ten trips
75.02% MEANS

The estimated chance that this detector's risk score reaches 0.9 within ten trips.

75.02% DOES NOT MEAN

The probability that the vehicle or component will mechanically fail.

Fewer than five trips returns no estimate. A flat or falling trend does not produce a near-term trip estimate. No real failure occurred in this example.
CODE PATHmodel_layer/ttm-related/src/model/failure_estimation.py::estimate_from_history()model_layer/ttm-related/src/model/risk_history.pyreport_layer/evaluation/viva_real_case
M6

Passing results between layers

A fixed interface passes Model results to Dashboard and stops when the files do not match.

INTERFACE v1.6 · STOPS ON ERROR
Data LayerSends cleaned features and rule decisions.
Model LayerChecks both files, forecasts and ranks every window.
DashboardReceives the highest-risk summary and all ranked windows.
Report LayerBuilds component histories from primary and secondary results.
Input stateModel response
Wrong columns, sampling rate or versionStop and return one clear ERROR: message.
No continuous 700-row sectionStop without producing a risk result.
Valid inputReturn the highest-risk summary and every analysed window.
ERROR CAUGHT

A historical °C/s versus °C/min mismatch changed values by 60 times.

OWNERSHIP CORRECTED

P0116 and P0128 were moved to the correct cooling owner.

217tests passed
18tests skipped
235tests collected
73/73core interface tests
Tests confirm that the code and interfaces work as designed. They do not prove that a mechanical diagnosis is correct.
CODE PATHdashboard/csv_pipeline.pymodel_layer/ttm-related/src/model/kit_residual_detector.pymodel_layer/ttm-related/src/model/validate_output.pyshared/interface_models.py
M7

Limits and next steps

This is an offline research system, not a confirmed diagnostic tool for real vehicles.

OFFLINE RESEARCH SYSTEM
Evidence supportsEvidence does not support
Response to artificial faultsAccuracy on real breakdowns
Risk trends across recorded tripsMechanical-failure probability or remaining useful life
Proxy-rule evidenceA confirmed diagnosis
Offline pipeline behaviourReadiness for live vehicle deployment
1 · Real faultsTest another dataset with mechanically confirmed failures.
2 · Better airflowCompare measured MAF with an estimate from pressure and engine speed.
3 · More vehiclesRepeat testing across different vehicles and driving conditions.
4 · Deployment checksAdd live integration, monitoring, model records and safety review.
AVAILABLE EVIDENCE

Artificial-fault tests and normal-driving risk histories from one vehicle dataset.

MISSING EVIDENCE

None of the 81 normal-driving trips contains a confirmed fault, repair result or failure event.

Important: risk score, confidence and the chance of reaching risk 0.9 measure different things. None gives the probability that the vehicle will break.
CODE PATHmodel_layer/ttm-related/src/model/fault_injection.pymodel_layer/ttm-related/src/model/run_synthetic_evaluation.pymodel_layer/ttm-related/src/model/kit_residual_detector.pyreport_layer/evaluation/viva_real_case
03 · REPORT LAYER

Evidence behind the final report pipeline

FINAL DESIGN · VIVA BACKUP
R0

Granite model selection

Four locally runnable Granite models were compared on the same Cooling case and the same three-call sequence.

HISTORICAL SELECTION TEST
CriterionWeight3.3 2B3.3 8B4.1 3B4.1 8B
Plain language30%3445
Grounding25%4445
Valid JSON20%2555
Action quality15%3345
Uncertainty10%4444
Weighted total100%3.154.054.154.85 · selected
Evidence boundary: this single controlled case supported the project choice; it is not a general ranking of Granite models.
R1

Automotive knowledge storage design

The storage method had to keep reviewed knowledge inspectable and select only the part required by the current report section.

20 REVIEWED RECORDS
OptionObserved trade-offDecision
Full knowledge in every promptUnrelated material and unnecessary prompt lengthNot used
Semantic vector retrievalUseful when the category is unknown; ranking varied across recordsCompared
Metadata-filtered ChromaDBExact filtering from the anomaly key already supplied by the Model LayerSelected
5supported anomaly types
1cause record per type
3risk-specific action records
20records rebuilt from YAML
Storage evidence
  • report_layer/rag/grounded_knowledge.yaml is the reviewed source.
  • report_layer/rag/knowledge_indexer.py rebuilds the collection and checks all five anomaly types.
R2

How knowledge is indexed and retrieved

knowledge_indexer.py prepares the reviewed YAML for retrieval; rag_retriever.py selects the two records required for the current report.

BUILD TIME → RUNTIME
OVERALL KNOWLEDGE PATH
grounded_knowledge.yamlReviewed source
knowledge_indexer.pyBuild records
ChromaDB collectionStore 20 records
rag_retriever.pySelect for prompts
KNOWLEDGE_INDEXER.PY · BUILD TIME
Load YAMLproxy_failures
ValidateFive required types
Cause recordDescription + causes
Action recordsLow · Medium · High
Upsert4 records × 5 types
VerifyCount = 20
RAG_RETRIEVER.PY · RUNTIME
Open collectionOtherwise fallback
Find causesType + section
Find actionsType + section + risk
Return bothTo context construction
R3

Metadata and semantic retrieval comparison

RAG is the overall method. This experiment compared two ways of choosing which reviewed record enters a prompt.

HISTORICAL · 7 TYPES
Retrieval methodCorrect recordAverage time
Metadata + section7/7≈0.34 ms
Semantic + section4/7≈61.0 ms
Metadata + whole document7/7≈0.31 ms
Semantic + whole document6/7≈57.4 ms
METADATA FILTERING · EXACT LABELS

Use fields already supplied by the Model Layer to select one matching record.

collection.get(where={"$and": [
  {"anomaly_type": {"$eq": anomaly_type}},
  {"section": {"$eq": "actions"}},
  {"risk_level": {"$eq": risk_level}}
]})
SEMANTIC RETRIEVAL · MEANING SIMILARITY

Embed a symptom description and rank records by similarity, even when their wording differs.

collection.query(
  query_texts=[symptom_description],
  n_results=1
)
Scope: this design experiment used seven categories available at that stage. Production now supports five anomaly types.
R4

Model evidence received

One Medium-risk Cooling case is used through the next three cards to show what the Report Layer receives and what it may explain.

STRUCTURED INPUT
CURRENT RESULT
  • cooling_degradation
  • Risk score: 0.5021 · Medium
  • Prediction confidence: 0.842
SUPPORTING SIGNALS
  • Coolant: 85°C · reference 90–95°C
  • Rise rate: 5.0486°C/min · reference 0–2
  • Both values remain Model Layer evidence.
TRIP ESTIMATE

estimated_cycles_to_failure = 5
Projected trips until the risk trend reaches the High threshold.

TEN-TRIP PROBABILITY

estimated_failure_probability = 0.7502
Chance of crossing High within the fixed next ten trips.

These projections describe the Model risk threshold. They are not a probability or countdown of confirmed mechanical failure.
R5

What the Model input contract checks

ModelLayerOutput(**summary) triggers these Pydantic rules before retrieval or any Granite call begins.

SHARED/INTERFACE_MODELS.PY
report_layer/pipeline/report_generator.py: validated = ModelLayerOutput(**summary_payload) → only the validated object is passed to build_context_with_rag(validated).
1 · REQUIRED FIELDS + ALLOWED VALUES
AnomalyType = Literal[
  "cooling_degradation",
  "air_intake_maf_anomaly",
  "accelerator_pedal_sensor",
  "intake_air_temperature_sensor_fault",
  "map_load_signal_plausibility_fault"
]
RiskLevel = Literal["Low", "Medium", "High"]

class ComponentRiskOutput(BaseModel):
  timestamp: str
  anomaly_type: AnomalyType
  risk_score: float
  component: AnomalyType
  prediction_confidence: float
  key_signals: List[KeySignal]
  estimated_cycles_to_failure: Optional[int] = Field(...)
  estimated_failure_probability: Optional[float] = Field(...)
2 · FIELD VALIDATORS
@field_validator(
  "risk_score",
  "prediction_confidence"
)
def _score_in_unit_range(value):
  if not 0 <= value <= 1:
    raise ValueError(...)

@field_validator(
  "estimated_failure_probability"
)
def _failure_probability_in_unit_range(value):
  if value is not None and not 0 <= value <= 1:
    raise ValueError(...)

@field_validator("reference_range")
def _reference_range_has_two_values(value):
  if len(value) != 2:
    raise ValueError(...)
3 · RELATIONSHIP VALIDATORS
@model_validator(mode="after")
def _component_mirrors_anomaly_type(self):
  if self.component != self.anomaly_type:
    raise ValueError(...)

@model_validator(mode="after")
def _secondary_risk_is_distinct_and_ranked(self):
  secondary = self.secondary_risk
  if secondary is None:
    return self
  if secondary.anomaly_type == self.anomaly_type:
    raise ValueError(...)
  if secondary.risk_score > self.risk_score:
    raise ValueError(...)
Boundary: schema validation catches malformed or internally invalid input. It cannot prove that a structurally valid anomaly classification is correct for the real vehicle.
R6

Prompt context construction

Technical fields are converted into a short evidence summary while their values and meanings remain unchanged.

PREPARED EVIDENCE
Readable namescoolant_temp → coolant temperature.
Reference check85°C is below the supplied 90–95°C range.
RelationshipLow temperature + rapid warming activates the defined thermal pattern.
Meaning retainedRisk, confidence and projections remain separate values.
“Coolant temperature is below its reference range, while its rate of increase is above the expected range.”
# Abridged from build_context_with_rag()
context = build_context(ttm_output)
risk = ttm_output.risk_level.lower()

rag_knowledge = retrieve_all(
    anomaly_type=ttm_output.anomaly_type,
    risk_level=risk,
)

# R7 applies the signal-relevance gate here.
return {
    "context": context,
    "fault_knowledge": fault_knowledge,
    "actions_knowledge": actions_knowledge,
}
contextCurrent Model evidence prepared for owner-facing language.
fault_knowledgeReviewed possible-cause record matched to the anomaly type.
actions_knowledgeReviewed guidance matched to the anomaly type and risk level.
What a defined relationship means
  • The relationship is activated only when the required signals are available.
  • It describes a combination chosen in the implementation; Granite does not discover a new statistical correlation.
  • report_layer/pipeline/context_injection.py prepares the context.
R7

Knowledge admitted to this Cooling case

Retrieval finds category-matched material; an additional check decides whether its explanation fits the direction of the current signals.

CASE-LEVEL ADMISSION
RETRIEVED POSSIBILITIES
  • Coolant circulation
  • Thermostat operation
  • Cooling fan operation
  • Coolant level or quality
  • Radiator airflow or restriction
CURRENT EVIDENCE
  • Temperature is below its reference range.
  • Temperature is rising faster than expected.
  • Pure overheating wording does not fit this observation.
  • Cautious circulation, sensor or fan checks may still be presented as possibilities.
Current limit: Cooling has this signal-direction check. The other four production categories currently use exact metadata filtering only.
R8

Three production Calls

Each Call has a separate question, evidence allowance and output contract.

SEQUENTIAL GENERATION
CALL 1 · WHAT HAPPENED?Evidence

Prepared Model fields only.

Example

“Coolant temperature is low while warming faster than expected.”

CALL 2 · WHY MIGHT IT MATTER?Evidence

Accepted Call 1 + admitted cause knowledge.

Example

“This pattern may relate to sensing or coolant circulation.”

CALL 3 · WHAT SHOULD I DO?Evidence

Risk state + role-separated action knowledge.

Example

“Arrange an inspection and report the two readings to the mechanic.”

CallFixed boundaryOutput
1No diagnosis or retrieved causeObserved pattern
2Possible causes remain conditionalExplanation
3Technical procedures assigned to mechanicNow · timing · stop conditions · mechanic information
R9

Example-value leakage

A specific number in an early few-shot example appeared in a new report even though it was absent from that report’s Model input.

OBSERVED FAILURE → REGRESSION CASE
CURRENT COOLING INPUT

Failure-threshold probability: 1.0
Estimated trips: 4
No value of 0.31% was supplied.

EARLY EXAMPLE

An unrelated accelerator-pedal example contained the illustrative value 0.31%.

“The model projects a very low probability (0.31%) of crossing the High-risk threshold…”
Remove concrete examplesProduction prompts no longer receive reusable fictional values.
Write direct rulesEvery reported value must occur in the current Model context.
Keep the failureThe leaked-value case becomes a regression input.
VerifyThe corrected run uses 100% and four trips from the real input.
R10

Three Calls through sequential gates

Each accepted section becomes part of the next prompt. A failed section is corrected before generation continues.

GENERATE → VALIDATE → CONTINUE
CALL 1
Observation

Model evidence and prepared context.

Clean → validate → one correction if required

CALL 2
Possible causes

Model evidence + accepted Call 1 + cause knowledge.

Clean → validate → one correction if required

CALL 3
Recommended actions

Model evidence + accepted Calls 1–2 + action knowledge.

Clean → validate → one correction if required

DEPENDENCY

Call 2 receives the accepted observation. Call 3 receives both accepted earlier sections. Each Call also keeps access to the relevant Model evidence.

RELEASE PATH

After Call 3 passes, the complete cleaned chain is checked once more. Release follows only when all three sections score at least 0.80; otherwise the report uses the fallback path.

R11

How the Validator checks one section

The same validation framework selects rules for the section, scores the result and returns exact warnings for one correction attempt.

SECTION-SPECIFIC RULES · ≥0.80
Generated JSON
Owner-facing cleanup
Select section rules
Score + warnings
Accept or correct once
RULE SEVERITY
−0.10−0.20−0.40

Start at 1.00 · release at 0.80 or above

Threshold check: The 0.80 threshold was checked against ten generated reports covering the five supported anomaly types.
ONE FRAMEWORK · DIFFERENT CHECKS
ObservationEvidence meaning · unsupported values · fault certainty
Possible causesHedging · repetition · signal direction
ActionsFour roles · urgency · owner safety
SharedHigh-risk projection consistency · release threshold
FAILED SECTION

“coolant_temp is high and the cooling fan has failed.”

Warnings identify the raw field, unsupported direction and confirmed-fault wording.

TARGETED CORRECTION

The correction prompt receives the original task, rejected JSON and exact warnings. Only this section is regenerated and checked again.

R12

Two failure paths

The pipeline treats a failed model request differently from a readable JSON response that breaks a report rule.

RETRY · CORRECT · FALLBACK
TECHNICAL FAILURE
  • Request error
  • Timeout
  • Malformed JSON
  • Response: retry the call, up to three attempts.
CONTENT FAILURE
  • JSON parses successfully
  • Section scores below 0.80
  • Response: one targeted correction, then check again.
Second content failureStop further generation.
Fallback objectKeep the same interface shape.
Preserve evidenceRisk values and signals remain available.
Leave text emptyDashboard shows report generation unavailable.
R13

Four knowledge conditions

The final evaluation changed only the knowledge supplied to Granite while keeping the model, prompts, temperature, inputs and Validator fixed.

4 × 5 = 20 REPORTS
ConditionKnowledge suppliedPurpose
No retrieved knowledgeModel context onlyGeneration baseline
Cause knowledgeReviewed descriptions and possible causesTest explanation grounding
Cause + raw workshop actionsCause records and unseparated proceduresExpose audience/action-role problems
Cause + role-separated actionsOwner observations, timing and stop conditions separated from mechanic proceduresFinal design
OWNER GUIDANCE

What to observe now, when to arrange service, and which visible warning signs require urgent help.

MECHANIC INFORMATION

Scan-tool checks, wiring inspection, calibration and replacement decisions are presented as requests for a qualified mechanic.

R14

Automated checks and manual review

The same 20 reports produced machine-checkable results and separate human judgements about meaning and audience.

FINAL V5 EVIDENCE
01 · AUTOMATED EXECUTION AND RULE AUDIT
ConditionReleasedValid structureNo unsupported numbersNo unsafe owner procedure detectedFallback
Controlled baseline5/55/55/55/50/5
Cause RAG5/55/55/55/50/5
Cause + raw actions5/55/55/55/50/5
Cause + role-separated actions5/55/55/55/50/5
02 · STRUCTURED MANUAL REVIEW
ConditionInput faithfulnessRetrieval relevanceKnowledge useAudienceAction safetyUncertainty
Controlled baseline5 passN/AN/A5 pass5 pass5 pass
Cause RAG5 pass4 pass · 1 partial1 pass · 4 partial3 pass · 2 partial5 pass5 pass
Cause + raw actions5 pass4 pass · 1 partial1 pass · 4 partial3 pass · 2 partial5 pass5 pass
Cause + role-separated actions5 pass4 pass · 1 partial1 pass · 4 partial3 pass · 2 partial5 pass5 pass
Where did manual review record partial?Reason
Retrieval relevance · IATThe record matched the intake-temperature category, but all displayed temperatures were within range. The current evidence could not show whether a circuit issue or a connector issue better matched this case.
Knowledge use · MAF, pedal, IAT and MAPRetrieved knowledge added several plausible causes, but the current signals did not contain enough evidence to choose which cause best matched the case. Cooling was the only full pass.
Audience · MAF and IATThe reports respected the evidence and action boundaries, but their cause explanations remained longer and more technical than ideal for a non-specialist reader.
SUPPORTED
  • 20/20 reports generated and released
  • Input meaning and uncertainty preserved
  • Owner–mechanic action boundary maintained
  • RAG usually retrieved the correct category
LIMITATION
  • RAG-only measures cannot prove improvement over baseline
  • Only five unlabelled cases per condition
  • No verified fault or technician judgement
  • Signals could not select among several possible causes
NEXT EVALUATION
  • Use technician-verified faults and repair outcomes
  • Include more vehicles and operating conditions
  • Check signal direction before admitting knowledge
  • Compare both conditions on specificity, clarity and decision usefulness
Meaning of partial: the report was released, but manual review found a material limitation in that dimension. These results assess report behaviour and rule compliance; technician-verified faults are still required to assess mechanical accuracy.
04 · DASHBOARD

Evidence behind the owner-facing Dashboard

13 TOPICS · VIVA BACKUP
D1

User journey and data sources

Start with an explicit choice: analyse uploaded OBD-II data or explore a labelled demonstration.

UPLOAD · DEMO · SOURCE
PrepareStart the local dependencies and Streamlit Dashboard.
ChooseUpload raw CSV trips or select Explore with demo data.
AnalyseValidate, then run Data → Model → Report.
ExploreReview affected components, signals and owner guidance.
SourceMeaning
uploaded / uploaded_historyResults from a live single-upload or joint-history request.
realReal-source output identified by the loader.
mockDemonstration values; not evidence of a live run.
missingNo available component data.
The per-component _data_source map carries provenance. Demo/missing warnings prevent silent substitution; accepted uploads override static data within the active session.
Implementation and evidence
  • dashboard/data_store.py: session result → cached static data → emergency fallback; static path can be set through DASHBOARD_TEST_DATA.
  • dashboard/data_loader.py: source map and real/mock loading. GL-85 → GL-129/131 → GL-132/133 established source-aware integration.
D2

Local deployment and usability

Live analysis relies on local Model dependencies and Ollama; installation remains a barrier for non-technical owners.

LOCAL LIVE ANALYSIS
CURRENT SETUP

Python, the Model environment, Ollama and Granite must be available. The hosted demonstration uses static results.

NEXT STEP

Package the application or provide a hosted inference service so owners can upload data without terminal setup.

DecisionReason / trade-off
Model batch subprocessUses the published CLI and isolates pinned Torch/Transformers dependencies; adds process startup and serialization overhead.
Interpreter selectionMODEL_LAYER_PYTHON → dedicated Model virtual environment → current Python.
Local artefactsTemporary raw/Model files are cleaned up; Data artefacts persist by run. Retention and privacy handling require further work.
Implementation and evidence
  • README.md and dashboard/README.md: live setup and hosted-demo scope.
  • dashboard/csv_pipeline.py::_run_model_layer(): fixed Model working directory and 120-second subprocess timeout.
D3

Multiple files as driving history

Accepted trips are ordered chronologically and processed together while retaining individual trip boundaries.

AT LEAST 5 TRIPS
OrderSort uploaded trips by filename date.
PreserveKeep original filenames and trip boundaries.
CombineData produces joint features and optional proxy decisions.
Analyse onceModel evaluates the combined artefact; Report generates component results.
The history route requires at least five chronological trips. Its uploaded_history label distinguishes the result. This request history is not a persistent cross-session vehicle record.
Implementation and evidence
  • dashboard/csv_pipeline.py::run_uploaded_csv_history_batch() and _run_data_layer_history().
  • The joint route calls Model once on combined production features and forwards optional proxy decisions; GL-398/399 closed the earlier proxy handoff gap.
D4

Upload validation

Reject unusable files before expensive processing and show the owner a readable reason.

SCHEMA · TIME · LENGTH
CheckBehaviour
Readable, non-empty CSVReject absent bytes, empty data and parsing failures.
11 required raw columnsValidate time, temperature, pressure, RPM, speed, airflow, throttle and pedal fields; accept the known °C/°C header variant.
At least 700 rows per admission checkProtect the temporal input requirement; 700 rows is about 11.7 minutes at 1 Hz. UI guidance asks for roughly 15 minutes.
Original KIT filenamePreserve the recording-date identity instead of renaming to a generic temporary file.
Continuity and usable segmentsThe Data Layer can still reject a file that passes the initial column/row checks.
A failed required check rejects the analysis and leaves the previous accepted result intact. Passing the admission checks does not guarantee a usable Model window.
Implementation and evidence
  • dashboard/csv_validator.py, dashboard/pages/overview.py and dashboard/csv_pipeline.py.
  • GL-256–258: upload UI, schema/row validation and readable failure cards. Model context/future requirements are 512 + 96 points, with additional segment constraints.
D5

Dashboard responsibilities

Dashboard coordinates the layers and presents their evidence; it does not make a new vehicle prediction.

CONTRACT → DISPLAY
OwnerResponsibility
ModelRisk score, supplied risk level, attribution confidence, key signals, window history and optional projections.
ReportPreserve structured evidence and add anomaly description, possible cause and recommended action.
DashboardValidate/adapt payloads, select and sort components, draw rings/charts, compare signal ranges and format exports.
BOUNDARY ADAPTER

Accept single outputs or batch envelopes; normalise legacy coolant_slope to ect_rate_180s, remove retired coolant_stability and canonicalise component aliases.

PORTABLE OUTPUT

PDF offers Summary, Failure Prediction, Risk Trend, Key Signals and Diagnostic Report. CSV offers feature, value, unit, range and status; multiple components can be packaged as ZIP files.

The ring uses the 0–1 score; the High/Medium label comes from the payload. Export reformats existing results and never reruns the diagnosis.
Implementation and evidence
  • dashboard/data_loader.py: extraction, normalisation and Pydantic validation.
  • dashboard/export_helper.py: missing-value formatting, safe filenames, PDF/CSV and ZIP packaging.
D6

Risk metrics and interpretation

Different indicators answer different questions; none is a validated probability of mechanical breakdown.

RISK ≠ CONFIRMED FAILURE
IndicatorMeaning
Risk scoreModel internal anomaly indicator on a 0–1 scale.
Risk levelModel-supplied Low, Medium or High category.
Prediction confidenceSupport for the component attribution; not breakdown probability.
Risk TrendInternal risk scores across recorded analysis windows in the request.
Estimated cycles to failureProjection of trips until the score crosses the High-risk threshold; not physical remaining useful life.
Estimated failure probabilityProjected chance of crossing that threshold within the prediction horizon; not calibrated mechanical-failure probability.
Keep Model notes and unavailable states visible. Explain these as risk-pattern estimates and possible causes; recommend professional inspection without claiming a confirmed fault. Projection headline wording remains an area for alignment.
Implementation and evidence
  • shared/interface_models.py, docs/INTERFACE.md, dashboard/pages/detail.py.
  • Model backup explains threshold-crossing semantics and the lack of technician-confirmed failure labels.
D7

Component selection and no-risk state

The Overview prioritises supported components that may need attention.

MEDIUM / HIGH
FilterCreate cards for Medium and High results.
PrioritiseHigh first, then descending risk score.
Open detailStore the selected component and show supporting evidence.
No affected resultShow the no-supported-risk state instead of misleading cards.
Five canonical types are supported. The current viva example shows Cooling System as High and Air Intake System as Medium. No supported risk found is limited to the analysed data and implemented detectors.
Implementation and evidence
  • dashboard/data_store.py::get_overview_components(), dashboard/pages/overview.py and dashboard/anomaly_display.py.
  • Aliases are canonicalised and duplicate components suppressed.
D8

Normal and abnormal signals

Compare each available reading with its supplied reference range and preserve the evidence next to the status.

VALUE VS RANGE
Input / resultInterpretation
Reading + reference rangeValues outside the range are Abnormal; values within it are Normal.
Coolant example84.0°C and rise rate 5.5069°C/min are shown as abnormal in the viva payload.
Supporting normal readingsVehicle speed 70.0 km/h and manifold pressure 132.0 kPa remain normal.
Owner-readable labelsShow signal name, value, unit and range; glossary tooltips explain technical terms.
A signal status is a comparison with the supplied range, not an independent confirmed fault. Normal signals remain useful context alongside abnormal readings.
Implementation and evidence
  • dashboard/pages/detail.py::_render_signals(), dashboard/glossary.py and dashboard/export_helper.py.
D9

Missing data display

Unavailable evidence remains unavailable instead of becoming an invented measurement or estimate.

MISSING ≠ ZERO
Missing itemDisplay behaviour
TrendShow an empty/unavailable state when usable history is insufficient.
Key signalsShow no available signals rather than fabricated readings.
Projection fieldsRetain null/unavailable values; do not invent percentages or cycles.
Component sourceKeep the missing/demo provenance visible.
Export valuesNormalise missing values explicitly in the portable report.
Missing evidence differs from generation failure. An empty generated explanation requires the failure check described in D10.
Implementation and evidence
  • dashboard/pages/detail.py, dashboard/data_store.py and dashboard/export_helper.py.
  • Historical edge fixtures cover empty, missing, single-component, no-signal and no-trend states.
D10

Processing failures and empty reports

A failed generation must not be presented as evidence that the vehicle is healthy.

FAILURE → RECOVERY
FailureResponse
Model unavailable / non-zero exitShow a readable Model error; extract the documented ERROR message when available.
Model timeout / invalid outputHandle the 120-second timeout, missing JSON or invalid JSON as pipeline failure.
Returned components all have empty descriptionsTreat the returned components as generation failure even when their shape is valid.
RecoveryClear loading state in finally; only accepted results update the Dashboard session.
Current check applies only when components exist and every description is empty. No affected components follows the no-supported-risk path (D7). One non-empty description can allow a partial multi-component report through; explicit generation status and per-component validation would be stronger.
Implementation and evidence
  • dashboard/pages/overview.py::_handle_uploaded_csv_submit(); GL-261 and commit bc50667 document silent-fallback detection.
  • Report catches normal generation failures internally, so an outer timeout exception alone cannot detect them. Progress percentages are stage milestones, not measured completion estimates.
D11

Explanation grounding and errors

Granite translates structured evidence and reviewed knowledge into owner-facing language, but grounding does not guarantee correctness.

EVIDENCE + REVIEWED KNOWLEDGE
Model evidenceRisk, signals, context and projection notes.
Retrieved knowledgeReviewed category-specific causes and risk-specific actions.
Constrained generationSeparate what, why and action; apply report validation.
Dashboard evidenceKeep original signals visible beside generated possibilities.
WHAT GROUNDING ADDS

Relevant knowledge and structured facts constrain the explanation. Owner observations are separated from mechanic procedures.

WHAT REMAINS UNCERTAIN

A plausible cause is still a hypothesis. Report checks address specified errors, but cannot prove every explanation matches a real mechanical condition.

See Report backup for the final Validator, correction/fallback route and manual-review results. Dashboard presentation cannot independently establish diagnostic accuracy.
Implementation and evidence
  • report_layer/report_generator.py and the reviewed RAG knowledge; Report backup cards R3–R11.
D12

Testing evidence and limitations

Contract tests, controlled substitutes and saved live runs answer different validation questions.

WORKFLOW ≠ ACCURACY
EvidenceWhat it establishes
Validation and UI testsUpload gates, source labels, sorting, missing states, loading recovery, risk display, glossary and report rendering.
Controlled pipeline testsData/Model handoff, proxy forwarding, timeouts and output contracts without expensive live calls.
Export testsDeterministic PDF/CSV transformation, packaging and formatting.
Saved real-case runThe available vehicle data can exercise the integration route; it does not establish accuracy on confirmed breakdowns.
Historical Dashboard run171 passed, 1 skipped on 2 Aug 2026; a historical snapshot, not a fresh test result.
The largest evidence gap is confirmed mechanical failures across vehicles. Also needed: wider live five-type coverage, accessibility evaluation, latency improvements and per-component failure handling.
Implementation and evidence
  • dashboard/tests/ and report_layer/evaluation/viva_real_case/.
  • Earlier focused runs recorded 40 loader/integration tests, 19 upload tests and 62 display/UI tests; their scopes overlap and should not be added together.
D13

User feedback and interface changes

Make risk easier to interpret without suggesting that a clearer interface makes the prediction more accurate.

RISK TREND · OWNER LANGUAGE
Interpretation problemInterface response
Percentage-led visuals can look like breakdown probabilityEmphasise Low, Medium and High risk levels while retaining the internal score for visual scale.
Risk Score Trend sounds like a probability forecastUse Risk Trend and explain that the chart represents an internal risk pattern.
Technical evidence is difficult to readUse owner-facing component names, glossary help and what / why / action sections.
User feedback supports presentation changes. It does not validate mechanical predictions; use the saved user-testing evidence when discussing participant findings.
Implementation and evidence
  • report_layer/evaluation/user_testing_ab/, dashboard/pages/detail.py and dashboard/glossary.py.
  • GL-41/42/44 introduced details, trends and signal evidence; GL-85/96 aligned Report data and confidence; later viva refinement changed risk wording.