Fix opportunity competing-risk evaluation

This commit is contained in:
Xin Wang
2026-09-16 04:31:18 +08:00
parent 02e83c0c11
commit d4c900ab13
10 changed files with 310 additions and 88 deletions
+93 -7
View File
@@ -30,7 +30,10 @@ def reviewed_row(
region_start: float,
region_end: float,
selected_release: float | None = None,
selected_release_uid: str | None = None,
nearby: list[dict[str, object]] | None = None,
opportunity_options: list[dict[str, object]] | None = None,
gap_other_intervals: list[dict[str, object]] | None = None,
candidate_source: str | None = None,
) -> dict[str, object]:
event_type = None
@@ -45,6 +48,7 @@ def reviewed_row(
"human_verdict": verdict,
"human_event_type": event_type,
"human_subtype": subtype,
"selected_opportunity_uid": selected_release_uid,
"selected_opportunity_seconds": selected_release,
"boundary_correction_seconds": 0,
},
@@ -59,6 +63,8 @@ def reviewed_row(
"behavior_anchor_seconds": anchor,
"wearer_offset_seconds": anchor,
"nearby_other_intervals": nearby or [],
"opportunity_options": opportunity_options or [],
"gap_other_intervals": gap_other_intervals or [],
},
},
}
@@ -119,7 +125,7 @@ class OpportunityEventTests(unittest.TestCase):
diagnostics["skipped"]["entry_backchannel_separate_diagnostic"], 1
)
def test_loader_selects_latest_crossing_other_for_int(self) -> None:
def test_loader_excludes_ambiguous_crossing_others_for_int(self) -> None:
nearby = [
{
"onset_seconds": 1.0,
@@ -145,11 +151,91 @@ class OpportunityEventTests(unittest.TestCase):
)
])
self.addCleanup(temporary.cleanup)
events, _ = load_opportunity_events([path], ["Session_4"])
self.assertEqual(len(events), 1)
self.assertEqual(events[0].opportunity_anchor_seconds, 2.0)
self.assertEqual(events[0].censor_seconds, 3.8)
self.assertEqual(events[0].competing_intervals, 2)
events, diagnostics = load_opportunity_events([path], ["Session_4"])
self.assertEqual(events, [])
self.assertEqual(
diagnostics["skipped"]["int_positive_ambiguous_competing_others"],
1,
)
def test_loader_excludes_entry_positive_at_competing_other_onset(self) -> None:
rows = [
reviewed_row(
"entry-competed",
family="entry",
verdict="POSITIVE",
subtype=None,
anchor=1.6,
region_start=0.0,
region_end=3.0,
selected_release=1.0,
selected_release_uid="release",
opportunity_options=[
{
"uid": "release",
"anchor_seconds": 1.0,
"wearer_active_between_release_and_onset": False,
"other_onset_between_release_and_onset": True,
}
],
gap_other_intervals=[
{"uid": "competitor", "onset_seconds": 1.4}
],
),
reviewed_row(
"entry-simultaneous",
family="entry",
verdict="POSITIVE",
subtype=None,
anchor=4.0,
region_start=3.0,
region_end=5.0,
selected_release=3.2,
gap_other_intervals=[
{"uid": "competitor", "onset_seconds": 4.0}
],
),
]
temporary, path = self.write_reviews(rows)
self.addCleanup(temporary.cleanup)
events, diagnostics = load_opportunity_events([path], ["Session_4"])
self.assertEqual(events, [])
self.assertEqual(
diagnostics["skipped"]["entry_positive_competed_before_behavior"],
2,
)
def test_loader_excludes_entry_positive_after_prior_wearer_activity(self) -> None:
temporary, path = self.write_reviews([
reviewed_row(
"entry-post-outcome",
family="entry",
verdict="POSITIVE",
subtype=None,
anchor=2.0,
region_start=0.0,
region_end=3.0,
selected_release=1.0,
selected_release_uid="release",
opportunity_options=[
{
"uid": "release",
"anchor_seconds": 1.0,
"wearer_active_between_release_and_onset": True,
"other_onset_between_release_and_onset": False,
}
],
)
])
self.addCleanup(temporary.cleanup)
events, diagnostics = load_opportunity_events([path], ["Session_4"])
self.assertEqual(events, [])
self.assertEqual(
diagnostics["skipped"][
"entry_positive_post_outcome_wearer_activity"
],
1,
)
def test_entry_hn_requires_matching_independently_mined_source(self) -> None:
rows = [
@@ -385,7 +471,7 @@ class OpportunityEventTests(unittest.TestCase):
payload = json.loads(output.read_text(encoding="utf-8"))
self.assertEqual(
payload["protocol"],
"egospeak-opportunity-conditioned-floor-actions-v1",
"egospeak-opportunity-conditioned-floor-actions-v2",
)
self.assertTrue((root / "summary_metrics.csv").is_file())
self.assertTrue((root / "summary_events.csv").is_file())