from __future__ import annotations import json from pathlib import Path import tempfile import unittest from review_floor_events import ( CandidateRepository, ReviewStore, expand_candidate_paths, load_candidates, parse_byte_range, ) class FloorEventReviewTests(unittest.TestCase): def _fixture(self, root: Path) -> tuple[Path, Path]: dataset = root / "dataset" video_dir = dataset / "Main" / "Video_Compressed" / "Session_4" transcript_dir = dataset / "Main" / "Speech_Transcriptions" / "Session_4" video_dir.mkdir(parents=True) transcript_dir.mkdir(parents=True) (video_dir / "00-00-000.mp4").write_bytes(b"mock-video") (transcript_dir / "00-00-000.json").write_text( json.dumps( [ { "Start_Frame": 1, "End_Frame": 20, "Participant_ID": 4, "Transcription": "Are you coming?", "Target_of_Speech": [2], }, { "Start_Frame": 25, "End_Frame": 40, "Participant_ID": 2, "Transcription": "Yes.", "Target_of_Speech": [4], }, ] ), encoding="utf-8", ) events = root / "candidates.jsonl" events.write_text( json.dumps( { "uid": "Session_4/00-00-000/0001", "session": "Session_4", "split": "train", "event_type": "ordinary_switch", "confidence": "timing", "participant_role": "wearer", "clip_stem": "00-00-000", "source_start_frame": 25, "dense_start_seconds": 1.2, } ) + "\n", encoding="utf-8", ) return dataset, events def test_candidate_detail_resolves_media_and_transcript(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) dataset, events = self._fixture(root) candidates = load_candidates([events]) repository = CandidateRepository( candidates, dataset, context_before_seconds=3.0, context_after_seconds=3.0, ) detail = repository.detail(candidates[0]) self.assertTrue(detail["media_available"]) self.assertEqual(detail["event_local_seconds"], 1.2) self.assertEqual(len(detail["transcript"]), 2) self.assertTrue(detail["transcript"][1]["is_anchor"]) def test_review_store_writes_resumable_jsonl_and_csv(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) _, events = self._fixture(root) candidates = load_candidates([events]) output = root / "reviews" / "reviews.jsonl" store = ReviewStore(output, candidates) review = { "protocol_disposition": "positive", "hard_negative_type": "not_applicable", "validity": "valid", "onset_function": "backchannel", "context": "addressed_wearer", "offset_outcome": "not_applicable", "anchor_adjustment_bins": 0, "behavior_adjustment_bins": 0, "region_end_adjustment_bins": None, "confidence": "high", "reviewer": "r1", "notes": "clear acknowledgement", } store.save(candidates[0].candidate_id, review) self.assertTrue(output.is_file()) self.assertTrue(output.with_suffix(".csv").is_file()) resumed = ReviewStore(output, candidates) self.assertEqual( resumed.reviews[candidates[0].candidate_id]["review"]["onset_function"], "backchannel", ) def test_directory_expansion_excludes_output(self) -> None: with tempfile.TemporaryDirectory() as temporary: root = Path(temporary) candidate = root / "Session_4.events.jsonl" output = root / "reviews.jsonl" candidate.write_text("{}\n", encoding="utf-8") output.write_text("{}\n", encoding="utf-8") self.assertEqual( expand_candidate_paths([root], output_path=output), [candidate.resolve()], ) def test_byte_ranges(self) -> None: self.assertEqual(parse_byte_range("bytes=2-5", 10), (2, 5)) self.assertEqual(parse_byte_range("bytes=7-", 10), (7, 9)) self.assertEqual(parse_byte_range("bytes=-3", 10), (7, 9)) with self.assertRaises(ValueError): parse_byte_range("bytes=20-30", 10) if __name__ == "__main__": unittest.main()