Sangeetha-Grantha

Metadata Value
Status Completed
Version 1.1.0
Last Updated 2026-07-19
Author Sangeetha Grantha Team

TRACK-126: Python Worker Quality Gates — Zero ruff/mypy Errors + CI Enforcement

Goal: Make the quality gates declared in tools/krithi-extract-enrich-worker/pyproject.toml real. The tool declares mypy strict = true and a ruff rule set, but currently carries ~226 mypy errors and ~125 ruff errors. Drive both to zero and gate them in CI so they stay there.

Origin: Python best-practices evaluation, 2026-07-19 (item 1 — highest priority). Most of the debt clears mechanically; the two big error classes are Pydantic-alias false positives and untyped psycopg generics.

Definition of Done

Inputs

Out of Scope

Steps

  1. Enable pydantic.mypy plugin; re-baseline mypy.
  2. Parameterize psycopg types in src/db.py; type to_json_dict() -> dict[str, Any] in schema.py.
  3. uv run ruff check --fix . (safe fixes), then hand-fix the remainder (B007 unused loop vars, E501, E741, B905 zip(strict=)).
  4. uv run ruff format . on the tree.
  5. Add mypy per-module overrides for tests.*/scripts.*; replace raw method assignment in tests with monkeypatch.setattr where that is the cheaper fix for method-assign.
  6. Add the three checks to CI for this tool’s path.
  7. Consider enabling ruff groups SIM, C4, PTH, RUF — adopt if the residual fix cost is small, otherwise record as follow-up.

Deliverables

Verification

Run inside tools/krithi-extract-enrich-worker/:

Outcome (2026-07-19)

Verified locally, all four gates from a clean .mypy_cache:

Gate Before After
ruff check . 125 errors 0
ruff format --check . n/a 53 files clean
mypy . 226 errors 0
pytest (unit) 210 passed 210 passed
pytest tests/integration 18 passed 18 passed

src/ is clean under full strict mypy with no relaxations.

Correction (2026-07-19, during TRACK-129): this section originally claimed there was no type: ignore anywhere in the tree. That was wrong. src/page_segmenter.py:138 carries a pre-existing # type: ignore[arg-type] that predates this track and survived it. The accurate claim is that TRACK-126 added none. (A second one, introduced by TRACK-128 in gemini_enricher.py, was removed once spotted.)

Resolved (2026-07-19, post-track): page_segmenter.py:138 no longer carries it. max(size_counts, key=size_counts.get) failed arg-type because dict.get is (float) -> int | None, which is not a valid sort key; indexing instead (key=lambda size: size_counts[size]) types cleanly. Verified value-identical, including tie-breaking, over 200,000 generated dicts.

One type: ignore remains in src/config.py’s @computed_field # type: ignore[prop-decorator]. That one is unavoidable: mypy reports “Decorators on top of @property are not supported” without it, confirmed by removing it and re-running.

How the two big error classes cleared:

Notes and deviations:

Follow-up (not adopted here): ruff groups SIM/C4/PTH/RUF report 128 additional findings. 24 are RUF002/RUF003 ambiguous-unicode false positives (the corpus is Indic text), and the SIM rewrites are behaviour-adjacent, so adopting them conflicts with this track’s zero-behaviour-change guarantee. 23 RUF100 unused-noqa removals are the cheap subset if a later track wants them. Coverage thresholds remain unmeasured.