Files
breakpilot-compliance/backend-compliance/compliance/api/reasoning_routes.py
T
Benjamin Admin 4e8eb2dc0e feat(product-scope): gate Navigator facts, then reuse discover_scope (step 3)
Connects the Navigator's fact-gate to the existing reasoning discover_scope —
the Scope Engine decides only once the minimum (P0) facts are released.

- resolve_product_scope(canonical): if not ready_for_scope -> NEEDS_FACTS
  (missing_facts + suggested_questions, discover_scope NOT run); else project
  canonical->reasoning profile and run the EXISTING discover_scope exactly once
  -> RESOLVED with applicable/excluded/uncertain regulations.
- Environmental triggers surface ONLY as unsupported_domains (future_corpus_needed),
  never as a legal evaluation — transparency, no false completeness.
- POST /reasoning/product-scope (thin handler) returns case NEEDS_FACTS or RESOLVED.
- No new scope rules, no new regulations, no environmental-law evaluation, no UI,
  no Go, no RAG, no percent-compliance. Response types are application-level, not
  meta-model classes (freeze v1.0 untouched).
- 6 tests incl. discover_scope spy (0 calls when gated, exactly 1 when ready),
  category separation, environmental-as-unsupported-only. 47 tests green (existing
  reasoning MVP tests stay green), mypy clean, LOC ok.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-26 10:21:27 +02:00

80 lines
2.9 KiB
Python

"""HTTP endpoints for the Regulatory Reasoning Engine (spec §7).
Thin handlers — all reasoning lives in `compliance.reasoning.*`. No DB, no RAG;
pure deterministic rule evaluation.
POST /reasoning/scope -> which regulations apply + missing facts
POST /reasoning/obligations -> obligations, overlaps, multi-evidence
POST /reasoning/implementation-reasoning -> claim->obligation mapping (Welt 1, no verdict)
POST /reasoning/interpretation-assessment -> verdict on a customer interpretation
POST /reasoning/product-scope -> gate on facts, else run discover_scope once
"""
from __future__ import annotations
from fastapi import APIRouter
from compliance.product_scope import (
ProductScopeRequest,
ProductScopeResponse,
resolve_product_scope,
)
from compliance.reasoning import (
assess_interpretation,
derive_obligations,
discover_scope,
reason_implementation_claim,
)
from compliance.reasoning.schemas import (
ImplementationReasoningRequest,
ImplementationReasoningResponse,
InterpretationRequest,
InterpretationResponse,
ObligationsRequest,
ObligationsResponse,
ScopeRequest,
ScopeResponse,
)
router = APIRouter(prefix="/reasoning", tags=["reasoning"])
@router.post("/scope", response_model=ScopeResponse)
def scope_discovery(req: ScopeRequest) -> ScopeResponse:
scope = discover_scope(req.product_profile)
return ScopeResponse(
regulatory_scope=scope,
missing_facts=scope.missing_facts,
confidence=scope.confidence,
)
@router.post("/obligations", response_model=ObligationsResponse)
def applicable_obligations(req: ObligationsRequest) -> ObligationsResponse:
return derive_obligations(req.product_profile, req.regulatory_scope)
@router.post("/implementation-reasoning", response_model=ImplementationReasoningResponse)
def implementation_reasoning(req: ImplementationReasoningRequest) -> ImplementationReasoningResponse:
return reason_implementation_claim(req.product_profile, req.customer_claim)
@router.post("/product-scope", response_model=ProductScopeResponse)
def product_scope(req: ProductScopeRequest) -> ProductScopeResponse:
return resolve_product_scope(req.product_profile)
@router.post("/interpretation-assessment", response_model=InterpretationResponse)
def interpretation_assessment(req: InterpretationRequest) -> InterpretationResponse:
result = assess_interpretation(req.customer_interpretation, req.product_profile)
return InterpretationResponse(
assessment=result.assessment,
affected_regulations=result.affected_regulations,
affected_obligations=result.affected_obligations,
corrected_interpretation=result.corrected_interpretation,
risks=result.risks,
legal_basis_refs=result.legal_basis_refs,
explanation=result.explanation,
confidence=result.confidence,
)