---
phase: 11-manual-ruff-cli-mypy
plan: 02
type: execute
wave: 1
depends_on: []
files_modified:
  - src/corpus_analyzer/core/database.py
autonomous: true
requirements:
  - MYPY-01

must_haves:
  truths:
    - "`uv run mypy src/ --no-error-summary 2>&1 | grep 'core/database.py'` returns empty (zero errors)"
    - "All sqlite-utils Table call sites use `cast(Table, self.db[...])` pattern"
    - "Bare `dict` and `list` annotations are replaced with parameterised equivalents"
    - "The `float()` None arg-type errors are fixed with explicit local variable guards"
    - "All 281 tests still pass"
  artifacts:
    - path: "src/corpus_analyzer/core/database.py"
      provides: "Mypy-clean database layer (zero errors)"
  key_links:
    - from: "src/corpus_analyzer/core/database.py"
      to: "sqlite_utils.db.Table"
      via: "cast(Table, self.db[...]) at every call site"
      pattern: "cast\\(Table, self\\.db\\["
---

<objective>
Fix all 17 mypy errors in `core/database.py`. Errors fall into three categories: (1) union-attr from `self.db["table"]` returning `Table | View`; (2) bare generic annotations (`dict`, `list`); (3) `float()` called with a value that could be `None`.

Purpose: MYPY-01 — database.py is the most error-dense file (17/45 total mypy errors). Fixing it in isolation makes the remaining mypy work more tractable.
Output: A mypy-clean database.py with zero type errors and no behaviour changes.
</objective>

<execution_context>
@/home/ollie/.claude/get-shit-done/workflows/execute-plan.md
@/home/ollie/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/phases/11-manual-ruff-cli-mypy/11-RESEARCH.md
</context>

<tasks>

<task type="auto">
  <name>Task 1: Add cast(Table, ...) at all sqlite-utils call sites in database.py</name>
  <files>src/corpus_analyzer/core/database.py</files>
  <action>
Read the full file first. The errors are on lines 94, 96, 97, 100, 101, 175, 176, 229, 233, 243, 254 (union-attr and no-any-return).

**Step 1:** Add imports at the top of `database.py` (after existing imports):
```python
from typing import cast
from sqlite_utils.db import Table
```
Verify the import works: `python -c "from sqlite_utils.db import Table; print(Table)"` — must not raise.

**Step 2:** Wrap every `self.db["tablename"]` call that calls a Table-only method (`.insert`, `.update`, `.delete_where`) with `cast(Table, ...)`:

Lines to wrap (exact patterns — read the file to find the actual context):
- `self.db["documents"].delete_where(...)` → `cast(Table, self.db["documents"]).delete_where(...)`
- `self.db["documents"].update(...)` → `cast(Table, self.db["documents"]).update(...)`
- `self.db["documents"].insert(...)` → `cast(Table, self.db["documents"]).insert(...)`
- `self.db["chunks"].delete_where(...)` → `cast(Table, self.db["chunks"]).delete_where(...)`
- `self.db["chunks"].insert(...)` → `cast(Table, self.db["chunks"]).insert(...)`

**Also fix no-any-return:** Lines 97 and 101 and 176 return the result of `.insert(...)` which is typed `Any`. If these methods return `int` (row id), wrap with `int(cast(Table, ...).insert(...))` or cast the return. Check the declared return type of the enclosing function; if it returns `int`, the cast is needed. If the return type is `None`, just drop the return value.

Run `uv run mypy src/ --no-error-summary 2>&1 | grep "core/database.py"` after edits to confirm union-attr errors are gone.
  </action>
  <verify>
    <automated>uv run mypy src/ --no-error-summary 2>&1 | grep "core/database.py" | grep -E "union-attr|no-any-return" | wc -l | grep -q "^0$" && echo "union-attr+no-any-return: 0"</automated>
  </verify>
  <done>All union-attr and no-any-return errors in database.py are gone; cast(Table, ...) is present at every sqlite-utils Table call site.</done>
</task>

<task type="auto">
  <name>Task 2: Fix bare generic annotations and float(None) arg-type errors in database.py</name>
  <files>src/corpus_analyzer/core/database.py</files>
  <action>
Fix the remaining 6 mypy errors in database.py (type-arg and arg-type categories).

**Bare generics (type-arg errors):**

Line 140 — `_doc_to_dict` return type:
```python
# Before:
def _doc_to_dict(self, doc: Document) -> dict:
# After:
def _doc_to_dict(self, doc: Document) -> dict[str, Any]:
```

Line 185 — `params` local variable:
```python
# Before:
params: list = []
# After:
params: list[str | int] = []
```
(Or `list[Any]` if the values are heterogeneous — read the surrounding code to determine which values go in.)

Line 271 — same `params` pattern, same fix.

Line 302 — `_row_to_document` parameter:
```python
# Before:
def _row_to_document(self, row: dict) -> Document:
# After:
def _row_to_document(self, row: dict[str, Any]) -> Document:
```

Ensure `Any` is imported from `typing` (add to the import line already added in Task 1 if needed).

**float(None) arg-type errors (lines 322 and 328):**

The pattern `float(row.get("x") if row.get("x") is not None else 0.0)` still causes an arg-type error because mypy sees `Any | float | None` from `.get()`. Use a local variable guard:
```python
# Before:
category_confidence=float(
    row.get("category_confidence")
    if row.get("category_confidence") is not None
    else 0.0
),

# After:
_cc = row.get("category_confidence")
category_confidence=float(_cc) if _cc is not None else 0.0,
```
Apply the same pattern to the second occurrence (line 328, likely `quality_score`).

After all edits run the full mypy gate and then pytest:
  </action>
  <verify>
    <automated>uv run mypy src/ --no-error-summary 2>&1 | grep "core/database.py" | wc -l | grep -q "^0$" && echo "database.py: 0 errors" && uv run pytest -x -q 2>&1 | tail -3</automated>
  </verify>
  <done>`uv run mypy src/ --no-error-summary 2>&1 | grep "core/database.py"` returns empty; all 281 tests pass.</done>
</task>

</tasks>

<verification>
After both tasks:
1. `uv run mypy src/ --no-error-summary 2>&1 | grep "core/database.py"` — empty output
2. `python -c "from sqlite_utils.db import Table; print(Table)"` — resolves without error
3. `uv run pytest -x -q` — all 281 tests green
</verification>

<success_criteria>
- Zero mypy errors in core/database.py
- `cast(Table, ...)` present at all Table call sites
- All 281 tests pass
</success_criteria>

<output>
After completion, create `.planning/phases/11-manual-ruff-cli-mypy/11-02-SUMMARY.md`
</output>
