Spaces:
Runtime error
Runtime error
Download tests/test_benchmark.py from fxsab/simpliscribe: direct link, hf CLI and curl.
- Browser
- Download file 11.9 kB
-
https://huggingface.co/spaces/fxsab/simpliscribe/resolve/main/tests/test_benchmark.py
- Command line
-
hf download hf://spaces/fxsab/simpliscribe/tests/test_benchmark.py
-
curl -L -o test_benchmark.py https://huggingface.co/spaces/fxsab/simpliscribe/resolve/main/tests/test_benchmark.py
11.9 kB
| from simpliscribe.benchmark import load_cases | |
| from simpliscribe.benchmark import load_case_bundle | |
| from simpliscribe.benchmark import evaluate_quality_gates | |
| from simpliscribe.benchmark import run_case | |
| from simpliscribe.benchmark import run_benchmark | |
| from simpliscribe.benchmark import score_case | |
| import pandas as pd | |
| import pytest | |
| def test_score_case_counts_matching_fields(): | |
| case = { | |
| "id": "case-1", | |
| "label": "Simple case", | |
| "raw_text": "Paracetamol 650 tab od 5 days", | |
| "expected_medications": [ | |
| { | |
| "name": "Paracetamol", | |
| "type": "Tablet", | |
| "dosage": "650 mg", | |
| "frequency": "once daily", | |
| "duration": "5 days", | |
| } | |
| ], | |
| } | |
| actual = [ | |
| { | |
| "name": "Paracetamol", | |
| "type": "Tablet", | |
| "dosage": "650 mg", | |
| "frequency": "once daily", | |
| "duration": "5 days", | |
| } | |
| ] | |
| result = score_case(case, actual) | |
| assert result.matched_fields == 5 | |
| assert result.total_fields == 5 | |
| assert result.score == 1.0 | |
| def test_run_benchmark_returns_summary(tmp_path): | |
| cases_path = tmp_path / "cases.json" | |
| cases_path.write_text( | |
| """ | |
| [ | |
| { | |
| "id": "case-1", | |
| "label": "Simple once-daily tablet", | |
| "raw_text": "Paracetamol 650 tab od 5 days", | |
| "expected_medications": [ | |
| { | |
| "name": "Paracetamol", | |
| "type": "Tablet", | |
| "dosage": "650 mg", | |
| "frequency": "once daily", | |
| "duration": "5 days" | |
| } | |
| ] | |
| } | |
| ] | |
| """.strip(), | |
| encoding="utf-8", | |
| ) | |
| cases = load_cases(cases_path) | |
| result = run_benchmark(cases) | |
| assert result["case_count"] == 1 | |
| assert result["success_count"] == 1 | |
| assert result["failure_count"] == 0 | |
| assert result["cases"][0]["label"] == "Simple once-daily tablet" | |
| assert result["cases"][0]["actual"][0]["frequency"] == "once daily" | |
| def test_run_benchmark_preserves_capsule_and_bedtime_in_mixed_case(tmp_path): | |
| cases_path = tmp_path / "cases.json" | |
| cases_path.write_text( | |
| """ | |
| [ | |
| { | |
| "id": "case-2", | |
| "label": "Two-line mixed medicines", | |
| "raw_text": "Amoxycillin 500 cap bd 5 days\\nCetirizine 10 tab hs 3 days", | |
| "expected_medications": [ | |
| { | |
| "name": "Amoxycillin", | |
| "type": "Capsule", | |
| "dosage": "500 mg", | |
| "frequency": "twice daily", | |
| "duration": "5 days" | |
| }, | |
| { | |
| "name": "Cetirizine", | |
| "type": "Tablet", | |
| "dosage": "10 mg", | |
| "frequency": "at bedtime", | |
| "duration": "3 days" | |
| } | |
| ] | |
| } | |
| ] | |
| """.strip(), | |
| encoding="utf-8", | |
| ) | |
| cases = load_cases(cases_path) | |
| result = run_benchmark(cases) | |
| assert result["average_score"] == 1.0 | |
| assert result["cases"][0]["actual"][0]["type"] == "Capsule" | |
| assert result["cases"][0]["actual"][1]["frequency"] == "at bedtime" | |
| def test_run_case_returns_failed_score_when_inference_errors(monkeypatch): | |
| case = { | |
| "id": "case-error", | |
| "label": "Broken case", | |
| "raw_text": "Broken OCR text", | |
| "expected_medications": [ | |
| {"name": "Paracetamol", "type": "Tablet", "dosage": "650 mg", "frequency": "once daily", "duration": "5 days"} | |
| ], | |
| } | |
| def fake_structure_medications(_: str): | |
| raise RuntimeError("Synthetic benchmark failure") | |
| monkeypatch.setattr("simpliscribe.benchmark.structure_medications", fake_structure_medications) | |
| result = run_case(case, retries=0) | |
| assert result.score == 0.0 | |
| assert result.error == "Synthetic benchmark failure" | |
| def test_run_case_supports_file_based_ocr(monkeypatch, tmp_path): | |
| image_path = tmp_path / "sample.png" | |
| image_path.write_bytes(b"mock-image") | |
| case = { | |
| "id": "case-file", | |
| "label": "File-based case", | |
| "file_path": "sample.png", | |
| "expected_medications": [ | |
| { | |
| "name": "Paracetamol", | |
| "type": "Tablet", | |
| "dosage": "650 mg", | |
| "frequency": "once daily", | |
| "duration": "5 days", | |
| } | |
| ], | |
| } | |
| monkeypatch.setattr("simpliscribe.benchmark.extract_ocr_text", lambda path: "Paracetamol 650 tab od 5 days") | |
| result = run_case(case, base_dir=tmp_path, retries=0) | |
| assert result.source_kind == "file" | |
| assert result.source_path == "sample.png" | |
| assert result.raw_text == "Paracetamol 650 tab od 5 days" | |
| assert result.actual[0]["frequency"] == "once daily" | |
| def test_load_cases_supports_parquet_ground_truth(tmp_path): | |
| parquet_path = tmp_path / "synthetic.parquet" | |
| dataframe = pd.DataFrame( | |
| [ | |
| { | |
| "ground_truth": "<s_ocr> doctor_name: Dr. A medications: - Paracetamol 650 mg - Take once daily - Cetirizine 10 mg - At bedtime signature: Dr. A </s>" | |
| } | |
| ] | |
| ) | |
| dataframe.to_parquet(parquet_path) | |
| cases = load_cases(parquet_path) | |
| assert len(cases) == 1 | |
| assert cases[0]["raw_text"] == "Paracetamol 650 mg Take once daily\nCetirizine 10 mg At bedtime" | |
| assert cases[0]["expected_medications"][0]["name"] == "Paracetamol" | |
| assert cases[0]["expected_medications"][0]["dosage"] == "650 mg" | |
| assert cases[0]["expected_medications"][0]["frequency"] == "once daily" | |
| assert cases[0]["expected_medications"][1]["frequency"] == "at bedtime" | |
| def test_load_cases_supports_parquet_ground_truth_without_signature(tmp_path): | |
| parquet_path = tmp_path / "synthetic_no_signature.parquet" | |
| dataframe = pd.DataFrame( | |
| [ | |
| { | |
| "ground_truth": "<s_ocr> doctor_name: Dr. A medications: - Paracetamol 650 mg - Take once daily </s>" | |
| } | |
| ] | |
| ) | |
| dataframe.to_parquet(parquet_path) | |
| cases = load_cases(parquet_path) | |
| assert len(cases) == 1 | |
| assert cases[0]["expected_medications"] == [ | |
| { | |
| "name": "Paracetamol", | |
| "type": "", | |
| "dosage": "650 mg", | |
| "frequency": "once daily", | |
| "duration": "N/A", | |
| } | |
| ] | |
| def test_load_cases_preserves_hyphenated_medication_names(tmp_path): | |
| parquet_path = tmp_path / "synthetic_hyphenated.parquet" | |
| dataframe = pd.DataFrame( | |
| [ | |
| { | |
| "ground_truth": "<s_ocr> medications: - Co-trimoxazole 500/125 mg - Take twice daily signature: Dr. A </s>" | |
| } | |
| ] | |
| ) | |
| dataframe.to_parquet(parquet_path) | |
| cases = load_cases(parquet_path) | |
| assert len(cases) == 1 | |
| assert cases[0]["expected_medications"][0]["name"] == "Co-Trimoxazole" | |
| assert cases[0]["expected_medications"][0]["dosage"] == "500/125 mg" | |
| assert cases[0]["expected_medications"][0]["frequency"] == "twice daily" | |
| def test_load_cases_supports_complex_dosage_formats(tmp_path): | |
| parquet_path = tmp_path / "synthetic_complex_dosage.parquet" | |
| dataframe = pd.DataFrame( | |
| [ | |
| { | |
| "ground_truth": "<s_ocr> medications: - Ambroxol 5 mg/5 ml - As directed signature: Dr. A </s>" | |
| } | |
| ] | |
| ) | |
| dataframe.to_parquet(parquet_path) | |
| cases = load_cases(parquet_path) | |
| assert len(cases) == 1 | |
| assert cases[0]["expected_medications"][0]["name"] == "Ambroxol" | |
| assert cases[0]["expected_medications"][0]["dosage"] == "5 mg/5 ml" | |
| assert cases[0]["expected_medications"][0]["frequency"] == "Refer to prescription" | |
| def test_score_case_penalizes_extra_medications(): | |
| expected = { | |
| "name": "Cetirizine", | |
| "type": "Tablet", | |
| "dosage": "10 mg", | |
| "frequency": "once daily", | |
| "duration": "5 days", | |
| } | |
| extra = {**expected, "name": "Paracetamol"} | |
| result = score_case({"id": "extra-drug", "expected_medications": [expected]}, [expected, extra]) | |
| assert result.matched_fields == 5 | |
| assert result.total_fields == 10 | |
| assert result.score == 0.5 | |
| assert result.actual_count == 2 | |
| def test_score_case_does_not_score_unspecified_expected_fields(): | |
| case = {"id": "name-only", "expected_medications": [{"name": "Cetirizine"}]} | |
| actual = [{ | |
| "name": "Cetirizine", | |
| "type": "Tablet", | |
| "dosage": "10 mg", | |
| "frequency": "once daily", | |
| "duration": "5 days", | |
| }] | |
| result = score_case(case, actual) | |
| assert result.matched_fields == 1 | |
| assert result.total_fields == 1 | |
| assert result.score == 1.0 | |
| def test_score_case_matches_medications_independent_of_order(): | |
| cetirizine = {"name": "Cetirizine", "dosage": "10 mg", "frequency": "at bedtime"} | |
| paracetamol = {"name": "Paracetamol", "dosage": "650 mg", "frequency": "once daily"} | |
| result = score_case( | |
| {"id": "reordered", "expected_medications": [cetirizine, paracetamol]}, | |
| [paracetamol, cetirizine], | |
| ) | |
| assert result.matched_fields == 6 | |
| assert result.total_fields == 6 | |
| assert result.score == 1.0 | |
| assert result.field_results[0]["actual_medication_index"] == 1 | |
| def test_load_versioned_golden_bundle(tmp_path): | |
| path = tmp_path / "golden.json" | |
| path.write_text( | |
| '{"schema_version":"1.0","dataset":"test-set","cases":[' | |
| '{"id":"case-1","raw_text":"Example","expected_medications":[]}]}', | |
| encoding="utf-8", | |
| ) | |
| metadata, cases = load_case_bundle(path) | |
| assert metadata["schema_version"] == "1.0" | |
| assert metadata["dataset"] == "test-set" | |
| assert cases[0]["id"] == "case-1" | |
| def test_load_versioned_golden_bundle_rejects_duplicate_ids(tmp_path): | |
| path = tmp_path / "golden.json" | |
| path.write_text( | |
| '{"schema_version":"1.0","cases":[' | |
| '{"id":"duplicate","expected_medications":[]},' | |
| '{"id":"duplicate","expected_medications":[]}]}', | |
| encoding="utf-8", | |
| ) | |
| with pytest.raises(ValueError, match="Duplicate benchmark case id"): | |
| load_case_bundle(path) | |
| def test_run_benchmark_reports_detection_field_and_safety_metrics(monkeypatch): | |
| cases = [ | |
| { | |
| "id": "positive", | |
| "raw_text": "positive", | |
| "expected_medications": [{"name": "A", "dosage": "10 mg", "requires_review": True}], | |
| }, | |
| {"id": "negative", "raw_text": "negative", "expected_medications": []}, | |
| {"id": "unreadable", "raw_text": "unreadable", "expected_rejection": True, "expected_medications": []}, | |
| ] | |
| outputs = { | |
| "positive": {"medications": [{"name": "A", "dosage": "10 mg", "requires_review": True}]}, | |
| "negative": {"medications": [{"name": "Hallucination"}]}, | |
| "unreadable": {"medications": []}, | |
| } | |
| monkeypatch.setattr("simpliscribe.benchmark.structure_medications", lambda text: outputs[text]) | |
| result = run_benchmark(cases) | |
| detection = result["metrics"]["medication_detection"] | |
| assert detection == { | |
| "true_positives": 1, | |
| "false_positives": 1, | |
| "false_negatives": 0, | |
| "precision": 0.5, | |
| "recall": 1.0, | |
| "f1": 0.6667, | |
| "hallucination_rate": 0.5, | |
| } | |
| assert result["metrics"]["field_accuracy"]["dosage"]["accuracy"] == 1.0 | |
| assert result["metrics"]["review_flag_recall"] == 1.0 | |
| assert result["metrics"]["unreadable_rejection_rate"] == 1.0 | |
| assert evaluate_quality_gates(result, 0.7, 0.4) | |