fix(kb): coerce sidecar date strings to datetime.date for asyncpg

Sidecar JSON supplies published_at / effective_at as 'YYYY-MM-DD' strings.
asyncpg rejects strings for DATE columns even with '::date' casts in the
SQL, so ingest now parses them via datetime.date.fromisoformat before the
INSERT. Non-string inputs (date, datetime, None, empty) are handled too.

Before: ingest crashed on any sidecar with a date, leaving the source in
failed/ while sidecarless siblings succeeded.

Refs Task Master #2

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-21 16:16:13 +00:00
parent 3d5bc38401
commit 68c9506f9c
+20 -2
View File
@@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import datetime as _dt
import hashlib import hashlib
import io import io
import logging import logging
@@ -20,6 +21,22 @@ class IngestError(RuntimeError):
pass pass
def _coerce_date(value) -> _dt.date | None:
"""Accept date objects, 'YYYY-MM-DD' strings, or None; return date|None."""
if value is None or value == "":
return None
if isinstance(value, _dt.date) and not isinstance(value, _dt.datetime):
return value
if isinstance(value, _dt.datetime):
return value.date()
if isinstance(value, str):
try:
return _dt.date.fromisoformat(value[:10])
except ValueError:
return None
return None
def _parse_pdf(data: bytes) -> str: def _parse_pdf(data: bytes) -> str:
parts: list[str] = [] parts: list[str] = []
with fitz.open(stream=data, filetype="pdf") as doc: with fitz.open(stream=data, filetype="pdf") as doc:
@@ -125,10 +142,11 @@ async def ingest_source(
INSERT INTO kb_source INSERT INTO kb_source
(kind, title, identifier, published_at, effective_at, (kind, title, identifier, published_at, effective_at,
source_url, original_path, checksum) source_url, original_path, checksum)
VALUES ($1,$2,$3,$4::date,$5::date,$6,$7,$8) VALUES ($1,$2,$3,$4,$5,$6,$7,$8)
RETURNING id RETURNING id
""", """,
kind, title, identifier, published_at, effective_at, kind, title, identifier,
_coerce_date(published_at), _coerce_date(effective_at),
source_url, original_path, checksum, source_url, original_path, checksum,
) )
source_id = row["id"] source_id = row["id"]