Faizan Mohiuddin 339cfc74fc Merged in feature/one-to-one-confidence-scoring (pull request #1002)
Feature/one to one confidence scoring

* T1 plumbing: capture per-field confidence + retrieved-chunk metadata for 1:1 HSC fields

Prep work for the 1:1 confidence-scoring stage. No scoring logic yet — this
just collects the inputs the next ticket (rule-based scorer) will consume.

- ONE_TO_ONE_SINGLE_FIELD_TEMPLATE: ask the LLM for confidence (0.0-1.0),
  verdict (correct/uncertain/not_found), and supporting_snippet alongside
  the field value. Existing field parser passes the extra keys through
  unchanged.
- prompt_hsc_single_field: now returns a 4-tuple (name, value, field,
  metadata) where metadata holds the confidence/verdict/snippet plus a
  lightweight summary of which chunks the LLM saw (count + ids).
  _extract_hsc_metadata is defensive: clamps out-of-range confidences,
  defaults a missing/garbage verdict, caps the snippet at 500 chars,
  returns _empty_hsc_metadata() on every bail-out path.
- run_hybrid_smart_chunked_fields: optional return_metadata flag. Default
  return shape unchanged (dict of values) for backwards compatibility;
  wh…
* DAIP2-2692: field-type-aware rule scorer for 1:1 confidence

Replaces the T1 placeholder _CONF (just the LLM-stated confidence) with
a rule-based score per field. No new LLM calls -- pure local computation.

New module: src/qc_qa/confidence/
  - field_types.py  : FieldType enum + per-field mapping + weight presets
                      (date / money / tin / npi / code / name / boolean /
                      free_text). New 1:1 fields default to FREE_TEXT.
  - signals.py      : exact / token_overlap / fuzzy / number_match / regex /
                      grounding / na_check. Each signal returns float in
                      [0,1] or None when not applicable.
  - scorer.py       : score_field(field_name, value, metadata, contract_text,
                      field_prompt) -> float. Runs the signals that have
                      non-zero weight for the field's type, renormalizes
                      over the signals that actually fired, blends lightly
                      with the LLM-stated confidence (15%…
* DAIP2-2694: integrate calibration table into 1:1 confidence scoring

Adds a per-field-type calibration step that maps raw confidence scores
to empirically-calibrated values via a linearly-interpolated curve.
Identity by default, so the stage is a no-op until the curve is
backfilled from QC data; once backfilled, a "0.X confidence" output
reflects roughly X% empirical accuracy.

New files:
  - src/qc_qa/confidence/calibration.py
      * loads + caches the curve from JSON
      * linear interpolation between anchor points
      * defensive: bad inputs / missing file / malformed table all fall
        back to identity (no scoring regression on failure)
  - src/qc_qa/confidence/calibration_table.json
      * identity baseline shipped as the starting point
      * one curve per FieldType plus a 'default' fallback
      * documents the regeneration entry point for the QC-data backfill
.gitignore:
  * carve-out !src/qc_qa/confidence/*.json so the table actually ships

Scorer wiring:
  * apply calibration AFTER the r…
* DAIP2-2695: field-level confidence distribution stats + flagged-row report

At end of every run, emit two CSVs under tracking/:
  - <BATCH_ID>-CONFIDENCE-SUMMARY.csv : per-field distribution stats
      columns: field, n, mean, p10, p50, p90, n_below_threshold,
               threshold, pct_below_threshold
      sorted by pct_below_threshold descending so the worst fields are
      at the top of the file.
  - <BATCH_ID>-CONFIDENCE-FLAGGED.csv : the reviewer hit list
      columns: FILE_NAME, field, confidence, value, threshold
      sorted by confidence ascending.

Both are derived from the per-row <FIELD>_CONF columns the rule scorer
populates. Empty inputs (no _CONF columns yet, or no rows) skip the
write rather than emitting empty files.

Threshold:
  * config.CONFIDENCE_THRESHOLD (CLI arg confidence_threshold=, default 0.6)

New module:
  - src/qc_qa/confidence/summary.py
      compute_summary(final_df, threshold) -> per-field stats DataFrame
      compute_flagged(final_df, threshold) -> below-threshold r…
* Skip _CONF columns in standard_postprocess value-shape normalizers

The per-column loop in standard_postprocess substring-matches column
names ("_IND" in col, "TIN" in col, "_DT" in col, etc.) to decide which
shape normalizer to apply. After DAIP2-2692 introduced <FIELD>_CONF
confidence-score columns, those columns started colliding with the
match conditions:

  - AUTO_RENEWAL_IND_CONF matched "_IND in col" -> normalize_indicator_field
    coerced the 1.0 float to the string "N", destroying the score.
  - *_DT_CONF / *_TIN_CONF etc. were also at risk via the same pattern.

Fix: short-circuit the loop with `if col.endswith("_CONF"): continue`
so confidence floats are never passed through the value-shape
normalizers, regardless of what string happens to appear in the
underlying field name.

Verified on the 1:1 smoke test (conf-1to1-smoke2): AUTO_RENEWAL_IND_CONF
now lands as 1.0 in RESULTS-FULL.csv and CONFIDENCE-SUMMARY.csv shows
all 10 HSC fields with proper float distributions.

* Merge remote-tracking branch 'origin/dev' into feature/one-to-one-confidence-scoring

* Black formatting: split generator in reorder_columns _CONF carve-out

Black wanted the multi-line generator expression formatted with one
clause per line. Behaviour unchanged.

* Fix na_check so amendment docs differentiate from base agreements

Three small bugs were stacking up to make CONTRACT_AMENDMENT_NUM flag every
file that returned a missing value, regardless of whether the document
actually was an amendment.

1) is_na() did not treat pandas/float NaN as N/A. When the LLM returned
   no value, the cell came through as a float NaN, str()'d to "nan", and
   the N/A token set ({"", "n/a", "na", "none", "null", "not found"})
   did not match. na_check therefore never fired and the other signals
   ran on the literal string "nan", producing a flat low score driven
   only by the LLM's self-reported confidence. Now also catches float
   NaN via value != value.

2) signal_na_check used three coarse buckets (0.30/0.50/0.85) and a
   per-keyword saturation of 2 mentions. Both produced the same bucket
   for docs that mention a concept 2x in boilerplate and docs whose
   entire topic is that concept. Switched to a continuous score with
   a saturation floor of ~30 mentions, so 8 mentions…
* Merged dev into feature/one-to-one-confidence-scoring


Approved-by: Katon Minhas
2026-05-12 21:53:05 +00:00

Field Extraction Pipeline

Contract field extraction using LLMs.

Setup

Install uv (if not already installed)

# Ubuntu/WSL (recommended)
curl -LsSf https://astral.sh/uv/install.sh | sh
source ~/.bashrc  # or restart terminal

# macOS
curl -LsSf https://astral.sh/uv/install.sh | sh

# Windows (PowerShell - native, not WSL)
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Verify installation: uv --version

Install dependencies

uv sync

Running the Code

Use uv run with Python's module flag from the project root:

# Run the default SaaS pipeline
uv run python -m src.pipelines.saas.main

# Run with unified runner (supports client selection)
uv run python -m src.pipelines.runner --client saas --input-dir /path/to/input

# Run with specific client
uv run python -m src.pipelines.runner --client clover --input-dir /path/to/input

Note: Always use uv run to ensure correct virtual environment. Always use -m flag for proper imports.

Development

Before pushing, always run these checks to avoid breaking the CI pipeline:

# Format code
uv run black src/

# Type checking
uv run mypy src/

# Run tests
uv run pytest

All checks must pass before merging to main.

Project Structure

├── src/
│   ├── pipelines/              # Pipeline implementations
│   │   ├── runner.py           # Unified CLI entry point with client routing
│   │   ├── saas/               # Default SaaS pipeline
│   │   │   └── main.py         # Main entry point for SaaS
│   │   ├── shared/             # Shared pipeline components
│   │   │   ├── preprocessing/  # Document preprocessing
│   │   │   ├── extraction/     # Field extraction logic
│   │   │   └── postprocessing/ # Result postprocessing
│   │   └── clients/            # Client-specific overrides
│   │       └── clover/         # Clover client customizations
│   ├── core/                   # Core utilities (registry, fieldset)
│   ├── constants/              # Constants, mappings, and field definitions
│   │   ├── mappings/           # Crosswalk JSON files
│   │   └── lists/              # Lookup lists
│   ├── prompts/                # LLM prompt templates
│   ├── utils/                  # Shared utilities (IO, string, logging, etc.)
│   ├── codes/                  # Medical code extraction utilities
│   ├── crosswalk/              # Crosswalk mapping logic
│   ├── embeddings/             # Pre-computed embeddings for code matching
│   ├── qc_qa/                  # QC/QA validation pipeline
│   ├── parent_child/           # Parent-child relationship mapping
│   ├── document_classification/ # Document type classification (DTC)
│   └── tests/                  # Unit tests
├── documentation/              # Project documentation
├── outputs/                    # Pipeline output files (gitignored)
├── logs/                       # Log files (gitignored)
└── pyproject.toml              # Project dependencies (uv/pip)

Branching

Naming Conventions

Type Pattern Use Case Example
Feature feature/<ticket>-<description> New functionality feature/PROJ-123-add-export-csv
Bugfix bugfix/<ticket>-<description> Bug fixes bugfix/PROJ-456-fix-null-handling
Hotfix hotfix/<ticket>-<description> Urgent production fixes hotfix/PROJ-789-critical-parse-error
Test test/<description> Testing/experimentation test/lesser-table-caching-refactor
Release release/<version> Release preparation release/v1.2.0

Branch Guidelines

  • Use lowercase with hyphens (kebab-case) for descriptions
  • Include ticket number when applicable (e.g., JIRA, GitHub issue)
  • Keep branch names concise but descriptive
  • Delete branches after merging

Workflow

  1. Create branch from main
  2. Make changes and commit with clear messages
  3. Run checks before pushing: uv run black src/ && uv run mypy src/ && uv run pytest
  4. Create PR to main
  5. Ensure all CI checks pass
  6. Get code review approval
  7. Squash and merge
S
Description
AARETE DoczyAI pipelines mirror.
Readme 642 MiB
Languages
Python 80.5%
Jupyter Notebook 13%
HCL 3.1%
HTML 1.6%
PLpgSQL 1.5%
Other 0.2%