-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_adapters.py
More file actions
150 lines (132 loc) · 6.24 KB
/
api_adapters.py
File metadata and controls
150 lines (132 loc) · 6.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""Transform a ContextIQ PipelineResult into the shape the UI JavaScript expects.
The UI was originally designed around a simple Azure OpenAI synthesis response:
{ facts, conflicts, brief, metrics, synthesizedAt }
ContextIQ produces a richer PipelineResult. This module maps between them so
the existing UI JS works without structural changes, while also attaching the
full ContextIQ communications as extra fields consumed by the Outputs screen.
"""
from __future__ import annotations
from models.schemas import PipelineResult
# ContextIQ confidence → UI severity
_CONF_TO_SEVERITY = {"high": "critical", "medium": "high", "low": "medium"}
def pipeline_result_to_synthesis(result: PipelineResult) -> dict:
"""Map a PipelineResult to the UI's expected synthesis response shape."""
syn = result.synthesis_report
rca = result.rca_report
cfd = result.conflict_report
comms = result.communications
met = result.run_metrics
# ── Facts ─────────────────────────────────────────────────────────────────
facts = [
{
"fact": f.fact,
"text": f.fact,
"source": f.source_artifact_id,
"source_artifact_id": f.source_artifact_id,
"source_entry_id": f.source_entry_id,
"confidence": f.confidence,
"severity": _CONF_TO_SEVERITY.get(f.confidence, "medium"),
}
for f in syn.confirmed_facts
]
# ── RCA chain ─────────────────────────────────────────────────────────────
rca_chain = [
{
"layer": node.layer,
"statement": node.statement,
"confidence": node.confidence,
"source": node.source_artifact_id,
"source_artifact_id": node.source_artifact_id,
"source_entry_id": node.source_entry_id,
}
for node in rca.causal_chain
]
# ── RCA dead branches ──────────────────────────────────────────────────────
dead_branches = [
{
"layer": node.layer,
"statement": node.statement,
"confidence": node.confidence,
"source": node.source_artifact_id,
"source_artifact_id": node.source_artifact_id,
"source_entry_id": node.source_entry_id,
"resolution_note": node.resolution_note,
}
for node in rca.dead_branches
]
# ── Conflicts ─────────────────────────────────────────────────────────────
conflicts = [
{
"id": i + 1,
"title": c.description[:70],
"severity": c.severity,
"artifactA": {
"name": c.source_a_artifact_id,
"claim": c.source_a_claim,
},
"artifactB": {
"name": c.source_b_artifact_id,
"claim": c.source_b_claim,
},
"possibleExplanation": c.rca_resolution or "",
}
for i, c in enumerate(cfd.conflicts)
]
# ── Triage brief ──────────────────────────────────────────────────────────
metrics_str = (
"; ".join(f"{k}: {v}" for k, v in syn.key_metrics.items())
if syn.key_metrics
else "See synthesised facts."
)
conflict_summary = (
f"{cfd.total_conflicts} conflict(s) detected, {cfd.unresolved_count} unresolved."
if cfd.total_conflicts
else "No conflicts detected."
)
brief = {
"situation": syn.initial_hypothesis,
"keyData": metrics_str,
"rootCauseHypothesis": rca.root_cause.statement,
"conflictSummary": conflict_summary,
"recommendedActions": [
rca.technical_recommendation,
rca.process_recommendation,
],
}
# ── Performance metrics ───────────────────────────────────────────────────
timing = met.get("timing", {}) if isinstance(met, dict) else {}
total_ms = int(timing.get("total", 0) * 1000) if isinstance(timing, dict) else 0
metrics_out = {
"factsExtracted": len(facts),
"conflictsFound": len(conflicts),
"processingTimeMs": total_ms,
"citationCoverage": comms.audit_manifest.citation_coverage_pct,
"timing": timing,
}
# ── Full synthesis response ───────────────────────────────────────────────
return {
# Core UI fields (unchanged contract)
"facts": facts,
"conflicts": conflicts,
"brief": brief,
"metrics": metrics_out,
"synthesizedAt": comms.comms_timestamp,
# RCA chain + confidence
"rca": {
"confidence_overall": rca.confidence_overall,
"causal_chain": rca_chain,
"dead_branches": dead_branches,
"contributing_factors": rca.contributing_factors,
},
# Synthesis extras consumed by the audit trail
"key_metrics": syn.key_metrics,
"open_questions": syn.open_questions,
# ContextIQ-specific — consumed by the Outputs screen
"communications": {
"customer_update": comms.customer_update,
"executive_brief": comms.executive_brief,
"engineering_handoff": comms.engineering_handoff,
},
"audit_manifest": comms.audit_manifest.model_dump(),
"security_report": result.security_report.model_dump(),
}