End-to-end integration · inputs, checks and failure paths
- 1 raw CSV, or ≥5 dated CSVs
- Original filenames retained
- Non-empty; valid KIT filename
- 11 required columns; minimum rows
- History: each file valid, names unique, date ordered
- Single file or one joint history run
- Trip boundaries preserved
- Specific upload error
- No downstream run
production_features.csv· 46-column sample evidenceproxy_decisions.csv· 21-column rule decisions
- Feature file exists; proxy file exists when reported
- Feature schema, calibration and 1 Hz timing
- Usable ≥700-row segment; proxy schema valid
- One isolated batch subprocess
- Writes one
model_output.json
- Non-zero
ERROR:or timeout - Dashboard shows failure
summary· highest primary-risk windowwindows· every eligible window
- Output file exists and parses as JSON
- Select affected components
- Pydantic checks fields, values and relationships
- One report per affected component
- Each section validated before release
- Invalid Model output
- No report generation
- Model evidence retained
- What was observed
- Possible causes
- Recommended actions
- Component-keyed result + source label
- At least one affected component
- Detect all-empty Report fallback
- Only accepted result updates session state
- Overview, detail and risk history
- Key signals, report and export
- Report unavailable card
- Previous state not overwritten
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.
IBM brief and MoSCoW priorities
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.
From the delivery plan to a completed Jira task

How the work was organised
Sprint meetings recorded plans, progress and improvements
S3 PLANNINGPlans, cross-layer interface work and action itemsOpen evidence

S5.1 STAND-UPCompleted work, work in progress and blockers by team memberOpen evidence





S3 RETROSPECTIVECommunication, documentation and interface improvementsOpen evidence

Jira automation kept task and code-review status in step
develop.The automation made progress easier to follow. Acceptance criteria, review and tests still determined whether the work was complete.
The CI workflow checked each proposed code change
develop or mainuv cache and install uv.lock.py_compile then flake8Find syntax errors, style problems and common code issues.pytest -m "not model_download"Run the core suite without external model downloads.pytest -m model_downloadCheck the real Hugging Face download path. Failure warns but does not block unrelated work.Regular updates made cross-layer progress visible
Shared interfaces allowed each group to develop against the same field definitions before the complete pipeline was connected.
Raw recording → governed evidence
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
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.
no_usable_segment is raised (run kept for inspection).| Intake rejection code | Triggered when |
|---|---|
bad_filename | The recording date cannot be recovered from the file name. |
missing_columns | The CSV does not carry the required KIT source columns. |
too_few_rows | Fewer than 700 raw rows, or under 700 seconds of recording. |
unreadable_csv | The file cannot be parsed as CSV at all. |
duplicate_upload_filenames | A multi-file upload history repeats the same KIT file name. |
segment_id, so it is judged after the pipeline runs (see the last flow step above), not before.data_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
Continuity-aware cleaning
Turn irregular OBD-II recordings into 1 Hz evidence without inventing continuity across missing time, invalid samples or recording boundaries.
| Rule | Threshold / behaviour |
|---|---|
| New segment | Raw timestamp gap >3 s starts a new segment_id. |
| Short-gap fill | Gaps of ≤2 s are filled on the 1 Hz grid; longer gaps are left missing, never bridged. |
| Hard-invalid values | Outside physical bounds → set to missing, but flagged (had_hard_invalid_source). |
| Suspicious values | Physically possible but unusual → kept as-is, flagged (is_suspicious); never silently rewritten. |
| Cross-field checks | Flags contradictions, e.g. RPM=0 with speed>0, MAF=0 with RPM>0, sub-zero coolant in warm ambient. |
| Identity | Breaks when | Granularity |
|---|---|---|
trip_id | Never breaks within one source file. | Coarsest — file-level provenance. |
segment_id | Raw timestamp gap >3 s — a genuine recording break. | Medium — “was recording paused or restarted”. |
continuity_block_id | Not 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. |
continuity_block_id, the strictest of the three identities.data_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
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.
| Thermal state (parent) | Determined by |
|---|---|
engine_off | RPM <50. |
warmup | Engine on, post-warmup condition not yet met. |
post_warmup | Engine on, coolant ≥75°C, plus hot-idle RPM <850, cumulative intake air ≥1,500 g, or IAT−ambient >8°C. |
unknown | RPM missing; forward-filled from the prior state within the same segment. |
| Child state (kinematic) | Determined by |
|---|---|
idle | Speed <1 km/h and |acceleration| <0.15 m/s². |
high_load | Moving and VSP ≥20 kW/t or acceleration ≥1.2 m/s². |
acceleration | Moving, acceleration ≥0.15 m/s², not already high load. |
deceleration | Moving, acceleration ≤−0.15 m/s², not already high load or accelerating. |
steady_driving | Moving, none of the above conditions met. |
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.data_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
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.
| Layer | What it computes | Why 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. |
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.data_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
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.
| Stage | What it does |
|---|---|
| 50 · Rule state | Checks each sub-check's guards/thresholds against the frozen calibration baseline; produces per-row eligibility and raw rule evidence. |
| 60 · Event evidence | Detects driver-demand pedal-step events, for the one sub-check (5-S1) that needs a transient trigger. |
| 61 · Duration evidence | Builds bounded persistence episodes from Stage 50's rule state, for every sub-check whose verdict depends on a sustained condition. |
| 70 · Decision | Reads 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. |
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.data_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
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.
| Invariant | What it guarantees |
|---|---|
| Offline-only fitting | Online 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 cohort | Every 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 binding | Registry (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 input | Quality-invalid required inputs yield null residuals rather than a fabricated estimate. |
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.data_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
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.
| Design factor | Value / rule |
|---|---|
| Severities | 3 ordered levels (mild / moderate / strong); detection rate must not decrease as severity rises. |
| Independent trips per severity | 3, so one favourable trip can't be counted as independent evidence. |
| Scoped observations | 14 cases × 3 severities × 3 trips = 126. |
| Unique injected runs | 42 — one copied run hosts all 3 severity points for a case/trip. |
| Acceptance rule | Per case curve, not per observation: non-decreasing detection rate, ≥0.8 at strongest severity, and the equivalent baseline must not already be positive. |
data_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
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.
production_features.csv for forecasting and context.proxy_decisions.csv for governed verdict forwarding and evaluation.| Boundary question | A · production_features.csv | B · proxy_decisions.csv |
|---|---|---|
| Upstream input | Stage 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. |
| Format | CSV, 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 fields | Every 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 input | No 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) receives | 512-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 boundary | Field 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). |
data_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.4Forecast evidence → bounded risk
M0Input checks and forecast windows
The model only forecasts when the input data is complete, valid and continuous.
46 COLUMNS · 512s INPUT · 96s FORECAST
Input checks and forecast windows
The model only forecasts when the input data is complete, valid and continuous.
- Required fields must be present and numeric.
- At least 700 continuous rows from one driving section.
- Correct sampling rate, file format and calibration version.
- Impossible values are repaired when safe or rejected.
- More than 5% invalid values stops the run.
- Separate driving sections are never joined.
model_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.pyM1Forecasting normal behaviour without fault labels
TTM learns normal driving patterns without using fault labels.
NORMAL DATA · NO FAULT LABELS
Forecasting normal behaviour without fault labels
TTM learns normal driving patterns without using fault labels.
| Approach | Training target | Decision |
|---|---|---|
| Fault classifier | Needs verified examples of each fault | Not used: the dataset has no confirmed fault labels. |
| TTM forecasting | Normal signal behaviour | Selected: no proxy type or DTC is used as a target. |
Using the same proxy rules for training and testing would mean that the rules created both the training answers and the test answers.
Rules are added after the forecast to help explain the result. They are not TTM training labels.
model_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.mdM2Risk scores and ranked results
We turn forecast errors into risk scores and rank the five supported fault types.
3 MODEL · 2 DATA
Risk scores and ranked results
We turn forecast errors into risk scores and rank the five supported fault types.
| Fault type | Result comes from | Main evidence |
|---|---|---|
| Cooling degradation | Model Layer | Forecast error + temperature checks |
| Reduced airflow | Model Layer | Forecast error + airflow checks |
| Accelerator pedal | Model Layer | Forecast result + pedal agreement |
| IAT sensor fault | Data Layer | No response, stuck signal and cold start |
| MAP/load plausibility | Data Layer | MAP and MAF disagreement |
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.
A risk score of 0.8 means strong unusual modelled behaviour. It does not mean an 80% probability of mechanical failure.
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.
Emitted DTC: 0.9 · triggered rule: 0.6 · supporting evidence: 0.5 · otherwise: 0.
model_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::ModelLayerOutputM3Training and final model
We trained on normal driving data and selected one final model for evaluation and runtime use.
EPOCH 5 · SELECTED
Training and final model
We trained on normal driving data and selected one final model for evaluation and runtime use.
| Model setup | Observed result | Final use |
|---|---|---|
| Original pretrained TTM | Comparison starting point | Comparison only |
| Epoch-five fine-tuned TTM | 5.23% lower forecast MAE on 250 validation windows | Selected for evaluation and runtime |
| Signal-mixing version | Overall MAE worsened from 54.9666 to 55.6532 | Rejected |
The model must learn normal behaviour. Training on faults could teach it to predict the fault and make the forecast error smaller.
MAP MAE changed from 21.63 to 21.66. Relationships with other signals may explain this, but the project did not prove the cause.
OFFICIAL_DETECTOR_MODEL_PATH.model_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.jsonM4Testing and alarm threshold
We tested artificial faults, measured each fault type separately and set a threshold to limit false alarms.
154 INJECTIONS · 11 CONTROLS
Testing and alarm threshold
We tested artificial faults, measured each fault type separately and set a threshold to limit false alarms.
| Fault type | Measured result | What it tells us |
|---|---|---|
| Cooling | Recall 1.0000 | Artificial cooling changes were caught consistently. |
| Accelerator pedal | Precision 1.0000 · recall 0.3247 | Few false positives, but many injected cases were missed. |
| MAF / airflow | Precision 0.2000 · recall 0.0455 | Only 2 of 44 artificial cases were detected correctly. |
| Threshold stage | Normal-data alarms | Decision |
|---|---|---|
| Before calibration | 3 of 11 | Too many false alarms |
| Selected threshold: 0.4129 | 1 of 11 | Best macro F1 with false alarms ≤10% |
| High-risk threshold: 0.9 | — | Unchanged |
model_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.jsonM5Risk 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
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.
The estimated chance that this detector's risk score reaches 0.9 within ten trips.
The probability that the vehicle or component will mechanically fail.
model_layer/ttm-related/src/model/failure_estimation.py::estimate_from_history()model_layer/ttm-related/src/model/risk_history.pyreport_layer/evaluation/viva_real_caseM6Passing 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
Passing results between layers
A fixed interface passes Model results to Dashboard and stops when the files do not match.
| Input state | Model response |
|---|---|
| Wrong columns, sampling rate or version | Stop and return one clear ERROR: message. |
| No continuous 700-row section | Stop without producing a risk result. |
| Valid input | Return the highest-risk summary and every analysed window. |
A historical °C/s versus °C/min mismatch changed values by 60 times.
P0116 and P0128 were moved to the correct cooling owner.
dashboard/csv_pipeline.pymodel_layer/ttm-related/src/model/kit_residual_detector.pymodel_layer/ttm-related/src/model/validate_output.pyshared/interface_models.pyM7Limits and next steps
This is an offline research system, not a confirmed diagnostic tool for real vehicles.
OFFLINE RESEARCH SYSTEM
Limits and next steps
This is an offline research system, not a confirmed diagnostic tool for real vehicles.
| Evidence supports | Evidence does not support |
|---|---|
| Response to artificial faults | Accuracy on real breakdowns |
| Risk trends across recorded trips | Mechanical-failure probability or remaining useful life |
| Proxy-rule evidence | A confirmed diagnosis |
| Offline pipeline behaviour | Readiness for live vehicle deployment |
Artificial-fault tests and normal-driving risk histories from one vehicle dataset.
None of the 81 normal-driving trips contains a confirmed fault, repair result or failure event.
model_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_caseRisk evidence → grounded explanation
R0Validated input and deterministic context injection
Convert Model's machine-oriented object into a stable evidence brief that Granite can read—without asking the LLM to recalculate abnormality, reinterpret units or turn data-quality notes into mechanical causes.
ADR 301 · CONTEXT CONTRACT
Validated input and deterministic context injection
Convert Model's machine-oriented object into a stable evidence brief that Granite can read—without asking the LLM to recalculate abnormality, reinterpret units or turn data-quality notes into mechanical causes.
- Converts 0–1 risk/confidence into percentages for natural owner-facing language.
- Includes every key signal with value, unit and reference range; units are omitted cleanly when absent.
- Pre-computes abnormality instead of letting Granite inconsistently compare numbers.
- Maps code names such as
coolant_tempandspeed_density_maf_residualto readable labels. - Preserves normal signals because the contrast between normal and abnormal evidence matters in atypical/contradictory cases.
- Known multi-signal patterns cover thermal stress, MAF/MAP, dual pedal channels, intake temperature and MAP/load plausibility.
- Unknown multi-signal combinations are described as possible systemic patterns, not confirmed causes.
- Model notes are explicitly labelled as data-quality, repaired-value or disabled-detection information—not mechanical causes.
- Batch windows become displayable
risk_history, while generation uses the validated worst-risk summary. - Confidence generates language-strength guidance: >0.8 stronger predictive, >0.5 moderate, otherwise cautious.
Implementation evolution
Why not send raw Model JSON directly to Granite?
- Raw field names reduce readability and may leak into owner-facing output.
- The LLM could classify the same boundary value differently between runs.
- Structured headings act as a lightweight attention schema for three different prompt tasks.
- Explicit notes/projection sections preserve semantic boundaries that a flat JSON dump does not explain.
- The trade-off is coupling: context-format changes require prompt and evaluation updates.
Failure projection wording boundary
- The context passes Model's two estimate fields because they are part of the shared contract.
- Model notes state that the probability concerns crossing the High-risk score threshold, not mechanical failure probability.
- Prompts must preserve this limitation and must not turn a linear risk projection into remaining useful life.
- If estimates are null, the projection section is omitted rather than filled with invented values.
Code and integration evidence
context_injection.py:build_context(),build_context_with_rag(), display names and correlation maps.report_generator.py:_extract_summary_payload()and_extract_risk_history_payload().shared/interface_models.py: the initial shared Pydantic contract and later cross-layer alignment.- GL-178, GL-182 and GL-187 record resolution of estimate/notes interface misalignment.
R1RAG knowledge architecture and retrieval evidence
Retrieve only the fault explanation and action tier relevant to the known anomaly and current risk, grounding Granite in source-backed domain knowledge without exposing it to unrelated or over-urgent instructions.
ADR 303 · META + SECTION
RAG knowledge architecture and retrieval evidence
Retrieve only the fault explanation and action tier relevant to the known anomaly and current risk, grounding Granite in source-backed domain knowledge without exposing it to unrelated or over-urgent instructions.
fault_knowledge: 5 types × 4 sections = 20 documents.symptom_knowledge: 5 types × one merged document = 5 documents; retained for comparison.- The four production sections are exactly:
description_causes,actions_low,actions_medium,actions_high. - Collections are deleted/rebuilt on indexing so changed source text cannot hide behind stable document IDs.
- Startup validates all five interface anomaly names and exact expected document counts.
- Layer 1/2 need explanation and possible causes; Layer 3 needs actions.
- More importantly, Layer 3 must receive only the action tier appropriate to Low, Medium or High risk.
- A merged document contains all three tiers; a semantic match could expose “stop immediately” guidance to a Low-risk report.
- Section metadata makes retrieval deterministic and reduces token/attention dilution.
- Adding a type changes source/index content, not prompt templates or retrieval algorithm.
Historical evolution and design decisions
Exact historical 2×2 retrieval experiment
- A Meta+28: 7/7 = 100%; mean 0.341 ms.
- B Semantic+28: 4/7 = 57.14%; mean 61.011 ms.
- C Meta+7: 7/7 = 100%; mean 0.309 ms.
- D Semantic+7: 6/7 = 85.71%; mean 57.407 ms.
- Metadata was approximately 178.8× faster under section chunking and 186.0× faster under document chunking.
- The experiment used three timing trials per method and evaluation-authored symptom descriptions.
What semantic retrieval got wrong
electronic_throttle_tracking_faultfailed semantic retrieval under both chunking strategies.- Its symptom language overlapped strongly with pedal, RPM, airflow and load concepts, so embedding similarity did not preserve the exact intended type.
- Section-level semantic search also missed the historical IAT/heat-soak and MAF cases, giving only 4/7 accuracy.
- The exact Model enum made semantic uncertainty unnecessary for this interface.
RAG, fine-tuning and lookup table
- LLM fine-tuning: domain updates would require retraining and sources would be harder to inspect.
- Hard-coded lookup output: deterministic, with limited flexibility for combining live evidence into owner-readable language.
- RAG: keeps knowledge external, reviewable and updateable while Granite performs controlled language generation.
- Metadata retrieval is lookup-like for selection; the retrieved content supplies richer evidence to the generator.
Knowledge and generation boundary
- The collection uses declared automotive handbooks, workshop manuals and troubleshooting references.
- Retrieval reduces unsupported generation risk, while generated claims still require validation.
- The knowledge base contains vehicle-general guidance and project-classified urgency tiers.
Code and evaluation evidence
knowledge_indexer.py:create_documents_for_anomaly(),index_knowledge_base().symptom_knowledge_indexer.py: merged-document comparison collection.rag_retriever.py::retrieve_all()andcontext_injection.py::build_context_with_rag().retrieval_comparison.mdpreserves the historical 7-type experiment; current code truth is five types and 20/5 documents.
R2Three-layer Granite prompt chain
Generate the owner-facing report in three dependent, narrowly scoped steps so observed evidence, possible explanation and recommended action remain distinguishable—and so only the action layer receives risk-specific maintenance guidance.
ADR 302 · GRANITE 4.1:8B
Three-layer Granite prompt chain
Generate the owner-facing report in three dependent, narrowly scoped steps so observed evidence, possible explanation and recommended action remain distinguishable—and so only the action layer receives risk-specific maintenance guidance.
- All three: deterministic context, certainty guidance and audience=
non-technical vehicle owner. - Layer 1: receives Model evidence only and translates the observed pattern.
- Layer 2: also receives Layer 1 and filtered cause knowledge.
- Layer 3: receives both prior outputs plus governed action knowledge.
- Input context remains authoritative when retrieved knowledge conflicts with live evidence.
- Translate raw names and unexplained acronyms into plain language.
- Preserve uncertainty around possible mechanical causes.
- Use only supplied projection values and current risk.
- Return four roles in order: Now, Service timing, Stop driving and seek help if, Tell the mechanic.
- Technical checks are framed as requests for a qualified mechanic.
Why three focused calls?
- Each call has one semantic responsibility and one JSON key.
- Layer 2 builds on the written evidence; Layer 3 acts on the observation and cautious interpretation.
- Each failed section can be corrected independently.
- The trade-off is three local inference calls and accumulated latency.
Prompt evolution
- The three-stage decomposition established separate evidence, cause and action responsibilities.
- Positive and negative examples helped expose missing boundaries during development.
- Example details sometimes reappeared in output, so production templates retain the lessons as explicit rules.
- Action guidance was reorganised into owner and mechanic roles after controlled review.
Live validator scope
- Each generated section is scored against layer-specific deterministic checks.
- A score below 0.80 blocks release and creates exact correction instructions.
- The failed section receives one targeted regeneration attempt.
- A second failure returns an explicit error through the report fallback path.
Code evidence
prompts/layer1_description.txt,layer2_cause.txt,layer3_action.txt: current roles, tasks, constraints, edge cases and JSON contracts.report_generator.py::_correct_and_validate_layer()connects validation and one targeted correction.prompt_chain_validator.py::validate_layer1(),validate_layer2(),validate_layer3().- Prompt-refinement and final-ablation artefacts preserve the controlled inputs, outputs and review conclusions.
R3Production execution, retry and graceful fallback
Run the three dependent local-LLM calls defensively: accept parseable field-level JSON, retry transient or malformed responses, and preserve Model evidence in a schema-shaped fallback instead of crashing the Dashboard pipeline.
GL-241–246 · NEVER RAISES
Production execution, retry and graceful fallback
Run the three dependent local-LLM calls defensively: accept parseable field-level JSON, retry transient or malformed responses, and preserve Model evidence in a schema-shaped fallback instead of crashing the Dashboard pipeline.
extract_json()first usesjson.loads(response), then extracts an embedded outer object as a repair attempt.- Each layer is accepted only when parsing succeeds and its expected key exists.
- The cleaned section passes its live layer-specific validator at ≥0.80.
- Layer 2 starts after Layer 1; Layer 3 starts after both earlier sections pass.
- Pass-through Model fields, request-local risk history and three generated fields are assembled.
- Request or JSON failure uses the request retry path.
- A validator score below 0.80 triggers one targeted correction for that section.
- A second validation failure returns an explicit error and enters the report fallback.
_build_empty_report()keeps Model scores, signals, estimates, notes and risk history.- The function logs the failure and does not raise to its caller.
Why fallback is deliberately incomplete
- Inventing generic advice after a generation failure would hide the fact that grounding or generation did not complete.
- Preserving Model evidence allows Dashboard to show risk/signals while treating narrative fields as unavailable.
- The trade-off is that an empty report can still look structurally plausible; downstream UI must visibly distinguish “generation unavailable” from “no anomaly.”
- Current fallback uses safe defaults for absent pass-through keys, so strict downstream schema validation remains valuable.
Failure modes a viva question may target
- Ollama offline: connection failures retry, then fallback.
- Slow model: each request has a 120-second timeout; worst-case user latency can still be large across three layers.
- Markdown around JSON: outer-brace extraction may recover it.
- Valid JSON but wrong value type: expected key is accepted here; this reveals the need for final schema/semantic validation.
- Layer 3 failure after Layers 1/2: current all-or-fallback policy discards both generated strings, preserving atomic report consistency.
Historical implementation evidence
Code evidence
report_generator.py::generate_report(): outer non-raising boundary and sequential layer loops.extract_json(),call_ollama(),_build_empty_report().tests/test_report_generator.py: success, timeout, malformed JSON, invalid input and pass-through preservation on success/fallback; 6 tests passed in 0.30 s with HTTP mocked rather than a live Ollama dependency.
R4Controlled RAG ablation and manual review
Hold the generation pipeline constant, vary only the knowledge supplied to the prompts, and inspect what retrieval changes across all five current anomaly types.
4 CONDITIONS · 5 TYPES · 20 REPORTS
Controlled RAG ablation and manual review
Hold the generation pipeline constant, vary only the knowledge supplied to the prompts, and inspect what retrieval changes across all five current anomaly types.
- Baseline: Model context only.
- Cause RAG: reviewed component descriptions and possible causes.
- Full workshop RAG: cause knowledge plus workshop procedures.
- Owner-safe RAG: owner observations and mechanic requests governed separately.
- Granite 4.1 8B, temperature 0, fixtures, prompt rules and validator were held constant.
- Cause RAG: relevant in 4/5 cases; one Cooling case was partially relevant.
- Full workshop RAG: audience and action-role criteria passed in 1/5 cases.
- Owner-safe RAG: audience and action-role criteria passed in 5/5 cases.
- Release: all 20 reports completed, while two High-risk fixtures retained relationship-level contradictions.
- These are structured review outcomes, not diagnostic-accuracy percentages.
Why RAG scored lower in the typical case
- The RAG report gave technically specific advice—thermostat operation around 82°C, radiator, coolant and fan checks—but omitted the evaluator's expected word “soon,” losing 0.30 Actionability.
- It also exposed raw
coolant_temp, losing 0.30 Readability. - The baseline used more generic but keyword-friendly urgency language, so scored 1.00.
- GL-206 added
SIGNAL_DISPLAY_NAMESso future context uses “coolant temperature” rather than raw schema names; prompt rules were also tightened. - The honest interpretation is: RAG added useful specificity but presentation quality still needed refinement.
What the evaluator actually checks—and misses
- Factual Grounding detects whether any description number exists and matches a context number; it does not verify every numerical claim or unit.
- Readability uses a finite blacklist and regex acronym heuristic; plain language outside the list is not semantically assessed.
- Hedging performs substring matching, so “no confirmed fault yet” is incorrectly penalised because it contains
confirmed. - Actionability recognises fixed urgency words; a detailed safe action can lose points merely because it omits “soon.”
- Therefore 0.95 is an automated comparison supported by manual review—not proof of factual correctness or safety.
Contradictory evidence remains unresolved
- The contradictory fixture combines Low risk/31% confidence with abnormal coolant temperature at 108°C.
- RAG hedged successfully, but neither baseline nor RAG explicitly explained why the abnormal signal and Low classification disagree.
- A useful next design would detect input contradictions deterministically and require the report to surface them before possible causes.
- This limitation matters more than the perfect 1.00 heuristic score in that scenario.
Critical self-evaluation and future work
- Only three authored scenarios were tested, all cooling degradation; no real owner language or full five-type report set was evaluated.
- No automotive domain expert validated causal statements, urgency tiers or maintenance actions.
- Replace substring rules with semantic entailment/similarity and explicit negation handling.
- Add blinded human-in-the-loop review by vehicle owners and domain experts, inter-rater agreement, all anomaly types and real/mixed inputs.
- Integrate quality/schema gates into production only after deciding whether low-quality output should retry, degrade or be blocked.
Code and evidence
report_quality_evaluator.py::evaluate_factual_grounding(),evaluate_readability(),evaluate_hedging_appropriateness(),evaluate_actionability(),evaluate_report().rag_baseline_comparison_table.md: exact scenario and aggregate scores plus manual interpretation.rag_evaluation_criteria.md: Huang/Qi/ADR 302 grounding and >0.7 quality threshold.- GL-119/120/121 and GL-123 preserve the evaluation design, execution artefacts and report-ready comparison.
R5ReportLayerOutput contract and Dashboard handoff
Package machine evidence and owner-facing language in one shared shape: Model facts pass through unchanged, batch-window risk becomes request-local chart history, and Granite adds exactly three narrative fields.
INTERFACE v1.5 · 3 FIELD GROUPS
ReportLayerOutput contract and Dashboard handoff
Package machine evidence and owner-facing language in one shared shape: Model facts pass through unchanged, batch-window risk becomes request-local chart history, and Granite adds exactly three narrative fields.
risk_history distilled from the current batch's windows.- Pass-through:
timestamp,risk_score,risk_level,component,prediction_confidence,key_signals, two nullable estimate fields andnotes. - Maintained:
risk_history: [{timestamp, risk_score}] | null. - Generated:
anomaly_description: string,possible_cause: string,recommended_action: string[]. - The report does not recompute Model scores, ranges or projection values.
- Shared
ReportLayerOutputconstrains component enum and 0–1 score/probability ranges.
- Uses the same keys so Dashboard integration does not branch on an entirely different payload type.
- Model evidence and available request history are retained.
- Description and cause become empty strings; actions become an empty list.
- No explicit
report_generation_successflag exists; Dashboard must infer unavailable narrative from empty fields. - The generator returns a compatible dictionary but does not itself instantiate
ReportLayerOutput; strict final validation is a documented hardening opportunity.
How batch history becomes a Dashboard trend
- A batch envelope is validated, while its
summarysupplies the report's single-window facts. _extract_risk_history_payload()scanswindowsand keeps entries containing both timestamp and risk score.- The Report layer does not persist this list across Dashboard sessions; it describes only the current request/batch.
- Model component projections use separate in-request histories assembled from primary and
secondary_riskappearances. - The append-only, deduplicated primary-risk file remains a distinct audit and single-window estimation path.
Interface leadership embodied in the output
- The initial shared Pydantic model replaced an ambiguous single
explanation_textwith three report fields and structured key signals. - Sprint 3 alignment added/propagated
estimated_cycles_to_failure,estimated_failure_probabilityandnotesthrough Report and Dashboard-facing contracts. - GL-178, GL-182 and GL-187 resolved mismatches between shared models, context/generator code and interface documentation.
- GL-405/INTERFACE v1.5 aligns the current five-type enum, batch envelope and projection semantics.
What Dashboard may safely say
- Risk score is Model anomaly evidence; prediction confidence is attribution confidence; failure probability is a projected threshold-crossing probability. They are not interchangeable percentages.
- Generated possible causes remain hypotheses, even at High risk.
- Empty narrative means generation unavailable, not “the vehicle is healthy.”
- Any displayed cycle estimate must retain Model notes explaining why it is a trend projection rather than physical remaining useful life.
Code and contract evidence
shared/interface_models.py::ReportLayerOutputandRiskHistoryEntry.report_generator.py::_extract_risk_history_payload(),_build_empty_report(),generate_report().docs/INTERFACE.md§3.1–3.3 and v1.5 change history.tests/test_report_generator.pyverifies pass-through preservation on both success and fallback.- The wider current Report/interface group passed 78 tests in 0.95 s; this proves contract behaviour, not LLM narrative quality, which remains covered by the separate three-scenario and five-type review artefacts.
Evidence behind the final report pipeline
R0Granite model selection
Four locally runnable Granite models were compared on the same Cooling case and the same three-call sequence.
HISTORICAL SELECTION TEST
Granite model selection
Four locally runnable Granite models were compared on the same Cooling case and the same three-call sequence.
| Criterion | Weight | 3.3 2B | 3.3 8B | 4.1 3B | 4.1 8B |
|---|---|---|---|---|---|
| Plain language | 30% | 3 | 4 | 4 | 5 |
| Grounding | 25% | 4 | 4 | 4 | 5 |
| Valid JSON | 20% | 2 | 5 | 5 | 5 |
| Action quality | 15% | 3 | 3 | 4 | 5 |
| Uncertainty | 10% | 4 | 4 | 4 | 4 |
| Weighted total | 100% | 3.15 | 4.05 | 4.15 | 4.85 · selected |
R1Automotive 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
Automotive knowledge storage design
The storage method had to keep reviewed knowledge inspectable and select only the part required by the current report section.
| Option | Observed trade-off | Decision |
|---|---|---|
| Full knowledge in every prompt | Unrelated material and unnecessary prompt length | Not used |
| Semantic vector retrieval | Useful when the category is unknown; ranking varied across records | Compared |
| Metadata-filtered ChromaDB | Exact filtering from the anomaly key already supplied by the Model Layer | Selected |
Storage evidence
report_layer/rag/grounded_knowledge.yamlis the reviewed source.report_layer/rag/knowledge_indexer.pyrebuilds the collection and checks all five anomaly types.
R2How 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
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.
proxy_failuresR3Metadata 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
Metadata and semantic retrieval comparison
RAG is the overall method. This experiment compared two ways of choosing which reviewed record enters a prompt.
| Retrieval method | Correct record | Average time |
|---|---|---|
| Metadata + section | 7/7 | ≈0.34 ms |
| Semantic + section | 4/7 | ≈61.0 ms |
| Metadata + whole document | 7/7 | ≈0.31 ms |
| Semantic + whole document | 6/7 | ≈57.4 ms |
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}}
]})Embed a symptom description and rank records by similarity, even when their wording differs.
collection.query( query_texts=[symptom_description], n_results=1 )
R4Model 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
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.
cooling_degradation- Risk score: 0.5021 · Medium
- Prediction confidence: 0.842
- Coolant: 85°C · reference 90–95°C
- Rise rate: 5.0486°C/min · reference 0–2
- Both values remain Model Layer evidence.
estimated_cycles_to_failure = 5
Projected trips until the risk trend reaches the High threshold.
estimated_failure_probability = 0.7502
Chance of crossing High within the fixed next ten trips.
R5What the Model input contract checks
ModelLayerOutput(**summary) triggers these Pydantic rules before retrieval or any Granite call begins.
SHARED/INTERFACE_MODELS.PY
What the Model input contract checks
ModelLayerOutput(**summary) triggers these Pydantic rules before retrieval or any Granite call begins.
report_layer/pipeline/report_generator.py: validated = ModelLayerOutput(**summary_payload) → only the validated object is passed to build_context_with_rag(validated).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(...)
@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(...)@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(...)R6Prompt context construction
Technical fields are converted into a short evidence summary while their values and meanings remain unchanged.
PREPARED EVIDENCE
Prompt context construction
Technical fields are converted into a short evidence summary while their values and meanings remain unchanged.
coolant_temp → coolant temperature.# 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,
}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.pyprepares the context.
R7Knowledge 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
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.
- Coolant circulation
- Thermostat operation
- Cooling fan operation
- Coolant level or quality
- Radiator airflow or restriction
- 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.
R8Three production Calls
Each Call has a separate question, evidence allowance and output contract.
SEQUENTIAL GENERATION
Three production Calls
Each Call has a separate question, evidence allowance and output contract.
Prepared Model fields only.
Example“Coolant temperature is low while warming faster than expected.”
Accepted Call 1 + admitted cause knowledge.
Example“This pattern may relate to sensing or coolant circulation.”
Risk state + role-separated action knowledge.
Example“Arrange an inspection and report the two readings to the mechanic.”
| Call | Fixed boundary | Output |
|---|---|---|
| 1 | No diagnosis or retrieved cause | Observed pattern |
| 2 | Possible causes remain conditional | Explanation |
| 3 | Technical procedures assigned to mechanic | Now · timing · stop conditions · mechanic information |
R9Example-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
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.
Failure-threshold probability: 1.0
Estimated trips: 4
No value of 0.31% was supplied.
An unrelated accelerator-pedal example contained the illustrative value 0.31%.
R10Three Calls through sequential gates
Each accepted section becomes part of the next prompt. A failed section is corrected before generation continues.
GENERATE → VALIDATE → CONTINUE
Three Calls through sequential gates
Each accepted section becomes part of the next prompt. A failed section is corrected before generation continues.
Observation
Model evidence and prepared context.
Clean → validate → one correction if required
Possible causes
Model evidence + accepted Call 1 + cause knowledge.
Clean → validate → one correction if required
Recommended actions
Model evidence + accepted Calls 1–2 + action knowledge.
Clean → validate → one correction if required
Call 2 receives the accepted observation. Call 3 receives both accepted earlier sections. Each Call also keeps access to the relevant Model evidence.
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.
R11How 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
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.
Start at 1.00 · release at 0.80 or above
“coolant_temp is high and the cooling fan has failed.”
Warnings identify the raw field, unsupported direction and confirmed-fault wording.
The correction prompt receives the original task, rejected JSON and exact warnings. Only this section is regenerated and checked again.
R12Two failure paths
The pipeline treats a failed model request differently from a readable JSON response that breaks a report rule.
RETRY · CORRECT · FALLBACK
Two failure paths
The pipeline treats a failed model request differently from a readable JSON response that breaks a report rule.
- Request error
- Timeout
- Malformed JSON
- Response: retry the call, up to three attempts.
- JSON parses successfully
- Section scores below 0.80
- Response: one targeted correction, then check again.
R13Four 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
Four knowledge conditions
The final evaluation changed only the knowledge supplied to Granite while keeping the model, prompts, temperature, inputs and Validator fixed.
| Condition | Knowledge supplied | Purpose |
|---|---|---|
| No retrieved knowledge | Model context only | Generation baseline |
| Cause knowledge | Reviewed descriptions and possible causes | Test explanation grounding |
| Cause + raw workshop actions | Cause records and unseparated procedures | Expose audience/action-role problems |
| Cause + role-separated actions | Owner observations, timing and stop conditions separated from mechanic procedures | Final design |
What to observe now, when to arrange service, and which visible warning signs require urgent help.
Scan-tool checks, wiring inspection, calibration and replacement decisions are presented as requests for a qualified mechanic.
R14Automated checks and manual review
The same 20 reports produced machine-checkable results and separate human judgements about meaning and audience.
FINAL V5 EVIDENCE
Automated checks and manual review
The same 20 reports produced machine-checkable results and separate human judgements about meaning and audience.
| Condition | Released | Valid structure | No unsupported numbers | No unsafe owner procedure detected | Fallback |
|---|---|---|---|---|---|
| Controlled baseline | 5/5 | 5/5 | 5/5 | 5/5 | 0/5 |
| Cause RAG | 5/5 | 5/5 | 5/5 | 5/5 | 0/5 |
| Cause + raw actions | 5/5 | 5/5 | 5/5 | 5/5 | 0/5 |
| Cause + role-separated actions | 5/5 | 5/5 | 5/5 | 5/5 | 0/5 |
| Condition | Input faithfulness | Retrieval relevance | Knowledge use | Audience | Action safety | Uncertainty |
|---|---|---|---|---|---|---|
| Controlled baseline | 5 pass | N/A | N/A | 5 pass | 5 pass | 5 pass |
| Cause RAG | 5 pass | 4 pass · 1 partial | 1 pass · 4 partial | 3 pass · 2 partial | 5 pass | 5 pass |
| Cause + raw actions | 5 pass | 4 pass · 1 partial | 1 pass · 4 partial | 3 pass · 2 partial | 5 pass | 5 pass |
| Cause + role-separated actions | 5 pass | 4 pass · 1 partial | 1 pass · 4 partial | 3 pass · 2 partial | 5 pass | 5 pass |
| Where did manual review record partial? | Reason |
|---|---|
| Retrieval relevance · IAT | The 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 MAP | Retrieved 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 IAT | The reports respected the evidence and action boundaries, but their cause explanations remained longer and more technical than ideal for a non-specialist reader. |
- 20/20 reports generated and released
- Input meaning and uncertainty preserved
- Owner–mechanic action boundary maintained
- RAG usually retrieved the correct category
- 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
- 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
Evidence behind the owner-facing Dashboard
D1User journey and data sources
Start with an explicit choice: analyse uploaded OBD-II data or explore a labelled demonstration.
UPLOAD · DEMO · SOURCE
User journey and data sources
Start with an explicit choice: analyse uploaded OBD-II data or explore a labelled demonstration.
| Source | Meaning |
|---|---|
| uploaded / uploaded_history | Results from a live single-upload or joint-history request. |
| real | Real-source output identified by the loader. |
| mock | Demonstration values; not evidence of a live run. |
| missing | No available component data. |
_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 throughDASHBOARD_TEST_DATA.dashboard/data_loader.py: source map and real/mock loading. GL-85 → GL-129/131 → GL-132/133 established source-aware integration.
D2Local deployment and usability
Live analysis relies on local Model dependencies and Ollama; installation remains a barrier for non-technical owners.
LOCAL LIVE ANALYSIS
Local deployment and usability
Live analysis relies on local Model dependencies and Ollama; installation remains a barrier for non-technical owners.
Python, the Model environment, Ollama and Granite must be available. The hosted demonstration uses static results.
Package the application or provide a hosted inference service so owners can upload data without terminal setup.
| Decision | Reason / trade-off |
|---|---|
| Model batch subprocess | Uses the published CLI and isolates pinned Torch/Transformers dependencies; adds process startup and serialization overhead. |
| Interpreter selection | MODEL_LAYER_PYTHON → dedicated Model virtual environment → current Python. |
| Local artefacts | Temporary raw/Model files are cleaned up; Data artefacts persist by run. Retention and privacy handling require further work. |
Implementation and evidence
README.mdanddashboard/README.md: live setup and hosted-demo scope.dashboard/csv_pipeline.py::_run_model_layer(): fixed Model working directory and 120-second subprocess timeout.
D3Multiple files as driving history
Accepted trips are ordered chronologically and processed together while retaining individual trip boundaries.
AT LEAST 5 TRIPS
Multiple files as driving history
Accepted trips are ordered chronologically and processed together while retaining individual trip boundaries.
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.
D4Upload validation
Reject unusable files before expensive processing and show the owner a readable reason.
SCHEMA · TIME · LENGTH
Upload validation
Reject unusable files before expensive processing and show the owner a readable reason.
| Check | Behaviour |
|---|---|
| Readable, non-empty CSV | Reject absent bytes, empty data and parsing failures. |
| 11 required raw columns | Validate time, temperature, pressure, RPM, speed, airflow, throttle and pedal fields; accept the known °C/°C header variant. |
| At least 700 rows per admission check | Protect the temporal input requirement; 700 rows is about 11.7 minutes at 1 Hz. UI guidance asks for roughly 15 minutes. |
| Original KIT filename | Preserve the recording-date identity instead of renaming to a generic temporary file. |
| Continuity and usable segments | The Data Layer can still reject a file that passes the initial column/row checks. |
Implementation and evidence
dashboard/csv_validator.py,dashboard/pages/overview.pyanddashboard/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.
D5Dashboard responsibilities
Dashboard coordinates the layers and presents their evidence; it does not make a new vehicle prediction.
CONTRACT → DISPLAY
Dashboard responsibilities
Dashboard coordinates the layers and presents their evidence; it does not make a new vehicle prediction.
| Owner | Responsibility |
|---|---|
| Model | Risk score, supplied risk level, attribution confidence, key signals, window history and optional projections. |
| Report | Preserve structured evidence and add anomaly description, possible cause and recommended action. |
| Dashboard | Validate/adapt payloads, select and sort components, draw rings/charts, compare signal ranges and format exports. |
Accept single outputs or batch envelopes; normalise legacy coolant_slope to ect_rate_180s, remove retired coolant_stability and canonicalise component aliases.
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.
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.
D6Risk metrics and interpretation
Different indicators answer different questions; none is a validated probability of mechanical breakdown.
RISK ≠ CONFIRMED FAILURE
Risk metrics and interpretation
Different indicators answer different questions; none is a validated probability of mechanical breakdown.
| Indicator | Meaning |
|---|---|
| Risk score | Model internal anomaly indicator on a 0–1 scale. |
| Risk level | Model-supplied Low, Medium or High category. |
| Prediction confidence | Support for the component attribution; not breakdown probability. |
| Risk Trend | Internal risk scores across recorded analysis windows in the request. |
| Estimated cycles to failure | Projection of trips until the score crosses the High-risk threshold; not physical remaining useful life. |
| Estimated failure probability | Projected chance of crossing that threshold within the prediction horizon; not calibrated mechanical-failure probability. |
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.
D7Component selection and no-risk state
The Overview prioritises supported components that may need attention.
MEDIUM / HIGH
Component selection and no-risk state
The Overview prioritises supported components that may need attention.
Implementation and evidence
dashboard/data_store.py::get_overview_components(),dashboard/pages/overview.pyanddashboard/anomaly_display.py.- Aliases are canonicalised and duplicate components suppressed.
D8Normal and abnormal signals
Compare each available reading with its supplied reference range and preserve the evidence next to the status.
VALUE VS RANGE
Normal and abnormal signals
Compare each available reading with its supplied reference range and preserve the evidence next to the status.
| Input / result | Interpretation |
|---|---|
| Reading + reference range | Values outside the range are Abnormal; values within it are Normal. |
| Coolant example | 84.0°C and rise rate 5.5069°C/min are shown as abnormal in the viva payload. |
| Supporting normal readings | Vehicle speed 70.0 km/h and manifold pressure 132.0 kPa remain normal. |
| Owner-readable labels | Show signal name, value, unit and range; glossary tooltips explain technical terms. |
Implementation and evidence
dashboard/pages/detail.py::_render_signals(),dashboard/glossary.pyanddashboard/export_helper.py.
D9Missing data display
Unavailable evidence remains unavailable instead of becoming an invented measurement or estimate.
MISSING ≠ ZERO
Missing data display
Unavailable evidence remains unavailable instead of becoming an invented measurement or estimate.
| Missing item | Display behaviour |
|---|---|
| Trend | Show an empty/unavailable state when usable history is insufficient. |
| Key signals | Show no available signals rather than fabricated readings. |
| Projection fields | Retain null/unavailable values; do not invent percentages or cycles. |
| Component source | Keep the missing/demo provenance visible. |
| Export values | Normalise missing values explicitly in the portable report. |
Implementation and evidence
dashboard/pages/detail.py,dashboard/data_store.pyanddashboard/export_helper.py.- Historical edge fixtures cover empty, missing, single-component, no-signal and no-trend states.
D10Processing failures and empty reports
A failed generation must not be presented as evidence that the vehicle is healthy.
FAILURE → RECOVERY
Processing failures and empty reports
A failed generation must not be presented as evidence that the vehicle is healthy.
| Failure | Response |
|---|---|
| Model unavailable / non-zero exit | Show a readable Model error; extract the documented ERROR message when available. |
| Model timeout / invalid output | Handle the 120-second timeout, missing JSON or invalid JSON as pipeline failure. |
| Returned components all have empty descriptions | Treat the returned components as generation failure even when their shape is valid. |
| Recovery | Clear loading state in finally; only accepted results update the Dashboard session. |
Implementation and evidence
dashboard/pages/overview.py::_handle_uploaded_csv_submit(); GL-261 and commitbc50667document 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.
D11Explanation grounding and errors
Granite translates structured evidence and reviewed knowledge into owner-facing language, but grounding does not guarantee correctness.
EVIDENCE + REVIEWED KNOWLEDGE
Explanation grounding and errors
Granite translates structured evidence and reviewed knowledge into owner-facing language, but grounding does not guarantee correctness.
Relevant knowledge and structured facts constrain the explanation. Owner observations are separated from mechanic procedures.
A plausible cause is still a hypothesis. Report checks address specified errors, but cannot prove every explanation matches a real mechanical condition.
Implementation and evidence
report_layer/report_generator.pyand the reviewed RAG knowledge; Report backup cards R3–R11.
D12Testing evidence and limitations
Contract tests, controlled substitutes and saved live runs answer different validation questions.
WORKFLOW ≠ ACCURACY
Testing evidence and limitations
Contract tests, controlled substitutes and saved live runs answer different validation questions.
| Evidence | What it establishes |
|---|---|
| Validation and UI tests | Upload gates, source labels, sorting, missing states, loading recovery, risk display, glossary and report rendering. |
| Controlled pipeline tests | Data/Model handoff, proxy forwarding, timeouts and output contracts without expensive live calls. |
| Export tests | Deterministic PDF/CSV transformation, packaging and formatting. |
| Saved real-case run | The available vehicle data can exercise the integration route; it does not establish accuracy on confirmed breakdowns. |
| Historical Dashboard run | 171 passed, 1 skipped on 2 Aug 2026; a historical snapshot, not a fresh test result. |
Implementation and evidence
dashboard/tests/andreport_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.
D13User feedback and interface changes
Make risk easier to interpret without suggesting that a clearer interface makes the prediction more accurate.
RISK TREND · OWNER LANGUAGE
User feedback and interface changes
Make risk easier to interpret without suggesting that a clearer interface makes the prediction more accurate.
| Interpretation problem | Interface response |
|---|---|
| Percentage-led visuals can look like breakdown probability | Emphasise Low, Medium and High risk levels while retaining the internal score for visual scale. |
| Risk Score Trend sounds like a probability forecast | Use Risk Trend and explain that the chart represents an internal risk pattern. |
| Technical evidence is difficult to read | Use owner-facing component names, glossary help and what / why / action sections. |
Implementation and evidence
report_layer/evaluation/user_testing_ab/,dashboard/pages/detail.pyanddashboard/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.
