Skip to content

Milestone 8 — Retrieval API and Downstream Publishing

Overview

M8 exposes the governed knowledge in the Raw to Knowledge registry as a first-class public API. It adds semantic retrieval (Weaviate + registry fallback), two downstream export formats (JSON-LD and Markdown), a dashboard metrics endpoint backed by Prometheus gauges, and a /metrics scrape target for the Prometheus ecosystem.

Architecture

GET /v1/retrieval/search          →  RetrievalService.search()
                                         ├─ WeaviateAdapter.search_similar_answers()   (primary)
                                         └─ PostgresRegistryRepository.query()         (fallback)

GET /v1/retrieval/answers/{id}/export  →  RetrievalService.export_answer()
                                              ├─ JSONLDExporter    (format=jsonld)
                                              └─ MarkdownAnswerExporter (format=markdown)

GET /v1/dashboard/metrics         →  DashboardService.get_metrics()
                                         ├─ registry counts by status/horn_type
                                         ├─ expiry window (30 days)
                                         ├─ work-item pending count
                                         └─ Prometheus gauge sync

GET /metrics                      →  generate_latest() — Prometheus scrape target

Domain Layer

src/raw_to_knowledge/domain/models/retrieval.py

Model Frozen Purpose
RetrievalResult Single search result with relevance_score and source label
ExportFormat str Enum jsonld | markdown
AnswerExport Rendered export artifact with content and timestamp
DashboardMetrics Registry health snapshot: counts, type distribution, F2

src/raw_to_knowledge/domain/services/answer_exporter.py

Two pure-Python (zero I/O) exporters:

JSONLDExporter.export(answer) -> str - Produces a JSON-LD document using schema.org and raw_to_knowledge: vocabulary - Includes all grammar fields, evidence links, review_due, version

MarkdownAnswerExporter.export(answer) -> str - GitHub-flavoured Markdown with Provenance and optional Assertion Grammar sections - Assertion Grammar section omitted when all grammar fields are None

Application Layer

src/raw_to_knowledge/application/retrieval/retrieval_service.py

class RetrievalService:
    async def search(query, customer_scope=None, horn_type=None, limit=10) -> list[RetrievalResult]
    async def export_answer(answer_id, fmt: ExportFormat) -> AnswerExport

Search algorithm: 1. Attempt vector_repo.search_similar_answers(query, limit=limit) 2. If fewer than 2 results returned, or vector repo raises → fall back to registry_repo.query(search_text=query, ...) 3. Deduplicate by answer_id, sort by relevance_score descending 4. Vector results labelled source="weaviate", fallback results labelled source="registry" with fixed score 0.70

src/raw_to_knowledge/application/dashboard/dashboard_service.py

class DashboardService:
    async def get_metrics() -> DashboardMetrics

Queries: - registry_repo.query(status="approved")total_approved - registry_repo.query(status="disputed")total_disputed - registry_repo.query(review_due_before=<+30d>)expiring_soon - Per-HornType count for all 9 Horn types → by_horn_type - work_item_repo.list_work_items(state=NEW) + PENDING_VALIDATIONwork_items_pending - f2_score, precision, recall, last_evaluatedNone (deferred to observability integration)

Infrastructure

src/raw_to_knowledge/infrastructure/metrics.py

Prometheus metric objects (module-level singletons):

Metric Type Labels Purpose
raw_to_knowledge_retrieval_requests_total Counter source Retrieval API call count
raw_to_knowledge_retrieval_latency_seconds Histogram End-to-end retrieval latency
raw_to_knowledge_approved_answers_total Gauge Updated by dashboard endpoint
raw_to_knowledge_disputed_answers_total Gauge Updated by dashboard endpoint
raw_to_knowledge_answers_expiring_soon Gauge Answers due within 30 days
raw_to_knowledge_work_items_pending Gauge NEW + PENDING_VALIDATION items
raw_to_knowledge_f2_score Gauge Latest evaluation F2
raw_to_knowledge_precision Gauge Latest precision
raw_to_knowledge_recall Gauge Latest recall
raw_to_knowledge_candidates_ingested_total Counter flow_type Intake throughput
raw_to_knowledge_validation_decisions_total Counter decision Validation throughput
raw_to_knowledge_classification_duration_seconds Histogram Classification latency
raw_to_knowledge_registry_query_duration_seconds Histogram Registry read latency

API Layer

Retrieval Router (GET /v1/retrieval/...)

GET /v1/retrieval/search

Query params:
  q              string   required  Search query text
  customer_scope string   optional  Filter by customer scope
  horn_type      string   optional  Filter by Horn information type
  limit          int      1-50      Max results (default 10)

Response: list[RetrievalResultResponse]
  answer_id, answer_text, horn_type, assertion_subtype,
  subject, predicate, object_value, scope,
  status, version, relevance_score, source

Instruments raw_to_knowledge_retrieval_latency_seconds (histogram) and raw_to_knowledge_retrieval_requests_total (counter, label = source of first result or "empty").

GET /v1/retrieval/answers/{answer_id}/export

Path:  answer_id  string  Registry answer identifier
Query: format     string  "markdown" (default) | "jsonld"

Response:
  Content-Type: text/markdown; charset=utf-8  (markdown)
               application/ld+json            (jsonld)
  Content-Disposition: attachment; filename="answer-{id}.md|.jsonld"

Returns 404 (via ValueError from service) when answer not found. Returns 422 when an unknown format string is supplied.

Dashboard Router (GET /v1/dashboard/...)

GET /v1/dashboard/metrics

Response: DashboardMetricsResponse
  total_approved         int
  total_disputed         int
  expiring_soon          int
  by_horn_type           dict[str, int]  (all 9 Horn types)
  f2_score               float | null
  precision              float | null
  recall                 float | null
  last_evaluated         string | null   (ISO 8601)
  validation_throughput_7d  int
  work_items_pending     int

Also syncs Prometheus gauges: raw_to_knowledge_approved_answers_total, raw_to_knowledge_disputed_answers_total, raw_to_knowledge_answers_expiring_soon, raw_to_knowledge_work_items_pending.

Prometheus Endpoint (GET /metrics)

Returns text/plain; version=0.0.4 Prometheus exposition format via prometheus_client.generate_latest(). Hidden from OpenAPI schema (include_in_schema=False).

Quality Gates

Gate Target Status
Export test coverage — JSONLDExporter 100% ✓ (11 tests)
Export test coverage — MarkdownAnswerExporter 100% ✓ (13 tests)
Retrieval service — search paths Both vector + fallback ✓ (9 tests)
Retrieval service — export paths Both formats ✓ (5 tests)
Dashboard service All metric fields ✓ (11 tests)
Integration test Skipped without WEAVIATE_URL ✓ (conditional)
Syntax check — all M8 files py_compile

Bug Fixes

  • GeneratedCandidate.answer_candidate_id was typed str (non-nullable) but AGWMPService set it to None for LLM-generated candidates. Fixed to str | None = None.
  • DashboardService.list_work_items was passing .value (string) instead of WorkItemState enum to PostgresWorkItemRepository.list_work_items. Fixed to pass enum directly.
  • retrieval.py export route passed a raw string fmt to RetrievalService.export_answer which expects ExportFormat. Fixed with explicit ExportFormat(fmt) conversion and 422 on invalid format.

Version

0.8.0 — bumped in main.py, FastAPI constructor, and /health endpoint.

Dependencies Added

No new dependencies. prometheus-client was already a transitive dependency via FastAPI; pytest-asyncio was installed for the async test suite (M6 onward).