@@ -201,9 +201,7 @@ def get_value_of_observation(self, observation) -> str | None:
201201 if value and observation .unit_concept_id :
202202 value = value + " " + self .get_concept_name (observation .unit_concept_id )
203203 if value and observation .qualifier_concept_id :
204- value = (
205- self .get_concept_name (observation .qualifier_concept_id ) + " : " + value
206- )
204+ value = (self .get_concept_name (observation .qualifier_concept_id ) + " : " + value )
207205 return value
208206
209207 def get_nodes_of_background (self , case_id , title_config ):
@@ -270,142 +268,144 @@ def get_page_configuration(self):
270268 """
271269 return self .system_config_repository .get_config_by_id ("page_config" ).json_config
272270
273- def get_case_review (self , case_config_id ): # pragma: no cover
271+ def get_case_review (self , case_config_id ): # pragma: no cover
274272 """
275- 1) Load the saved DisplayConfig (path_config list of { "path": "...", "style": {...} })
276- for this case_config_id + user_email. If none or wrong user, error .
277- 2) Build the full “case_details” tree by calling `get_case_detail(case_id)` .
278- 3) Prune each parent (e.g. “Family History”, “Medical History”, etc.) down to exactly the
279- leaves specified in path_config. Attach collapse/highlight/top style at the parent .
280- 4) If the raw CSV contained exactly the path "RISK ASSESSMENT. CRC risk assessments", then
281- and only then fetch any “Adjusted CRC Risk: …” obs (concept_id = 45614722), rename it,
282- and append as “AI Colorectal Cancer Risk Score” under importantInfos.
283- 5) Return a Case(...) object containing the pruned tree plus any “important info” nodes .
273+ 1) Load saved DisplayConfig for this case_config_id + user_email.
274+ 2) Build full unpruned case_details tree .
275+ 3) Prune under “BACKGROUND” according to CSV path_config .
276+ 4) If CSV included a “Colorectal Cancer Score” entry, use that value
277+ directly under “AI CRC Risk Score (<6: Low; 6-11: Medium; >11: High)” .
278+ 5) Otherwise, if CSV toggled CRC risk assessments, look first for a
279+ “Colorectal Cancer Score” observation in the DB; if none, fall
280+ back to the old “Adjusted CRC Risk” observation logic. If still
281+ nothing, emit “N/A” .
284282 """
283+ # load configuration & verify access
285284 configuration = self .configuration_repository .get_configuration_by_id (case_config_id )
286285 current_user = get_user_email_from_jwt ()
287286 if not configuration or configuration .user_email != current_user :
288287 raise BusinessException (BusinessExceptionEnum .NoAccessToCaseReview )
289288
290- # 1) Build the raw case_details ( unpruned – contains all leaves)
289+ # raw, unpruned
291290 case_details = self .get_case_detail (configuration .case_id )
292291
293- # 2) Gather every single path_config entry into a map: parent_key → list of {leaf, style}
292+ # index CSV path_config entries
294293 raw_path_cfg = configuration .path_config or []
295294 parent_to_entries : dict [str , list [dict ]] = defaultdict (list )
296295
297- # Also: detect if the CSV explicitly toggled CRC risk assessments at all
298- has_crc_toggle = False
296+ csv_crc_score_leaf : str | None = None
297+ old_crc_toggle = False
299298
300299 for entry in raw_path_cfg :
301- path_str = entry .get ("path" , "" ).strip ()
302- style_dict = entry .get ("style" , {} ) or {}
300+ path_str = ( entry .get ("path" ) or "" ).strip ()
301+ style = entry .get ("style" ) or {}
303302 if not path_str :
304- # skip lines with no path
305303 continue
306304
307- # If exactly "RISK ASSESSMENT.CRC risk assessments" appears, note it:
308- if path_str == "RISK ASSESSMENT.CRC risk assessments" :
309- has_crc_toggle = True
310-
311305 segments = path_str .split ("." )
312306 if len (segments ) < 2 :
313- # malformed, skip
314307 continue
315- parent_key = "." .join (segments [:- 1 ]) # e.g. "BACKGROUND.Family History"
316- leaf_text = segments [- 1 ] # e.g. "Diabetes: Yes"
317- parent_to_entries [parent_key ].append ({
318- "leaf" : leaf_text ,
319- "style" : style_dict ,
320- })
321-
322- # 3) Build a list of important_infos for any node that has a “top” style
323- important_infos : list [dict ] = []
324308
325- # 4) Walk down case_details to prune each parent. We only prune under “BACKGROUND”
326- for top_node in case_details :
327- if top_node .key != "BACKGROUND" :
309+ parent_key = "." .join (segments [:- 1 ])
310+ leaf_text = segments [- 1 ]
311+
312+ # CSV‐provided score override?
313+ if path_str .startswith ("RISK ASSESSMENT.Colorectal Cancer Score" ):
314+ csv_crc_score_leaf = leaf_text
315+
316+ # old‐style toggle
317+ if path_str == "RISK ASSESSMENT.CRC risk assessments" :
318+ old_crc_toggle = True
319+
320+ parent_to_entries [parent_key ].append ({"leaf" : leaf_text , "style" : style })
321+
322+ # prune under BACKGROUND
323+ important_infos : list [dict ] = []
324+ for top in case_details :
325+ if top .key != "BACKGROUND" :
328326 continue
329- # top_node.values is a list of TreeNode children:
330- # [ TreeNode("Patient Demographics"), TreeNode("Family History"), TreeNode("Social History"),
331- # TreeNode("Medical History"), ... ]
332- for child in top_node .values :
333- # ALWAYS keep "Patient Demographics" as-is (Age/Gender). Skip pruning for it:
327+ for child in top .values :
334328 if child .key == "Patient Demographics" :
335329 continue
336330
337- parent_key = f"BACKGROUND.{ child .key } "
338- if parent_key not in parent_to_entries :
339- # If CSV asked nothing about this parent, prune _all_ leaves under it.
331+ pk = f"BACKGROUND.{ child .key } "
332+ if pk not in parent_to_entries :
340333 child .values = []
341334 continue
342335
343- # Otherwise, collect all leaf entries under this parent
344- entries = parent_to_entries [ parent_key ]
345- leaves_to_keep = { e [ "leaf" ] for e in entries }
336+ entries = parent_to_entries [ pk ]
337+ keep = { e [ "leaf" ] for e in entries }
338+ child . values = [ v for v in child . values if v in keep ]
346339
347- # child.values was originally a list of strings (all possible leaves). Prune:
348- child .values = [val for val in child .values if val in leaves_to_keep ]
349-
350- # Merge all style dicts from entries for this parent into one combined style
351- combined_style : dict = {}
340+ merged : dict = {}
352341 for e in entries :
353- style_dict = e .get ("style" ) or {}
354- # CSV generator sets collapse=False to indicate “show this leaf” → invert it
355- if "collapse" in style_dict :
356- combined_style ["collapse" ] = not style_dict ["collapse" ]
357- if "highlight" in style_dict :
358- combined_style ["highlight" ] = style_dict ["highlight" ]
359- if "top" in style_dict :
360- # If multiple “top” values, pick the largest weight
361- if "top" not in combined_style or style_dict ["top" ] > combined_style ["top" ]:
362- combined_style ["top" ] = style_dict ["top" ]
363-
364- # Attach the merged style to this parent node
365- child .style = combined_style
366-
367- # If there is a “top” key, add to important_infos
368- if "top" in combined_style :
342+ s = e ["style" ]
343+ if "collapse" in s :
344+ merged ["collapse" ] = not s ["collapse" ]
345+ if "highlight" in s :
346+ merged ["highlight" ] = s ["highlight" ]
347+ if "top" in s :
348+ merged ["top" ] = max (merged .get ("top" , - 1 ), s ["top" ])
349+
350+ child .style = merged
351+ if "top" in merged :
369352 important_infos .append ({
370353 "key" : child .key ,
371354 "values" : child .values ,
372- "weight" : combined_style ["top" ],
355+ "weight" : merged ["top" ],
373356 })
374357
375- # 5) Sort important_infos by weight, then convert to TreeNode list
358+ # sort & wrap
376359 important_infos .sort (key = itemgetter ("weight" ))
377- sorted_important_infos = list (map (lambda e : TreeNode (e ["key" ], e ["values" ]), important_infos ))
360+ sorted_important = [TreeNode (e ["key" ], e ["values" ]) for e in important_infos ]
361+
362+ # label we use in the UI
363+ ai_label = "AI CRC Risk Score (<6: Low; 6-11: Medium; >11: High)"
364+
365+ if csv_crc_score_leaf :
366+ sorted_important .append (TreeNode (ai_label , [csv_crc_score_leaf ]))
378367
379- # 6) ONLY if the CSV included "RISK ASSESSMENT.CRC risk assessments",
380- # fetch any “Adjusted CRC Risk: …” observation (concept_id = 45614722),
381- # rename it to “AI-Predicted CRC Risk Score”, and append as “AI Colorectal Cancer Risk Score”.
382- if has_crc_toggle :
368+ # fallback branch
369+ elif old_crc_toggle :
383370 crc_obs = self .observation_repository .get_observations_by_concept (
384371 configuration .case_id , [45614722 ]
385372 )
386- for obs in crc_obs :
387- if obs .value_as_string and obs .value_as_string .startswith ("Adjusted CRC Risk" ):
388- # Rewrite the leaf text: replace "Adjusted CRC Risk" with "AI-Predicted CRC Risk Score"
389- raw_text = obs .value_as_string # e.g. "Adjusted CRC Risk: 0.546125"
390- new_leaf = raw_text .replace (
391- "Adjusted CRC Risk" , "AI-Predicted CRC Risk Score"
392- )
393- # Use "AI Colorectal Cancer Risk Score" as the parent key
394- sorted_important_infos .append (
395- TreeNode ("AI Colorectal Cancer Risk Score" , [new_leaf ])
396- )
397- break
398373
399- # 7) Return a Case object containing:
400- # • person_source_value (e.g. MRN or whatever)
401- # • case_id (converted to str)
402- # • case_details
403- # • importantInfos (only includes AI‐CRC node if CSV requested RISK ASSESSMENT toggles)
374+ # (i) is there a literal Colorectal Cancer Score: X row?
375+ csv_obs = next (
376+ (
377+ o .value_as_string
378+ for o in crc_obs
379+ if o .value_as_string and o .value_as_string .startswith ("Colorectal Cancer Score" )
380+ ),
381+ None
382+ )
383+ if csv_obs :
384+ sorted_important .append (TreeNode (ai_label , [csv_obs ]))
385+ else :
386+ # (ii) fall back to Adjusted CRC Risk
387+ for obs in crc_obs :
388+ txt = obs .value_as_string or ""
389+ if txt .startswith ("Adjusted CRC Risk" ):
390+ new_txt = txt .replace (
391+ "Adjusted CRC Risk" ,
392+ "AI-Predicted CRC Risk Score"
393+ )
394+ sorted_important .append (TreeNode (ai_label , [new_txt ]))
395+ break
396+ else :
397+ # (iii) still nothing? emit N/A
398+ sorted_important .append (TreeNode (ai_label , ["N/A" ]))
399+
400+ # if neither CSV nor old toggle, emit N/A
401+ else :
402+ sorted_important .append (TreeNode (ai_label , ["N/A" ]))
403+
404404 return Case (
405405 self .person .person_source_value ,
406406 str (configuration .case_id ),
407407 case_details ,
408- sorted_important_infos ,
408+ sorted_important ,
409409 )
410410
411411 def __get_current_case_by_user (self , user_email ) -> tuple [int , str ] | tuple [None , None ]:
0 commit comments