Skip to content

Add findings cross-validation pipeline step (LLM findings vs. ML signals) #52

Description

@Liohtml

Part of #51.

Summary

Add an optional, additive pipeline step that cross-checks the Vision-LLM's
structured findings against the independent signals MedCheck already computes
(ML anomaly scores, selected/“top” slices, detected anatomy), and annotates the
report with a Validation section: agreement, contradictions, and confidence
flags. This catches the classic failure mode where a Vision-LLM confidently
reports a finding that the quantitative analysis does not support (or misses one
the anomaly detector flagged).

This is the lowest-risk child of #51: no new heavy dependencies, no model
downloads, no network calls
— it only reasons over data already present in the
PipelineContext.

Why (vision)

For a research tool whose outputs "must be reviewed by a qualified radiologist,"
the most valuable thing we can add is not another model — it's making
disagreement visible
. A finding that all signals agree on is more trustworthy;
a finding the LLM asserts with high confidence but no anomaly-score support
deserves a flag. This is defense-in-depth, not a new diagnosis.

Where

  • New file: src/medcheck/pipeline/validate.pyValidationStep(PipelineStep)
  • Register in src/medcheck/main.py::_build_registry() as "validate"
  • Runs after vision_analysis, before report
  • Extend PipelineContext (src/medcheck/core/context.py) with a
    validation: list[ValidationFinding] (or dict) field, default empty
  • Surface results in src/medcheck/pipeline/report.py (JSON + PDF + HTML) under a
    new "Validation / Quality checks" section
  • Optionally expose a validate entry in the default workflows/*.yml

Proposed checks (all heuristic, no ML)

  1. Anomaly corroboration — for each StructureFinding with a non-"normal"
    status, check whether the series it maps to has an elevated anomaly_scores
    entry / appears in top_slices. Flag "asserted abnormality with no
    quantitative support" and vice-versa ("high anomaly score in a region the LLM
    called normal").
  2. Confidence floor — flag findings with confidence below a configurable
    threshold (default 0.5) as "low confidence — verify".
  3. Anatomy consistency — flag structures that don't belong to the
    detected_anatomy region (e.g. an "ACL" finding on a shoulder study) as a
    possible mislabel/hallucination.
  4. Schema/sanity — confidence outside [0,1], empty findings text, duplicate
    structures.
  5. Overall quality score — a simple aggregate (e.g. fraction of findings that
    are corroborated) shown in the report header.

Each produces a ValidationFinding(level: info|warn|flag, message, related_structure).

Configuration

  • Step config (via workflow YAML / step_config): confidence_threshold,
    anomaly_percentile, toggles per check.
  • Must be a no-op if the relevant inputs are absent (e.g. no anomaly scores,
    no findings) — fail-soft, never raises.

Acceptance criteria

  • ValidationStep registered as "validate"; not in the default step list
    unless explicitly added (additive).
  • Populates context.validation; never raises on missing/partial inputs.
  • Report renderers (JSON/PDF/HTML) include a Validation section when present.
  • Unit tests: corroborated finding (no flag), contradicted finding (flag),
    low-confidence flag, anatomy-mismatch flag, empty-context no-op.
  • Disclaimer wording reinforced: validation assists, does not certify.

Effort

~3–4 h. No new dependencies.

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions