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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions