8c893ca783
HEAD of the spine Company->Capability->Product->Regulation->Obligation->Procedure ->Evidence. New compliance/company/ package: CompanyContext container + a four-state trust model (declared/inferred/confirmed/unknown). Hard rule (structural): a certification yields at most an INFERRED candidate and is never auto-treated as CONFIRMED/"erfuellt". A certification produces evidence-of- capability; only real ExistingEvidence promotes a capability to CONFIRMED. Ownership: Reasoning owns the container + trust-state; the Certification->Capability mapping is Execution's domain, consumed via an injected contract. No mapping data in product code (tests inject mocks). No endpoint/UI/RAG/new regs/controls; no meta-model classes (freeze v1.0 untouched). 8 tests; mypy --strict clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
115 lines
4.3 KiB
Python
115 lines
4.3 KiB
Python
"""Company Intelligence engine (Phase 2A) — build the Company Capability Profile.
|
|
|
|
Deterministic, no LLM/RAG. Turns a raw CompanyContext into capability evidence,
|
|
candidates and (only via explicit verification) confirmed capabilities.
|
|
|
|
HARD RULE enforced here: a certification yields at most an INFERRED candidate; it
|
|
can NEVER produce a CONFIRMED capability on its own. Only real ExistingEvidence
|
|
(`proves_capability_id`) promotes a capability to CONFIRMED. Certifications without
|
|
a known mapping yield evidence-of-claim but NO inferred capability (the mapping is
|
|
Execution's data, injected — never hard-coded here).
|
|
|
|
Python 3.9 compatible (no `|` unions).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Dict, List, Optional, Tuple
|
|
|
|
from compliance.reasoning.enums import Confidence
|
|
|
|
from .contract import EMPTY_MAPPING, CertificationCapabilityMap
|
|
from .schemas import (
|
|
CapabilityEvidence,
|
|
CompanyCapabilityProfile,
|
|
CompanyContext,
|
|
OperationalCapability,
|
|
OperationalCapabilityCandidate,
|
|
VerificationStatus,
|
|
)
|
|
|
|
|
|
def _declared(context: CompanyContext) -> List[OperationalCapabilityCandidate]:
|
|
out: List[OperationalCapabilityCandidate] = []
|
|
for d in context.declarations:
|
|
out.append(
|
|
OperationalCapabilityCandidate(
|
|
capability_id=d.capability_id,
|
|
source="declaration:%s" % context.company_id,
|
|
confidence=Confidence.MEDIUM,
|
|
verification_status=VerificationStatus.DECLARED,
|
|
)
|
|
)
|
|
return out
|
|
|
|
|
|
def _from_certifications(
|
|
context: CompanyContext, mapping: CertificationCapabilityMap
|
|
) -> Tuple[List[CapabilityEvidence], List[OperationalCapabilityCandidate]]:
|
|
# refinement 1: certification -> evidence-of-capability (claim) -> inferred candidate
|
|
evidence: List[CapabilityEvidence] = []
|
|
inferred: List[OperationalCapabilityCandidate] = []
|
|
for cert in context.certifications:
|
|
source = "certification:%s" % cert.certification_id
|
|
evidence.append(
|
|
CapabilityEvidence(
|
|
source=source,
|
|
claim="Company holds %s" % (cert.name or cert.certification_id),
|
|
certification_id=cert.certification_id,
|
|
)
|
|
)
|
|
entry = mapping.get(cert.certification_id)
|
|
if entry is None:
|
|
continue # no mapping known -> NO inferred capability (data is Execution's)
|
|
for cap_id in entry.capability_ids:
|
|
inferred.append(
|
|
OperationalCapabilityCandidate(
|
|
capability_id=cap_id,
|
|
source=source,
|
|
confidence=entry.confidence,
|
|
verification_status=VerificationStatus.INFERRED,
|
|
)
|
|
)
|
|
return evidence, inferred
|
|
|
|
|
|
def _confirmed_from_evidence(context: CompanyContext) -> List[OperationalCapability]:
|
|
proven: Dict[str, List[str]] = {}
|
|
for ev in context.evidence:
|
|
cap = ev.proves_capability_id
|
|
if not cap:
|
|
continue
|
|
proven.setdefault(cap, []).append(ev.evidence_id)
|
|
return [
|
|
OperationalCapability(
|
|
capability_id=cap,
|
|
verification_status=VerificationStatus.CONFIRMED,
|
|
confidence=Confidence.HIGH,
|
|
sources=sources,
|
|
)
|
|
for cap, sources in proven.items()
|
|
]
|
|
|
|
|
|
def build_company_profile(
|
|
context: CompanyContext, mapping: Optional[CertificationCapabilityMap] = None
|
|
) -> CompanyCapabilityProfile:
|
|
"""Build the Company Capability Profile from raw context + an injected mapping.
|
|
|
|
`mapping` defaults to EMPTY (no inferred candidates) so that the cert->capability
|
|
table can only ever come from the Compliance Execution domain.
|
|
"""
|
|
mapping = EMPTY_MAPPING if mapping is None else mapping
|
|
evidence, inferred = _from_certifications(context, mapping)
|
|
declared = _declared(context)
|
|
confirmed = _confirmed_from_evidence(context)
|
|
confirmed_ids = {oc.capability_id for oc in confirmed}
|
|
# a confirmed capability is no longer a mere candidate
|
|
candidates = [c for c in (declared + inferred) if c.capability_id not in confirmed_ids]
|
|
return CompanyCapabilityProfile(
|
|
company_id=context.company_id,
|
|
capability_evidence=evidence,
|
|
candidate_capabilities=candidates,
|
|
confirmed_capabilities=confirmed,
|
|
)
|