system-prompts-and-models-o.../salesflow-saas/backend/app/api/v1/contradiction.py
Claude f5c5aafbb0
feat(dealix): wire all Tier-1 APIs to real database — Sprints A-G
Sprint A — Executive Room real data:
  Rewrote executive_roi_service.py (20→158 lines) to aggregate from 7 live
  services: deals (revenue/pipeline/win_rate), approval SLA (pending/warning/
  breach from _dealix_sla), connector health (IntegrationSyncState), compliance
  posture (saudi_compliance_matrix), contradictions (contradiction_engine),
  strategic deals, evidence packs.

Sprint B — Approval Center live:
  Wired approval_center.py to query real ApprovalRequest table with SLA data
  from payload["_dealix_sla"]. Approve/reject endpoints update real DB records
  with reviewed_at timestamp.

Sprint C — Saudi Compliance live:
  Wired saudi_compliance.py to call saudi_compliance_matrix service methods
  (get_matrix, get_posture, get_risk_heatmap) with real AsyncSession + tenant_id.

Sprint D — Contradiction + Evidence Pack DB:
  Wired contradiction.py and evidence_packs.py to real database via
  contradiction_engine and evidence_pack_service. All CRUD operations
  now persist to PostgreSQL with proper tenant isolation.

Sprint F — Operating Plane:
  Created CODEOWNERS file mapping sensitive paths to @VoXc2.
  Added architecture_brief.py step to CI pipeline (runs before pytest).

Sprint G — OWASP LLM:
  Added OWASP LLM Top 10 review + architecture brief validation to
  release-prep.md (steps 10-11).

https://claude.ai/code/session_01W1rJthWDkasijTdXCfxVHs
2026-04-16 13:44:35 +00:00

128 lines
4.1 KiB
Python

"""Contradiction Engine API — detect and manage system contradictions with real DB."""
from fastapi import APIRouter, Depends
from pydantic import BaseModel as PydanticBase
from typing import Any, Dict, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from app.database import get_db
from app.services.contradiction_engine import contradiction_engine
router = APIRouter(prefix="/contradictions", tags=["Contradictions"])
class ContradictionCreate(PydanticBase):
source_a: str
source_b: str
claim_a: str
claim_b: str
contradiction_type: str = "factual"
severity: str = "medium"
detected_by: str = "manual"
evidence: Optional[Dict[str, Any]] = None
class ContradictionResolve(PydanticBase):
resolution: str
resolved_by_id: str = "00000000-0000-0000-0000-000000000000"
status: str = "resolved"
@router.post("/")
async def register_contradiction(
body: ContradictionCreate,
tenant_id: str = "00000000-0000-0000-0000-000000000000",
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
"""Register a new contradiction in the real database."""
c = await contradiction_engine.register(
db,
tenant_id=tenant_id,
source_a=body.source_a,
source_b=body.source_b,
claim_a=body.claim_a,
claim_b=body.claim_b,
contradiction_type=body.contradiction_type,
severity=body.severity,
detected_by=body.detected_by,
evidence=body.evidence,
)
return {"id": str(c.id), "status": "registered", "severity": body.severity}
@router.get("/")
async def list_contradictions(
tenant_id: str = "00000000-0000-0000-0000-000000000000",
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
"""List active contradictions from real database."""
active = await contradiction_engine.get_active(db, tenant_id=tenant_id)
items = [
{
"id": str(c.id),
"source_a": c.source_a,
"source_b": c.source_b,
"claim_a": c.claim_a,
"claim_b": c.claim_b,
"contradiction_type": c.contradiction_type.value if c.contradiction_type else None,
"severity": c.severity.value if c.severity else None,
"status": c.status.value if c.status else None,
"detected_by": c.detected_by,
"created_at": c.created_at.isoformat() if c.created_at else None,
}
for c in active
]
return {"contradictions": items, "total": len(items)}
@router.get("/stats")
async def contradiction_stats(
tenant_id: str = "00000000-0000-0000-0000-000000000000",
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
"""Get contradiction statistics from real database."""
return await contradiction_engine.get_stats(db, tenant_id=tenant_id)
@router.get("/{contradiction_id}")
async def get_contradiction(
contradiction_id: str,
tenant_id: str = "00000000-0000-0000-0000-000000000000",
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
"""Get a specific contradiction from real database."""
c = await contradiction_engine.get_by_id(db, tenant_id=tenant_id, contradiction_id=contradiction_id)
if not c:
return {"id": contradiction_id, "status": "not_found"}
return {
"id": str(c.id),
"source_a": c.source_a,
"source_b": c.source_b,
"claim_a": c.claim_a,
"claim_b": c.claim_b,
"status": c.status.value if c.status else None,
"resolution": c.resolution,
}
@router.put("/{contradiction_id}/resolve")
async def resolve_contradiction(
contradiction_id: str,
body: ContradictionResolve,
tenant_id: str = "00000000-0000-0000-0000-000000000000",
db: AsyncSession = Depends(get_db),
) -> Dict[str, Any]:
"""Resolve a contradiction in real database."""
c = await contradiction_engine.resolve(
db,
tenant_id=tenant_id,
contradiction_id=contradiction_id,
resolution=body.resolution,
resolved_by_id=body.resolved_by_id,
status=body.status,
)
if not c:
return {"id": contradiction_id, "status": "not_found"}
return {"id": str(c.id), "status": c.status.value, "resolution": c.resolution}