Skip to content

Commit 47b2028

Browse files
committed
update to enable rule extract endpoint to return json format
1 parent 03ede4a commit 47b2028

File tree

4 files changed

+134
-194
lines changed

4 files changed

+134
-194
lines changed

frontend/src/api/apiClient.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const handleSendDrugSummary = async (message: FormValues["message"], guid: strin
5858

5959
const handleRuleExtraction = async (guid: string) => {
6060
try {
61-
const response = await api.get(`/v1/api/rule_extraction?guid=${guid}`);
61+
const response = await api.get(`/v1/api/rule_extraction_openai?guid=${guid}`);
6262
// console.log("Rule extraction response:", JSON.stringify(response.data, null, 2));
6363
return response.data;
6464
} catch (error) {

frontend/src/pages/DrugSummary/Insights.tsx

Lines changed: 50 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -7,40 +7,36 @@ interface Rule {
77
type: string;
88
reason: string;
99
medications: string[];
10+
source: string;
11+
chunk_number: number;
12+
chunk_text: string;
1013
}
1114

1215
interface RuleExtractionData {
16+
rules: Rule[];
1317
texts: string;
1418
cited_texts: string;
1519
}
1620

1721
const Insights: React.FC = () => {
18-
const [extractedData, setExtractedData] = useState<RuleExtractionData | null>(
19-
null
20-
);
22+
const [data, setData] = useState<RuleExtractionData | null>(null);
2123
const [loading, setLoading] = useState(false);
2224
const [error, setError] = useState<string | null>(null);
23-
const [parsedRules, setParsedRules] = useState<Rule[]>([]);
2425

2526
const { search } = useLocation();
2627
const params = new URLSearchParams(search);
2728
const guid = params.get("guid") || "";
2829

2930
useEffect(() => {
30-
fetchRuleExtraction();
31-
}, []);
31+
fetchData();
32+
}, [guid]);
3233

33-
const fetchRuleExtraction = async () => {
34+
const fetchData = async () => {
3435
setLoading(true);
3536
setError(null);
36-
3737
try {
3838
const result = await handleRuleExtraction(guid);
39-
setExtractedData(result);
40-
41-
if (result.texts) {
42-
parseRulesFromText(result.texts);
43-
}
39+
setData(result);
4440
} catch (err) {
4541
setError(
4642
err instanceof Error ? err.message : "Failed to fetch rule extraction"
@@ -50,101 +46,6 @@ const Insights: React.FC = () => {
5046
}
5147
};
5248

53-
const parseRulesFromText = (text: string) => {
54-
const rules: Rule[] = [];
55-
56-
let ruleMatches = text.match(/Rule \d+:[\s\S]*?(?=Rule \d+:|$)/g);
57-
58-
if (!ruleMatches || ruleMatches.length === 0) {
59-
ruleMatches = text.match(/\d+\.\s*Rule:[\s\S]*?(?=\d+\.\s*Rule:|$)/g);
60-
}
61-
62-
if (!ruleMatches || ruleMatches.length === 0) {
63-
ruleMatches = text.match(/\d+\.[\s\S]*?(?=\d+\.|$)/g);
64-
}
65-
66-
if (ruleMatches && ruleMatches.length > 0) {
67-
ruleMatches.forEach((ruleText) => {
68-
let ruleMatch, typeMatch, reasonMatch, medicationsMatch;
69-
70-
ruleMatch = ruleText.match(/(?:The )?rule(?:\s+is)?:?\s*([^.\n]+)/i);
71-
typeMatch = ruleText.match(
72-
/(?:The )?type(?:\s+of\s+rule)?(?:\s+is)?:?\s*"?([^".\n]+)"?/i
73-
);
74-
reasonMatch = ruleText.match(
75-
/(?:The )?reason(?:\s+is)?:?\s*([\s\S]*?)(?=(?:The )?medications?|$)/i
76-
);
77-
medicationsMatch = ruleText.match(
78-
/(?:The )?medications?(?:\s+for\s+this\s+rule)?(?:\s+are?)?:?\s*([^.\n]+)/i
79-
);
80-
81-
if (!ruleMatch) {
82-
ruleMatch =
83-
ruleText.match(/Rule:\s*([^.\n]+)/i) ||
84-
ruleText.match(/^\d+\.\s*([^:\n]+)/);
85-
}
86-
87-
if (!typeMatch) {
88-
typeMatch =
89-
ruleText.match(/Type:\s*"?([^".\n]+)"?/i) ||
90-
ruleText.match(/(EXCLUDE|INCLUDE)/i);
91-
}
92-
93-
if (!reasonMatch) {
94-
reasonMatch = ruleText.match(
95-
/Reason:\s*([\s\S]*?)(?=Medications?|$)/i
96-
);
97-
}
98-
99-
if (!medicationsMatch) {
100-
medicationsMatch = ruleText.match(/Medications?:\s*([^.\n]+)/i);
101-
}
102-
103-
let ruleName = "";
104-
if (ruleMatch) {
105-
ruleName = ruleMatch[1].trim();
106-
} else {
107-
const contextMatch = ruleText.match(
108-
/(pregnancy|metabolic|cardiac|renal|thyroid|drug interaction|extrapyramidal)/i
109-
);
110-
if (contextMatch) {
111-
ruleName = contextMatch[1];
112-
}
113-
}
114-
115-
let ruleType = "";
116-
if (typeMatch) {
117-
ruleType = typeMatch[1].trim().toUpperCase();
118-
}
119-
120-
let reason = "";
121-
if (reasonMatch) {
122-
reason = reasonMatch[1].trim().replace(/\s+/g, " ");
123-
}
124-
125-
let medications: string[] = [];
126-
if (medicationsMatch) {
127-
medications = medicationsMatch[1]
128-
.split(/[,;]/)
129-
.map((med) => med.trim())
130-
.filter((med) => med.length > 0);
131-
}
132-
133-
if (ruleName || ruleType) {
134-
rules.push({
135-
rule: ruleName || "Unknown Rule",
136-
type: ruleType || "UNKNOWN",
137-
reason: reason,
138-
medications: medications,
139-
});
140-
}
141-
});
142-
}
143-
144-
console.log("Parsed rules:", rules);
145-
setParsedRules(rules);
146-
};
147-
14849
const getTypeColor = (type: string) => {
14950
return type === "EXCLUDE"
15051
? "bg-red-100 text-red-800"
@@ -171,7 +72,7 @@ const Insights: React.FC = () => {
17172
<div className="bg-red-50 border border-red-200 rounded-lg p-4">
17273
<p className="text-red-700">{error}</p>
17374
<button
174-
onClick={fetchRuleExtraction}
75+
onClick={fetchData}
17576
className="mt-2 bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition-colors"
17677
>
17778
Retry
@@ -186,22 +87,22 @@ const Insights: React.FC = () => {
18687
<div className="flex justify-between items-center mb-4">
18788
<h2 className="text-lg font-semibold">Extracted Rules</h2>
18889
<button
189-
onClick={fetchRuleExtraction}
90+
onClick={fetchData}
19091
className="bg-blue-600 text-white px-3 py-1 rounded text-sm hover:bg-blue-700 transition-colors"
19192
>
19293
Refresh
19394
</button>
19495
</div>
19596

196-
{extractedData ? (
97+
{data ? (
19798
<div className="space-y-6">
19899
{/* Parsed Rules Section */}
199100
<div>
200101
<h3 className="text-md font-semibold mb-3 text-gray-800">
201-
Medication Rules ({parsedRules.length})
102+
Medication Rules ({data.rules.length})
202103
</h3>
203104
<div className="space-y-3">
204-
{parsedRules.map((rule, index) => (
105+
{data.rules.map((rule, index) => (
205106
<div
206107
key={index}
207108
className="border bg-blue-50 bg-opacity-50 border-sky-400 text-sm font-quicksand shadow-md rounded-lg p-2 relative"
@@ -218,20 +119,20 @@ const Insights: React.FC = () => {
218119
</div>
219120

220121
{rule.reason && (
221-
<p className="text-sm text-gray-700 mb-3 leading-relaxed">
122+
<p className="text-sm text-gray-700 mb-2 leading-relaxed">
222123
{rule.reason}
223124
</p>
224125
)}
225126

226127
{rule.medications.length > 0 && (
227-
<div>
128+
<div className="mb-2">
228129
<span className="text-sm font-medium text-gray-600">
229130
Medications:{" "}
230131
</span>
231132
<div className="flex flex-wrap gap-1 mt-1">
232-
{rule.medications.map((med, medIndex) => (
133+
{rule.medications.map((med, i) => (
233134
<span
234-
key={medIndex}
135+
key={i}
235136
className="bg-gray-100 text-gray-800 px-2 py-1 rounded text-xs"
236137
>
237138
{med}
@@ -240,46 +141,50 @@ const Insights: React.FC = () => {
240141
</div>
241142
</div>
242143
)}
144+
145+
{/* 🔍 View Source Text Section */}
146+
{rule.chunk_text && (
147+
<details className="mt-3 text-xs border border-gray-300 rounded p-2 bg-white text-gray-800">
148+
<summary className="cursor-pointer text-blue-600 font-medium">
149+
View Source
150+
{/* View Source Chunk ({rule.source}) */}
151+
</summary>
152+
<div className="mt-2 whitespace-pre-wrap">
153+
{rule.chunk_text}
154+
</div>
155+
</details>
156+
)}
157+
158+
{/* <div className="mt-2">
159+
<button
160+
onClick={() =>
161+
window.dispatchEvent(
162+
new CustomEvent("navigateToPdfPage", {
163+
detail: { pageNumber: rule.chunk_number },
164+
})
165+
)
166+
}
167+
className="text-blue-500 underline text-xs hover:text-blue-700"
168+
>
169+
Jump to Page {rule.chunk_number}
170+
</button>
171+
</div> */}
243172
</div>
244173
))}
245174
</div>
246175
</div>
247176

248-
{/* Raw Data Section - Collapsible */}
249-
<details className="border border-gray-200 rounded-lg">
250-
<summary className="bg-gray-50 px-4 py-2 cursor-pointer hover:bg-gray-100 transition-colors font-medium">
251-
Raw Extracted Text
252-
</summary>
253-
<div className="p-4 border-t border-gray-200">
254-
<div className="bg-gray-50 rounded p-3 text-sm text-gray-700 whitespace-pre-wrap max-h-60 overflow-y-auto">
255-
{extractedData.texts}
256-
</div>
257-
</div>
258-
</details>
259-
260-
{/* Cited Texts Section - Collapsible */}
261-
<details className="border border-gray-200 rounded-lg">
262-
<summary className="bg-gray-50 px-4 py-2 cursor-pointer hover:bg-gray-100 transition-colors font-medium">
263-
Cited References
264-
</summary>
265-
<div className="p-4 border-t border-gray-200">
266-
<div className="bg-gray-50 rounded p-3 text-sm text-gray-700 whitespace-pre-wrap max-h-60 overflow-y-auto">
267-
{extractedData.cited_texts}
268-
</div>
269-
</div>
270-
</details>
271-
272-
{/* JSON View - Collapsible */}
273-
<details className="border border-gray-200 rounded-lg">
177+
{/* JSON View */}
178+
{/* <details className="border border-gray-200 rounded-lg">
274179
<summary className="bg-gray-50 px-4 py-2 cursor-pointer hover:bg-gray-100 transition-colors font-medium">
275180
Raw JSON Response
276181
</summary>
277182
<div className="p-4 border-t border-gray-200">
278183
<pre className="bg-gray-900 text-green-400 rounded p-3 text-xs overflow-x-auto max-h-60 overflow-y-auto">
279-
{JSON.stringify(extractedData, null, 2)}
184+
{JSON.stringify(data, null, 2)}
280185
</pre>
281186
</div>
282-
</details>
187+
</details> */}
283188
</div>
284189
) : (
285190
<div className="text-center py-8 text-gray-500">

0 commit comments

Comments
 (0)