feat(company): Company Intelligence 2A — Company Capability Profile foundation
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>
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
"""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,
|
||||
)
|
||||
Reference in New Issue
Block a user