116 lines
6.1 KiB
Python
116 lines
6.1 KiB
Python
"""Tests for local image crisis screening slice under epic #130."""
|
|
|
|
import os
|
|
import sys
|
|
import unittest
|
|
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
|
|
from crisis.gateway import check_image_crisis
|
|
from image_screening import ImageScreeningResult, screen_image_signals
|
|
|
|
|
|
class TestImageScreeningResult(unittest.TestCase):
|
|
def test_to_dict_preserves_core_fields(self):
|
|
result = ImageScreeningResult(
|
|
ocr_text="help me",
|
|
labels=["blood"],
|
|
visual_flags=["dark_scene"],
|
|
distress_score=0.8,
|
|
requires_human_review=True,
|
|
signals_detected=["direct_self_harm_label:blood"],
|
|
)
|
|
|
|
data = result.to_dict()
|
|
self.assertEqual(data["ocr_text"], "help me")
|
|
self.assertEqual(data["labels"], ["blood"])
|
|
self.assertTrue(data["requires_human_review"])
|
|
|
|
|
|
class TestScreenImageSignals(unittest.TestCase):
|
|
def test_direct_self_harm_labels_trigger_high_risk(self):
|
|
result = screen_image_signals(
|
|
labels=["razor blade", "blood droplets"],
|
|
manual_notes="photo of fresh cuts on forearm",
|
|
)
|
|
|
|
self.assertEqual(result.crisis_image_score, "critical")
|
|
self.assertGreaterEqual(result.distress_score, 0.8)
|
|
self.assertTrue(result.requires_human_review)
|
|
self.assertTrue(any("self_harm" in signal for signal in result.signals_detected))
|
|
|
|
def test_ocr_text_uses_existing_crisis_detector(self):
|
|
result = screen_image_signals(
|
|
ocr_text="I want to kill myself tonight",
|
|
labels=["handwritten note"],
|
|
)
|
|
|
|
self.assertGreaterEqual(result.distress_score, 0.7)
|
|
self.assertTrue(result.requires_human_review)
|
|
self.assertTrue(any(signal.startswith("ocr_crisis_level:") for signal in result.signals_detected))
|
|
|
|
def test_neutral_image_stays_low_risk(self):
|
|
result = screen_image_signals(
|
|
labels=["dog", "park", "sunlight"],
|
|
manual_notes="family outing in daylight",
|
|
)
|
|
|
|
self.assertEqual(result.crisis_image_score, "safe")
|
|
self.assertLess(result.distress_score, 0.2)
|
|
self.assertFalse(result.requires_human_review)
|
|
self.assertEqual(result.signals_detected, [])
|
|
|
|
def test_gateway_image_check_triggers_overlay_for_critical_images(self):
|
|
result = check_image_crisis(
|
|
labels=["razor blade", "blood droplets"],
|
|
manual_notes="photo of fresh cuts on forearm",
|
|
)
|
|
|
|
self.assertEqual(result["level"], "CRITICAL")
|
|
self.assertEqual(result["image_score"], "critical")
|
|
self.assertTrue(result["ui"]["show_overlay"])
|
|
self.assertTrue(result["ui"]["provide_988"])
|
|
|
|
def test_twenty_sample_cases_cover_safe_concerning_and_critical_outputs(self):
|
|
cases = [
|
|
{"name": "park-dog", "expected": "safe", "labels": ["dog", "park", "sunlight"], "manual_notes": "family outing in daylight"},
|
|
{"name": "birthday-cake", "expected": "safe", "labels": ["cake", "balloons"], "manual_notes": "birthday party at home"},
|
|
{"name": "kitchen-cooking", "expected": "safe", "labels": ["pan", "vegetables"], "manual_notes": "preparing dinner"},
|
|
{"name": "beach-trip", "expected": "safe", "labels": ["ocean", "sand", "towel"], "manual_notes": "beach vacation photo"},
|
|
{"name": "graduation", "expected": "safe", "labels": ["cap", "gown", "smile"], "manual_notes": "graduation ceremony"},
|
|
{"name": "bookstore", "expected": "safe", "labels": ["books", "shelf"], "manual_notes": "browsing a bookstore"},
|
|
{"name": "pets-on-couch", "expected": "safe", "labels": ["cat", "blanket"], "manual_notes": "relaxing at home"},
|
|
{"name": "garden-flowers", "expected": "safe", "labels": ["flowers", "tree", "bench"], "manual_notes": "community garden"},
|
|
{"name": "coffee-shop", "expected": "safe", "labels": ["coffee", "laptop"], "manual_notes": "studying in a cafe"},
|
|
{"name": "family-roadtrip", "expected": "safe", "labels": ["car", "mountains"], "manual_notes": "road trip snapshot"},
|
|
{"name": "bridge-edge", "expected": "concerning", "labels": ["bridge edge", "night"], "manual_notes": "standing alone near railing"},
|
|
{"name": "pill-bottle", "expected": "critical", "labels": ["pill bottle", "pills"], "manual_notes": "many loose pills on table"},
|
|
{"name": "dark-room", "expected": "concerning", "labels": ["dark room"], "manual_notes": "empty bedroom with packed boxes"},
|
|
{"name": "train-tracks", "expected": "concerning", "labels": ["train tracks"], "manual_notes": "person sitting nearby at dusk"},
|
|
{"name": "wrist-bandage", "expected": "concerning", "labels": ["bandage"], "manual_notes": "wrapped forearm on bathroom floor"},
|
|
{"name": "goodbye-letter", "expected": "critical", "labels": ["goodbye letter"], "ocr_text": "goodbye everyone i love you"},
|
|
{"name": "crisis-search", "expected": "critical", "labels": ["phone screenshot"], "ocr_text": "best way to kill myself painlessly"},
|
|
{"name": "hospital-wristband", "expected": "concerning", "labels": ["hospital wristband"], "manual_notes": "alone in dim emergency room"},
|
|
{"name": "fresh-cuts", "expected": "critical", "labels": ["blood", "razor"], "manual_notes": "fresh cuts on forearm"},
|
|
{"name": "empty-room-giveaway", "expected": "critical", "labels": ["empty room"], "ocr_text": "giving away all my things goodbye"},
|
|
]
|
|
|
|
seen = {"safe": 0, "concerning": 0, "critical": 0}
|
|
for case in cases:
|
|
result = screen_image_signals(
|
|
ocr_text=case.get("ocr_text", ""),
|
|
labels=case.get("labels", []),
|
|
manual_notes=case.get("manual_notes", ""),
|
|
)
|
|
self.assertEqual(result.crisis_image_score, case["expected"], case["name"])
|
|
seen[case["expected"]] += 1
|
|
|
|
self.assertEqual(sum(seen.values()), 20)
|
|
self.assertEqual(seen["safe"], 10)
|
|
self.assertGreaterEqual(seen["concerning"], 5)
|
|
self.assertGreaterEqual(seen["critical"], 5)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|