Skip to content

Commit c8338a9

Browse files
authored
Merge pull request #5 from DHEPLab/feat/adjust-ai-score-display
feat(api): adjust AI Risk score display
2 parents a89c5b5 + ea4d24b commit c8338a9

File tree

3 files changed

+35
-50
lines changed

3 files changed

+35
-50
lines changed

src/cases/service/case_service.py

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -273,26 +273,27 @@ def get_case_review(self, case_config_id): # pragma: no cover
273273
1) Load saved DisplayConfig for this case_config_id + user_email.
274274
2) Build full unpruned case_details tree.
275275
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”.
276+
4) If CSV included a literal Colorectal Cancer Score leaf, use that directly.
277+
5) Else if CSV toggled CRC risk assessments, look for an observation row:
278+
a) First, any value_as_string starting with "Colorectal Cancer Score"
279+
b) Then, any "Adjusted CRC Risk" row (renaming it)
280+
c) If neither found, emit "N/A"
281+
6) If CSV had no RISK ASSESSMENT entries at all, do not emit any CRC score node.
282282
"""
283-
# load configuration & verify access
283+
# --- 1) Load configuration & verify access ---
284284
configuration = self.configuration_repository.get_configuration_by_id(case_config_id)
285285
current_user = get_user_email_from_jwt()
286286
if not configuration or configuration.user_email != current_user:
287287
raise BusinessException(BusinessExceptionEnum.NoAccessToCaseReview)
288288

289-
# raw, unpruned
289+
# --- 2) Raw, unpruned case tree ---
290290
case_details = self.get_case_detail(configuration.case_id)
291291

292-
# index CSV path_config entries
292+
# --- 3) Index CSV path_config entries ---
293293
raw_path_cfg = configuration.path_config or []
294294
parent_to_entries: dict[str, list[dict]] = defaultdict(list)
295295

296+
# Track whether CSV provided an explicit literal score or just toggled the section
296297
csv_crc_score_leaf: str | None = None
297298
old_crc_toggle = False
298299

@@ -303,40 +304,43 @@ def get_case_review(self, case_config_id): # pragma: no cover
303304
continue
304305

305306
segments = path_str.split(".")
307+
# We need at least a parent and a leaf
306308
if len(segments) < 2:
307309
continue
308310

309311
parent_key = ".".join(segments[:-1])
310312
leaf_text = segments[-1]
311313

312-
# CSVprovided score override?
314+
# CSV-provided literal score override?
313315
if path_str.startswith("RISK ASSESSMENT.Colorectal Cancer Score"):
314316
csv_crc_score_leaf = leaf_text
315317

316-
# old‐style toggle
318+
# Old-style toggle of the entire CRC section?
317319
if path_str == "RISK ASSESSMENT.CRC risk assessments":
318320
old_crc_toggle = True
319321

320322
parent_to_entries[parent_key].append({"leaf": leaf_text, "style": style})
321323

322-
# prune under BACKGROUND
324+
# --- 4) Prune under BACKGROUND according to path_config ---
323325
important_infos: list[dict] = []
324326
for top in case_details:
325327
if top.key != "BACKGROUND":
326328
continue
327329
for child in top.values:
328330
if child.key == "Patient Demographics":
331+
# always keep demographics
329332
continue
330-
331333
pk = f"BACKGROUND.{child.key}"
332334
if pk not in parent_to_entries:
335+
# no CSV entries for this category ⇒ drop it entirely
333336
child.values = []
334337
continue
335-
336338
entries = parent_to_entries[pk]
339+
# only keep those leaves the CSV listed
337340
keep = {e["leaf"] for e in entries}
338341
child.values = [v for v in child.values if v in keep]
339342

343+
# merge style directives (collapse, highlight, top)
340344
merged: dict = {}
341345
for e in entries:
342346
s = e["style"]
@@ -348,30 +352,32 @@ def get_case_review(self, case_config_id): # pragma: no cover
348352
merged["top"] = max(merged.get("top", -1), s["top"])
349353

350354
child.style = merged
351-
if "top" in merged:
355+
# collect any "top"-weighted nodes for separate display
356+
if merged.get("top") is not None:
352357
important_infos.append({
353358
"key": child.key,
354359
"values": child.values,
355360
"weight": merged["top"],
356361
})
357362

358-
# sort & wrap
363+
# sort by weight and wrap into TreeNodes
359364
important_infos.sort(key=itemgetter("weight"))
360365
sorted_important = [TreeNode(e["key"], e["values"]) for e in important_infos]
361366

362-
# label we use in the UI
367+
# --- 5) Handle AI CRC Risk Score section only if CSV referenced it ---
363368
ai_label = "AI CRC Risk Score (<6: Low; 6-11: Medium; >11: High)"
364369

365370
if csv_crc_score_leaf:
371+
# CSV gave us the exact leaf text to show
366372
sorted_important.append(TreeNode(ai_label, [csv_crc_score_leaf]))
367373

368-
# fallback branch
369374
elif old_crc_toggle:
375+
# CSV toggled the section but did not supply a literal score; fetch from DB
370376
crc_obs = self.observation_repository.get_observations_by_concept(
371377
configuration.case_id, [45614722]
372378
)
373379

374-
# (i) is there a literal Colorectal Cancer Score: X row?
380+
# (a) look first for any literal "Colorectal Cancer Score: X"
375381
csv_obs = next(
376382
(
377383
o.value_as_string
@@ -383,24 +389,24 @@ def get_case_review(self, case_config_id): # pragma: no cover
383389
if csv_obs:
384390
sorted_important.append(TreeNode(ai_label, [csv_obs]))
385391
else:
386-
# (ii) fall back to Adjusted CRC Risk
392+
# (b) fallback to any "Adjusted CRC Risk" row
387393
for obs in crc_obs:
388394
txt = obs.value_as_string or ""
389395
if txt.startswith("Adjusted CRC Risk"):
396+
# rename to match "AI-Predicted CRC Risk Score"
390397
new_txt = txt.replace(
391398
"Adjusted CRC Risk",
392399
"AI-Predicted CRC Risk Score"
393400
)
394401
sorted_important.append(TreeNode(ai_label, [new_txt]))
395402
break
396403
else:
397-
# (iii) still nothing? emit N/A
404+
# (c) CSV toggled but no score found ⇒ explicitly show N/A
398405
sorted_important.append(TreeNode(ai_label, ["N/A"]))
399406

400-
# if neither CSV nor old toggle, emit N/A
401-
else:
402-
sorted_important.append(TreeNode(ai_label, ["N/A"]))
407+
# 6) If neither csv_crc_score_leaf nor old_crc_toggle, we do not append any CRC score node.
403408

409+
# --- 7) Return the assembled Case object ---
404410
return Case(
405411
self.person.person_source_value,
406412
str(configuration.case_id),

tests/cases/controller/case_controller_test.py

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,14 +54,8 @@ def test_get_case_review(client, session, mocker):
5454
expected["details"][0]["values"][1]["values"] = []
5555
expected["details"][0]["values"][2]["values"] = []
5656

57-
# inject the AI CRC Risk Score node
58-
expected["importantInfos"] = [
59-
{
60-
"key": "AI CRC Risk Score (<6: Low; 6-11: Medium; >11: High)",
61-
"style": None,
62-
"values": ["N/A"],
63-
}
64-
]
57+
# when CSV doesn’t reference any RISK ASSESSMENT, importantInfos should be empty
58+
expected["importantInfos"] = []
6559

6660
assert data == expected
6761

tests/cases/service/case_service_test.py

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -920,12 +920,7 @@ def test_get_case_review_with_configuration_and_path_config(self, mocker):
920920
],
921921
)
922922
],
923-
importantInfos=[
924-
TreeNode(
925-
"AI CRC Risk Score (<6: Low; 6-11: Medium; >11: High)",
926-
["N/A"],
927-
)
928-
],
923+
importantInfos=[]
929924
)
930925

931926
def test_get_case_review_without_path_config(self, mocker):
@@ -974,12 +969,7 @@ def test_get_case_review_without_path_config(self, mocker):
974969
],
975970
)
976971
],
977-
importantInfos=[
978-
TreeNode(
979-
"AI CRC Risk Score (<6: Low; 6-11: Medium; >11: High)",
980-
["N/A"],
981-
)
982-
],
972+
importantInfos=[]
983973
)
984974

985975
def test_get_case_review_when_path_config_top_area(self, mocker):
@@ -1036,12 +1026,7 @@ def test_get_case_review_when_path_config_top_area(self, mocker):
10361026
],
10371027
)
10381028
],
1039-
importantInfos=[
1040-
TreeNode(
1041-
"AI CRC Risk Score (<6: Low; 6-11: Medium; >11: High)",
1042-
["N/A"],
1043-
)
1044-
],
1029+
importantInfos=[]
10451030
)
10461031

10471032

0 commit comments

Comments
 (0)