@@ -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
1215interface RuleExtractionData {
16+ rules : Rule [ ] ;
1317 texts : string ;
1418 cited_texts : string ;
1519}
1620
1721const 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 ( / R u l e \d + : [ \s \S ] * ?(? = R u l e \d + : | $ ) / g) ;
57-
58- if ( ! ruleMatches || ruleMatches . length === 0 ) {
59- ruleMatches = text . match ( / \d + \. \s * R u l e : [ \s \S ] * ?(? = \d + \. \s * R u l e : | $ ) / 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 ( / (?: T h e ) ? r u l e (?: \s + i s ) ? : ? \s * ( [ ^ . \n ] + ) / i) ;
71- typeMatch = ruleText . match (
72- / (?: T h e ) ? t y p e (?: \s + o f \s + r u l e ) ? (?: \s + i s ) ? : ? \s * " ? ( [ ^ " . \n ] + ) " ? / i
73- ) ;
74- reasonMatch = ruleText . match (
75- / (?: T h e ) ? r e a s o n (?: \s + i s ) ? : ? \s * ( [ \s \S ] * ?) (? = (?: T h e ) ? m e d i c a t i o n s ? | $ ) / i
76- ) ;
77- medicationsMatch = ruleText . match (
78- / (?: T h e ) ? m e d i c a t i o n s ? (?: \s + f o r \s + t h i s \s + r u l e ) ? (?: \s + a r e ? ) ? : ? \s * ( [ ^ . \n ] + ) / i
79- ) ;
80-
81- if ( ! ruleMatch ) {
82- ruleMatch =
83- ruleText . match ( / R u l e : \s * ( [ ^ . \n ] + ) / i) ||
84- ruleText . match ( / ^ \d + \. \s * ( [ ^ : \n ] + ) / ) ;
85- }
86-
87- if ( ! typeMatch ) {
88- typeMatch =
89- ruleText . match ( / T y p e : \s * " ? ( [ ^ " . \n ] + ) " ? / i) ||
90- ruleText . match ( / ( E X C L U D E | I N C L U D E ) / i) ;
91- }
92-
93- if ( ! reasonMatch ) {
94- reasonMatch = ruleText . match (
95- / R e a s o n : \s * ( [ \s \S ] * ?) (? = M e d i c a t i o n s ? | $ ) / i
96- ) ;
97- }
98-
99- if ( ! medicationsMatch ) {
100- medicationsMatch = ruleText . match ( / M e d i c a t i o n s ? : \s * ( [ ^ . \n ] + ) / i) ;
101- }
102-
103- let ruleName = "" ;
104- if ( ruleMatch ) {
105- ruleName = ruleMatch [ 1 ] . trim ( ) ;
106- } else {
107- const contextMatch = ruleText . match (
108- / ( p r e g n a n c y | m e t a b o l i c | c a r d i a c | r e n a l | t h y r o i d | d r u g i n t e r a c t i o n | e x t r a p y r a m i d a l ) / 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