Integration prompt — ahu-doc-classifier
Paste this into a Claude Code session running inside the
ahu-doc-classifierrepo. No placeholder needed; all new behavior is env-gated.
We are introducing a central GPU/LLM manager (ahu-gpu-manager) and an audit system (ahu-ai-observatory). This classifier runs its model in-process (LayoutLMv3 on local CUDA), so it cannot sit behind the gateway proxy — instead it becomes an instrumented GPU tenant: it reports what it does and that it is alive. Hard constraint: all changes must be dormant — with no new env vars set, behavior must be identical to today. This service is in production; keep dependencies minimal (redis-py is already a dependency; add nothing heavier).
1. Structured logging
Replace the print() calls in scripts/server.py and scripts/ocr.py with stdlib logging emitting one JSON object per line to stdout (ts, level, event, and the relevant fields). Keep messages semantically identical to today's prints.
2. Audit events per classification
After each /classify and each item of /classify/batch, emit one audit event. Transport: XADD to a Redis Stream — reuse the existing Redis connection settings; stream key from new env AUDIT_STREAM_KEY (suggested value ahu.ai.audit), and a separate optional AUDIT_REDIS_URL (falls back to REDIS_URL). If AUDIT_STREAM_KEY is unset, emit nothing. Emission must be fire-and-forget: wrap in try/except, never fail or slow a classification because Redis is down (use a short socket timeout).
Event fields (flat JSON, one stream entry):
event_id ULID or uuid4
schema_ver 1
ts ISO-8601 UTC
engine "doc-classifier"
operation "classify"
model model version string already exposed at /info
status "ok" | "error" | "low_confidence" | "cache_hit"
doc_hash the sha256 already computed for the cache key
classification, confidence, matched_keywords, reason
queue_ms 0 (no queue in this service)
upstream_ms ocr_ms (the Azure DI call time)
total_ms elapsed_ms
trace_id from incoming X-Request-Id header if present, else null
These timing/result fields are all already computed for the HTTP response — this change persists what is currently thrown away.
3. Heartbeat to the manager
When new env MANAGER_URL is set, start a background task that POSTs every 30 s to {MANAGER_URL}/agents/heartbeat with {service: "doc-classifier", device, model_version, cache_stats} (device/dtype from the existing resolution logic, model version from /info). Fire-and-forget with a 5 s timeout; log failures at debug level only. If MANAGER_URL is unset, no task starts.
4. Leave OCR routing alone
LAYOUT_URL (Azure DI) stays as-is; it will be pointed at the gateway in a later phase by env change only. Do not modify scripts/ocr.py routing logic.
5. Verification
- With no new envs:
/classifyresponses byte-identical (compare a golden response), startup logs equivalent, no Redis stream writes. - With
AUDIT_STREAM_KEYset against a local Redis: one well-formed event per classification (including cache hits, withstatus: "cache_hit"), and killing Redis mid-run must not fail classifications. - Container still builds; no new pip dependencies beyond what
requirements.txtalready has (ULID may be replaced by uuid4 to honor this).
Report back: files changed, sample emitted event JSON, and confirmation of the Redis-down behavior test.