From 2f7fd299c72aba183c832038a9534b6a0e042cd3 Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Fri, 19 Dec 2025 18:08:45 +0200 Subject: [PATCH 01/13] Access class_probabilities attribute from node --- pathways/typing/mermaid.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pathways/typing/mermaid.py b/pathways/typing/mermaid.py index 50008d6..326ec9d 100644 --- a/pathways/typing/mermaid.py +++ b/pathways/typing/mermaid.py @@ -79,7 +79,7 @@ def create_cart_diagram(root: Node) -> str: links = [] for node in root.preorder(): - probabilities = getattr(node, "class_probabilities", None) + probabilities = node.class_probabilities if node.is_leaf and probabilities: prob_shapes, prob_links = create_segment_probability_stack( @@ -195,7 +195,7 @@ def create_form_diagram(root: Node, *, skip_notes: bool = False) -> str: continue is_segment_leaf = node.name == "segment" - probabilities = getattr(node, "class_probabilities", None) + probabilities = node.class_probabilities if is_segment_leaf and probabilities: prob_shapes, prob_links = create_segment_probability_stack( From e9e6b50e07e353e45665533fd28e95f2ca9367d0 Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Mon, 22 Dec 2025 16:16:20 +0200 Subject: [PATCH 02/13] Use deadend note when max prob is less than the threshold --- pathways/typing/options.py | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 89dc033..6916b6e 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -98,18 +98,38 @@ def add_segment_note( def add_segment_notes( - root: Node, settings_config: dict, segments_config: dict | None = None + root: Node, + settings_config: dict, + segments_config: dict | None = None, + confidence_threshold: float | None = None, ) -> Node: - """Add notes once segments are assigned.""" + """Add notes once segments are assigned. + + If confidence_threshold is provided (0.0-1.0), calculate the max probability. If max_probability < threshold, + dead-end note will be applied. Otherwise, the segment note is applied. + """ new_root = copy.deepcopy(root) note_label = { key.replace("segment_note", "label"): value for key, value in settings_config.items() if key.startswith("segment_note") } + low_conf_label = { + key.replace("deadend_note", "label"): value + for key, value in settings_config.items() + if key.startswith("deadend_note") + } for node in new_root.preorder(): if node.is_leaf and node.name == "segment": - add_segment_note(node, note_label, segments_config) + use_low_conf = False + if confidence_threshold is not None and node.class_probabilities: + max_prob = max(node.class_probabilities.values()) + use_low_conf = max_prob < confidence_threshold + + if use_low_conf and low_conf_label: + add_segment_note(node, low_conf_label, segments_config) + else: + add_segment_note(node, note_label, segments_config) return new_root From e0a084c56a2a856ae0013eb502ccef1b1b3a4ba1 Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Mon, 22 Dec 2025 16:55:11 +0200 Subject: [PATCH 03/13] parameter name fix --- pathways/typing/options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 6916b6e..6c5df78 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -101,7 +101,7 @@ def add_segment_notes( root: Node, settings_config: dict, segments_config: dict | None = None, - confidence_threshold: float | None = None, + low_confidence_threshold: float | None = None, ) -> Node: """Add notes once segments are assigned. @@ -122,9 +122,9 @@ def add_segment_notes( for node in new_root.preorder(): if node.is_leaf and node.name == "segment": use_low_conf = False - if confidence_threshold is not None and node.class_probabilities: + if low_confidence_threshold is not None and node.class_probabilities: max_prob = max(node.class_probabilities.values()) - use_low_conf = max_prob < confidence_threshold + use_low_conf = max_prob < low_confidence_threshold if use_low_conf and low_conf_label: add_segment_note(node, low_conf_label, segments_config) From b227f6cfda2f1768b71eae7a4d0f2ff31841adfc Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Mon, 22 Dec 2025 18:54:11 +0200 Subject: [PATCH 04/13] Add print statements for debugging --- pathways/typing/options.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 6c5df78..60e6411 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -120,16 +120,30 @@ def add_segment_notes( if key.startswith("deadend_note") } for node in new_root.preorder(): + # --- DEBUG ROOT: loop reaches here --- + print("\nVisiting node:", node) if node.is_leaf and node.name == "segment": + print(" -> Segment leaf detected") + print("class_probabilities:", node.class_probabilities) + print("threshold:", low_confidence_threshold) + print("low_conf_label:", low_conf_label) use_low_conf = False if low_confidence_threshold is not None and node.class_probabilities: max_prob = max(node.class_probabilities.values()) + print("max_prob:", max_prob, " type:", type(max_prob)) + try: + print("comparison result:", max_prob < low_confidence_threshold) + except Exception as e: + print("comparison error:", e) use_low_conf = max_prob < low_confidence_threshold - + print("use_low_conf final =", use_low_conf) if use_low_conf and low_conf_label: + print(" *** APPLYING LOW CONF NOTE ***") add_segment_note(node, low_conf_label, segments_config) else: + print(" --- applying normal note ---") add_segment_note(node, note_label, segments_config) + return new_root From 26d6a7e88be66ad357b812f4a3657860f889ff25 Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Mon, 22 Dec 2025 19:14:10 +0200 Subject: [PATCH 05/13] Using percentage instead of floats for threshold --- pathways/typing/options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 60e6411..ce1b2d7 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -129,6 +129,7 @@ def add_segment_notes( print("low_conf_label:", low_conf_label) use_low_conf = False if low_confidence_threshold is not None and node.class_probabilities: + low_confidence_threshold = low_confidence_threshold / 100.0 max_prob = max(node.class_probabilities.values()) print("max_prob:", max_prob, " type:", type(max_prob)) try: From efb0ad8a2189978787e14bad7dccfdfc7db29857 Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Mon, 22 Dec 2025 19:19:01 +0200 Subject: [PATCH 06/13] Revert "Using percentage instead of floats for threshold" This reverts commit 26d6a7e88be66ad357b812f4a3657860f889ff25. --- pathways/typing/options.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index ce1b2d7..60e6411 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -129,7 +129,6 @@ def add_segment_notes( print("low_conf_label:", low_conf_label) use_low_conf = False if low_confidence_threshold is not None and node.class_probabilities: - low_confidence_threshold = low_confidence_threshold / 100.0 max_prob = max(node.class_probabilities.values()) print("max_prob:", max_prob, " type:", type(max_prob)) try: From a176ecd11df6e8418dcb5e2650d915106fe8fd3b Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Mon, 22 Dec 2025 19:23:28 +0200 Subject: [PATCH 07/13] Adding dividing by 100 to create percentage back --- pathways/typing/options.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 60e6411..5f18b00 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -108,6 +108,7 @@ def add_segment_notes( If confidence_threshold is provided (0.0-1.0), calculate the max probability. If max_probability < threshold, dead-end note will be applied. Otherwise, the segment note is applied. """ + low_confidence_threshold = low_confidence_threshold / 100 new_root = copy.deepcopy(root) note_label = { key.replace("segment_note", "label"): value From 2b9bd6f6972ebb9d1ace98f25821ef93ca8177c2 Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Tue, 23 Dec 2025 11:55:26 +0200 Subject: [PATCH 08/13] Add segment assignment note to low confidence note and remove debugging print statements --- pathways/typing/options.py | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 5f18b00..e000922 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -120,29 +120,18 @@ def add_segment_notes( for key, value in settings_config.items() if key.startswith("deadend_note") } + for node in new_root.preorder(): - # --- DEBUG ROOT: loop reaches here --- - print("\nVisiting node:", node) if node.is_leaf and node.name == "segment": - print(" -> Segment leaf detected") - print("class_probabilities:", node.class_probabilities) - print("threshold:", low_confidence_threshold) - print("low_conf_label:", low_conf_label) use_low_conf = False if low_confidence_threshold is not None and node.class_probabilities: max_prob = max(node.class_probabilities.values()) - print("max_prob:", max_prob, " type:", type(max_prob)) - try: - print("comparison result:", max_prob < low_confidence_threshold) - except Exception as e: - print("comparison error:", e) use_low_conf = max_prob < low_confidence_threshold - print("use_low_conf final =", use_low_conf) if use_low_conf and low_conf_label: - print(" *** APPLYING LOW CONF NOTE ***") - add_segment_note(node, low_conf_label, segments_config) + # Combine segment note with low confidence note + combined_label = {**note_label, **low_conf_label} + add_segment_note(node, combined_label, segments_config) else: - print(" --- applying normal note ---") add_segment_note(node, note_label, segments_config) return new_root From 4cd64ace9592f5a36ab938b6622a784238e36816 Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Tue, 23 Dec 2025 13:32:43 +0200 Subject: [PATCH 09/13] Code refactor for combined label --- pathways/typing/options.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index e000922..454afb6 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -127,16 +127,20 @@ def add_segment_notes( if low_confidence_threshold is not None and node.class_probabilities: max_prob = max(node.class_probabilities.values()) use_low_conf = max_prob < low_confidence_threshold - if use_low_conf and low_conf_label: - # Combine segment note with low confidence note - combined_label = {**note_label, **low_conf_label} - add_segment_note(node, combined_label, segments_config) - else: - add_segment_note(node, note_label, segments_config) + final_label = note_label.copy() + if use_low_conf: + for key, seg_note in final_label.items(): + low_conf_note = low_conf_label.get( + key, + "\n[Low segment assignment confidence]\n" + "We recommend stopping this survey and starting with a new respondent." + ) + final_label[key] = seg_note + low_conf_note + + add_segment_note(node, final_label, segments_config) return new_root - def enforce_relevance(root: Node) -> Node: """Enforce relevance rules for the node. From b258a6dd33e0c90c657969f5c05c588fa5d03d8e Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Tue, 23 Dec 2025 14:45:05 +0200 Subject: [PATCH 10/13] Update docstring --- pathways/typing/options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 454afb6..972a0c0 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -105,8 +105,8 @@ def add_segment_notes( ) -> Node: """Add notes once segments are assigned. - If confidence_threshold is provided (0.0-1.0), calculate the max probability. If max_probability < threshold, - dead-end note will be applied. Otherwise, the segment note is applied. + If confidence_threshold is provided (percentage), calculate max probability. If max_probability < threshold, + segment + dead-end note will be applied. Otherwise, only segment note is applied. """ low_confidence_threshold = low_confidence_threshold / 100 new_root = copy.deepcopy(root) From 93f1e44c9f5d6ac2fc17f715f875d4fe65886a8b Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Tue, 23 Dec 2025 16:05:03 +0200 Subject: [PATCH 11/13] feat: display probability stack only if max prob less than threshold --- pathways/typing/mermaid.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pathways/typing/mermaid.py b/pathways/typing/mermaid.py index 326ec9d..ceb9f57 100644 --- a/pathways/typing/mermaid.py +++ b/pathways/typing/mermaid.py @@ -174,9 +174,10 @@ def create_segment_probability_stack( return shapes, links -def create_form_diagram(root: Node, *, skip_notes: bool = False) -> str: +def create_form_diagram(root: Node, *, skip_notes: bool = False, threshold: float) -> str: """Create mermaid diagram for typing form.""" header = "flowchart TD" + threshold = threshold / 100.0 shapes = { "segment": "stadium", "select_one": "rectangle", @@ -198,11 +199,17 @@ def create_form_diagram(root: Node, *, skip_notes: bool = False) -> str: probabilities = node.class_probabilities if is_segment_leaf and probabilities: - prob_shapes, prob_links = create_segment_probability_stack( - node, probabilities, "circle" - ) - shapes_lst.extend(prob_shapes) - links.extend(prob_links) + max_prob = max(probabilities.values()) + if max_prob < threshold: + prob_shapes, prob_links = create_segment_probability_stack( + node, probabilities, "circle" + ) + shapes_lst.extend(prob_shapes) + links.extend(prob_links) + else: + shape_label = get_form_shape_label(node) + shape = draw_shape(node.uid, shape_label, "circle") + shapes_lst.append(shape) else: shape_type = "circle" if is_segment_leaf else shapes[node.question.type] shape_label = get_form_shape_label(node) From 4302bc9e4f010f3142c970083ddc601a3aec206f Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Tue, 23 Dec 2025 18:23:23 +0200 Subject: [PATCH 12/13] Normalize escaped newlines in segment notes --- pathways/typing/options.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 972a0c0..225aa12 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -111,12 +111,12 @@ def add_segment_notes( low_confidence_threshold = low_confidence_threshold / 100 new_root = copy.deepcopy(root) note_label = { - key.replace("segment_note", "label"): value + key.replace("segment_note", "label"): value.replace("\\n", "\n") if isinstance(value, str) else value for key, value in settings_config.items() if key.startswith("segment_note") } low_conf_label = { - key.replace("deadend_note", "label"): value + key.replace("deadend_note", "label"): value.replace("\\n", "\n") if isinstance(value, str) else value for key, value in settings_config.items() if key.startswith("deadend_note") } @@ -308,7 +308,7 @@ def exit_deadends( # create note for dead-end deadend_label = { - key.replace("deadend_note", "label"): value + key.replace("deadend_note", "label"): value.replace("\\n", "\n") if isinstance(value, str) else value for key, value in settings_config.items() if key.startswith("deadend_note") } From 749df4141db3c23022158016ade337a1016fcd45 Mon Sep 17 00:00:00 2001 From: yara-elkady Date: Wed, 24 Dec 2025 12:49:05 +0200 Subject: [PATCH 13/13] Add default value of zero to threshold parameter to avoid division error --- pathways/typing/mermaid.py | 2 +- pathways/typing/options.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pathways/typing/mermaid.py b/pathways/typing/mermaid.py index ceb9f57..899f0c3 100644 --- a/pathways/typing/mermaid.py +++ b/pathways/typing/mermaid.py @@ -174,7 +174,7 @@ def create_segment_probability_stack( return shapes, links -def create_form_diagram(root: Node, *, skip_notes: bool = False, threshold: float) -> str: +def create_form_diagram(root: Node, *, skip_notes: bool = False, threshold: float = 0.0) -> str: """Create mermaid diagram for typing form.""" header = "flowchart TD" threshold = threshold / 100.0 diff --git a/pathways/typing/options.py b/pathways/typing/options.py index 225aa12..3581eed 100644 --- a/pathways/typing/options.py +++ b/pathways/typing/options.py @@ -101,7 +101,7 @@ def add_segment_notes( root: Node, settings_config: dict, segments_config: dict | None = None, - low_confidence_threshold: float | None = None, + low_confidence_threshold: float = 0.0, ) -> Node: """Add notes once segments are assigned. @@ -124,7 +124,7 @@ def add_segment_notes( for node in new_root.preorder(): if node.is_leaf and node.name == "segment": use_low_conf = False - if low_confidence_threshold is not None and node.class_probabilities: + if low_confidence_threshold > 0 and node.class_probabilities: max_prob = max(node.class_probabilities.values()) use_low_conf = max_prob < low_confidence_threshold final_label = note_label.copy()