Hackathon Builder commited on
Commit
2bb04fc
Β·
1 Parent(s): 6ed6bd7

Fix: React Error #31 by enforcing strict AI schemas and defensive rendering

Browse files
backend/main.py CHANGED
@@ -127,57 +127,58 @@ def _patient_card(p: Dict) -> Dict:
127
  # ── Agents ────────────────────────────────────────────────────────────────────
128
 
129
  def agent_triage(record: Dict, override_key: str = None) -> Dict:
130
- prompt = f"""You are an ER Danger Pattern Specialist. Analyze for 'Time-to-Collapse' risks.
131
- REASONING: Finding β†’ Danger β†’ Risk β†’ Urgency.
132
- PATIENT: {json.dumps(record)}
133
- Return ONLY JSON: {{ "critical_risks": [...], "severity": 1-3, "severity_label": "...", "chief_complaint": "...", "agent_reasoning": [...] }}"""
134
-
 
 
 
 
 
 
 
 
 
 
 
 
135
  r = _gemini(prompt, override_key=override_key)
136
  if r: return r
137
 
138
- # Fallback with diagnostic info
139
  v = record.get("vitals", {})
140
  spo2 = float(v.get("SpO2", 100)) if v.get("SpO2") else 100
141
  hr = float(v.get("Heart Rate", 80)) if v.get("Heart Rate") else 80
142
  sev = 1 if (spo2 < 90 or hr > 130 or hr < 45) else 2 if (spo2 < 93 or hr > 110) else 3
143
  labels = {1: "IMMEDIATE", 2: "EMERGENT", 3: "URGENT"}
144
-
145
  return {
146
  "critical_risks": [], "severity": sev, "severity_label": labels[sev],
147
- "chief_complaint": "Stabilization (LLM Unreachable)",
148
- "agent_reasoning": ["Connectivity Error: Gemini brain did not respond. Check API Key.", "Calculated based on vital thresholds."]
149
  }
150
 
151
  def agent_contraindication(record: Dict, proposed_treatment: str = "", triage: Dict = None, override_key: str = None) -> Dict:
152
  context = f"TREATMENT: {proposed_treatment}" if proposed_treatment else "SAFETY SCAN"
153
- prompt = f"ED Safety Guardian. PATIENT: {json.dumps(record)}\nTRIAGE: {json.dumps(triage)}\n{context}\nReturn JSON: {{ 'treatment_verdict': '...', 'verdict_reason': '...', 'agent_reasoning': [...] }}"
154
-
155
  r = _gemini(prompt, override_key=override_key)
156
- return r or {
157
- "treatment_verdict": "NOT_EVALUATED",
158
- "verdict_reason": "AI brain offline. Manual review required.",
159
- "agent_reasoning": ["Safety Agent failed to connect to Gemini."]
160
- }
161
 
162
  def agent_labs(record: Dict, triage: Dict = None, override_key: str = None) -> Dict:
163
- prompt = f"Pathologist. PATIENT: {json.dumps(record)}\nTRIAGE: {json.dumps(triage)}\nReturn JSON: {{ 'critical_labs': [...], 'agent_reasoning': [...] }}"
164
  r = _gemini(prompt, override_key=override_key)
165
- return r or {"critical_labs": [], "agent_reasoning": ["Lab Interpreter failed to connect to Gemini."]}
166
 
167
  def agent_differentials(record: Dict, triage: Dict = None, labs: Dict = None, override_key: str = None) -> Dict:
168
- prompt = f"Chief Resident. PATIENT: {json.dumps(record)}\nLABS: {json.dumps(labs)}\nReturn JSON: {{ 'differentials': [...], 'agent_reasoning': [...] }}"
169
  r = _gemini(prompt, override_key=override_key)
170
- return r or {"differentials": [], "agent_reasoning": ["Differential Agent failed to connect to Gemini."]}
171
 
172
  def agent_action_protocol(record: Dict, triage: Dict, contra: Dict, labs: Dict, diffs: Dict, override_key: str = None) -> Dict:
173
- prompt = f"ED Attending. Action Mapping.\nTRIAGE: {json.dumps(triage)}\nSAFETY: {json.dumps(contra)}\nLABS: {json.dumps(labs)}\nDIFFS: {json.dumps(diffs)}\nReturn JSON: {{ 'primary_directive': '...', 'action_mapping': [...], 'clinical_summary': '...', 'agent_reasoning': [...] }}"
174
  r = _gemini(prompt, override_key=override_key)
175
- return r or {
176
- "primary_directive": "Stabilize patient immediately (LLM Offline)",
177
- "action_mapping": [],
178
- "clinical_summary": "System operating in safety-fallback mode due to connectivity issues.",
179
- "agent_reasoning": ["Attending Agent failed to reach Gemini brain."]
180
- }
181
 
182
  # ── API Endpoints ─────────────────────────────────────────────────────────────
183
 
 
127
  # ── Agents ────────────────────────────────────────────────────────────────────
128
 
129
  def agent_triage(record: Dict, override_key: str = None) -> Dict:
130
+ prompt = f"""You are an ER Danger Pattern Specialist.
131
+ Analyze PATIENT: {json.dumps(record)}
132
+ Return ONLY JSON:
133
+ {{
134
+ "critical_risks": [
135
+ {{
136
+ "finding": "e.g. Glucose 48",
137
+ "danger": "e.g. Brain starvation",
138
+ "risk": "e.g. Seizure/Coma",
139
+ "urgency": "IMMEDIATE"
140
+ }}
141
+ ],
142
+ "severity": 1,
143
+ "severity_label": "IMMEDIATE",
144
+ "chief_complaint": "Summary",
145
+ "agent_reasoning": ["Step 1", "Step 2"]
146
+ }}"""
147
  r = _gemini(prompt, override_key=override_key)
148
  if r: return r
149
 
150
+ # Fallback
151
  v = record.get("vitals", {})
152
  spo2 = float(v.get("SpO2", 100)) if v.get("SpO2") else 100
153
  hr = float(v.get("Heart Rate", 80)) if v.get("Heart Rate") else 80
154
  sev = 1 if (spo2 < 90 or hr > 130 or hr < 45) else 2 if (spo2 < 93 or hr > 110) else 3
155
  labels = {1: "IMMEDIATE", 2: "EMERGENT", 3: "URGENT"}
 
156
  return {
157
  "critical_risks": [], "severity": sev, "severity_label": labels[sev],
158
+ "chief_complaint": "Stabilization (LLM Offline)",
159
+ "agent_reasoning": ["Connectivity Error: Gemini brain did not respond."]
160
  }
161
 
162
  def agent_contraindication(record: Dict, proposed_treatment: str = "", triage: Dict = None, override_key: str = None) -> Dict:
163
  context = f"TREATMENT: {proposed_treatment}" if proposed_treatment else "SAFETY SCAN"
164
+ prompt = f"ED Safety Guardian. PATIENT: {json.dumps(record)}\nTRIAGE: {json.dumps(triage)}\n{context}\nReturn ONLY JSON: {{ 'treatment_verdict': 'SAFE|CAUTION|CONTRAINDICATED', 'verdict_reason': 'string', 'agent_reasoning': ['string'] }}"
 
165
  r = _gemini(prompt, override_key=override_key)
166
+ return r or {"treatment_verdict": "NOT_EVALUATED", "verdict_reason": "AI offline.", "agent_reasoning": ["Failed to connect."]}
 
 
 
 
167
 
168
  def agent_labs(record: Dict, triage: Dict = None, override_key: str = None) -> Dict:
169
+ prompt = f"Pathologist. PATIENT: {json.dumps(record)}\nReturn ONLY JSON: {{ 'critical_labs': [{{'name':'string','value':'string','significance':'string'}}], 'agent_reasoning': ['string'] }}"
170
  r = _gemini(prompt, override_key=override_key)
171
+ return r or {"critical_labs": [], "agent_reasoning": ["Failed to connect."]}
172
 
173
  def agent_differentials(record: Dict, triage: Dict = None, labs: Dict = None, override_key: str = None) -> Dict:
174
+ prompt = f"Chief Resident. PATIENT: {json.dumps(record)}\nReturn ONLY JSON: {{ 'differentials': [{{'diagnosis':'string','likelihood':'HIGH','supporting_evidence':['string']}}], 'agent_reasoning': ['string'] }}"
175
  r = _gemini(prompt, override_key=override_key)
176
+ return r or {"differentials": [], "agent_reasoning": ["Failed to connect."]}
177
 
178
  def agent_action_protocol(record: Dict, triage: Dict, contra: Dict, labs: Dict, diffs: Dict, override_key: str = None) -> Dict:
179
+ prompt = f"ED Attending. Return ONLY JSON: {{ 'primary_directive': 'string', 'action_mapping': [{{'condition':'string','action':'string'}}], 'clinical_summary': 'string', 'agent_reasoning': ['string'] }}"
180
  r = _gemini(prompt, override_key=override_key)
181
+ return r or {"primary_directive": "Stabilize patient.", "action_mapping": [], "clinical_summary": "Offline.", "agent_reasoning": ["Failed to connect."]}
 
 
 
 
 
182
 
183
  # ── API Endpoints ─────────────────────────────────────────────────────────────
184
 
frontend/dist/assets/{index-C0LhoLqc.js β†’ index-DiPQ1rSL.js} RENAMED
The diff for this file is too large to render. See raw diff
 
frontend/dist/index.html CHANGED
@@ -7,7 +7,7 @@
7
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
  <title>CodeBlue Context</title>
10
- <script type="module" crossorigin src="/assets/index-C0LhoLqc.js"></script>
11
  <link rel="stylesheet" crossorigin href="/assets/index-DKXwe-v6.css">
12
  </head>
13
  <body>
 
7
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
8
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
9
  <title>CodeBlue Context</title>
10
+ <script type="module" crossorigin src="/assets/index-DiPQ1rSL.js"></script>
11
  <link rel="stylesheet" crossorigin href="/assets/index-DKXwe-v6.css">
12
  </head>
13
  <body>
frontend/src/BriefPanel.jsx CHANGED
@@ -4,6 +4,13 @@ import { ShieldAlert, FlaskConical, Stethoscope, ListChecks, AlertTriangle, Cloc
4
 
5
  const API_BASE = import.meta.env.VITE_API_BASE || '';
6
 
 
 
 
 
 
 
 
7
  function Section({ title, icon, children, accent }) {
8
  return (
9
  <div className="fade-up" style={{
@@ -191,23 +198,23 @@ export function BriefPanel({ data, patient }) {
191
  </div>
192
  )}
193
 
194
- {/* Immediate Critical Risks */}
195
  {triage.critical_risks?.length > 0 && (
196
  <SectionBlock title="Immediate Critical Risks" icon={<ShieldAlert size={14}/>} accent="var(--red-critical)">
197
  <div className="pulse-critical" style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
198
  {triage.critical_risks.map((r, i) => (
199
- <div key={i} style={{
200
  background: 'rgba(239, 68, 68, 0.08)', border: '1px solid rgba(239, 68, 68, 0.2)',
201
  borderRadius: 10, padding: '10px 14px'
202
  }}>
203
  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
204
  <AlertTriangle size={14} color="var(--red-critical)" />
205
  <span style={{ fontWeight: 800, fontSize: 12, color: 'var(--red-critical)' }}>
206
- {r.condition} ({r.evidence})
207
  </span>
208
  </div>
209
  <div style={{ fontSize: 11, color: 'var(--text-secondary)', marginLeft: 22 }}>
210
- β†’ risk of <span style={{ fontWeight: 700, color: 'var(--text-primary)' }}>{r.danger}</span>
211
  </div>
212
  </div>
213
  ))}
@@ -265,7 +272,7 @@ export function BriefPanel({ data, patient }) {
265
  {collectiveReasoning.map((r, i) => (
266
  <div key={i} style={{ fontSize: 10.5, color: 'var(--text-secondary)', display: 'flex', gap: 10 }}>
267
  <span style={{ fontWeight: 800, color: 'var(--purple-ai)', minWidth: 60, textTransform: 'uppercase', fontSize: 9 }}>{r.agent}</span>
268
- <span style={{ lineHeight: 1.4 }}>{r.text}</span>
269
  </div>
270
  ))}
271
  </div>
 
4
 
5
  const API_BASE = import.meta.env.VITE_API_BASE || '';
6
 
7
+ const renderText = (val) => {
8
+ if (val === null || val === undefined) return '';
9
+ if (typeof val === 'string') return val;
10
+ if (typeof val === 'object') return JSON.stringify(val);
11
+ return String(val);
12
+ };
13
+
14
  function Section({ title, icon, children, accent }) {
15
  return (
16
  <div className="fade-up" style={{
 
198
  </div>
199
  )}
200
 
201
+ {/* Immediate Critical Risks (Condition-First) */}
202
  {triage.critical_risks?.length > 0 && (
203
  <SectionBlock title="Immediate Critical Risks" icon={<ShieldAlert size={14}/>} accent="var(--red-critical)">
204
  <div className="pulse-critical" style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
205
  {triage.critical_risks.map((r, i) => (
206
+ <div key={`${r.condition}-${i}`} style={{
207
  background: 'rgba(239, 68, 68, 0.08)', border: '1px solid rgba(239, 68, 68, 0.2)',
208
  borderRadius: 10, padding: '10px 14px'
209
  }}>
210
  <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 4 }}>
211
  <AlertTriangle size={14} color="var(--red-critical)" />
212
  <span style={{ fontWeight: 800, fontSize: 12, color: 'var(--red-critical)' }}>
213
+ {renderText(r.finding || r.condition)} {r.evidence && `(${renderText(r.evidence)})`}
214
  </span>
215
  </div>
216
  <div style={{ fontSize: 11, color: 'var(--text-secondary)', marginLeft: 22 }}>
217
+ β†’ {renderText(r.risk)} <span style={{ opacity: 0.6 }}>[{renderText(r.danger)}]</span>
218
  </div>
219
  </div>
220
  ))}
 
272
  {collectiveReasoning.map((r, i) => (
273
  <div key={i} style={{ fontSize: 10.5, color: 'var(--text-secondary)', display: 'flex', gap: 10 }}>
274
  <span style={{ fontWeight: 800, color: 'var(--purple-ai)', minWidth: 60, textTransform: 'uppercase', fontSize: 9 }}>{r.agent}</span>
275
+ <span style={{ lineHeight: 1.4 }}>{renderText(r.text)}</span>
276
  </div>
277
  ))}
278
  </div>