4e8eb2dc0e
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>
64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
"""Response schemas for the product-scope orchestrator (step 3).
|
|
|
|
These are application/API types — NOT compliance-meta-model classes (architecture
|
|
freeze v1.0 untouched). The scope verdict itself is produced by the existing
|
|
`discover_scope`; nothing here adds scope rules.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
from typing import List, Optional
|
|
|
|
from pydantic import BaseModel, Field
|
|
|
|
from compliance.navigator.engine import CompletenessSummary
|
|
from compliance.navigator.questions import NavigatorQuestion
|
|
from compliance.profile.canonical import CanonicalProductRegulatoryProfile
|
|
from compliance.reasoning.enums import Confidence
|
|
from compliance.reasoning.schemas import (
|
|
ApplicableRegulation,
|
|
ExcludedRegulation,
|
|
UncertainRegulation,
|
|
)
|
|
|
|
|
|
class ScopeStatus(str, Enum):
|
|
NEEDS_FACTS = "needs_facts" # P0 facts missing -> ask, do not decide
|
|
RESOLVED = "resolved" # minimum facts present -> scope decided
|
|
|
|
|
|
class UnsupportedDomain(BaseModel):
|
|
"""A domain the product triggers but the corpus does not yet cover.
|
|
|
|
Surfaced for transparency (no false completeness) — NEVER a legal evaluation.
|
|
"""
|
|
|
|
domain: str
|
|
trigger: str
|
|
status: str = "future_corpus_needed"
|
|
note: str = ""
|
|
|
|
|
|
class RegulatoryScopeResult(BaseModel):
|
|
applicable_regulations: List[ApplicableRegulation] = Field(default_factory=list)
|
|
excluded_regulations: List[ExcludedRegulation] = Field(default_factory=list)
|
|
uncertain_regulations: List[UncertainRegulation] = Field(default_factory=list)
|
|
unsupported_domains: List[UnsupportedDomain] = Field(default_factory=list)
|
|
reasoning_summary: str = ""
|
|
confidence: Confidence = Confidence.MEDIUM
|
|
|
|
|
|
class ProductScopeRequest(BaseModel):
|
|
product_profile: CanonicalProductRegulatoryProfile
|
|
|
|
|
|
class ProductScopeResponse(BaseModel):
|
|
status: ScopeStatus
|
|
completeness_summary: CompletenessSummary
|
|
# case NEEDS_FACTS
|
|
missing_facts: List[str] = Field(default_factory=list)
|
|
suggested_questions: List[NavigatorQuestion] = Field(default_factory=list)
|
|
# case RESOLVED
|
|
regulatory_scope: Optional[RegulatoryScopeResult] = None
|