File size: 4,446 Bytes
9c9268b
 
 
 
 
 
 
3d5e588
 
 
 
 
 
 
 
 
 
 
9c9268b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3d5e588
 
 
 
 
9c9268b
 
 
 
 
3d5e588
 
9c9268b
 
 
 
3d5e588
 
 
 
 
9c9268b
 
 
 
3d5e588
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
"""Tests for models.py — pure deterministic functions only."""
from __future__ import annotations

import math

import pytest

from models import PatientProfile, geocode_zip, haversine_miles


@pytest.fixture
def minimal_patient(**kwargs):
    """Minimal PatientProfile factory — callers only specify fields that vary."""
    def _make(**overrides):
        defaults = dict(disease="ALS", age=45, onset_months=6, zip_code="10001", lat=40.75, lon=-73.99)
        defaults.update(overrides)
        return PatientProfile(**defaults)
    return _make


# ---------------------------------------------------------------------------
# haversine_miles
# ---------------------------------------------------------------------------

class TestHaversineMiles:
    def test_same_point_is_zero(self):
        assert haversine_miles(42.0, -71.0, 42.0, -71.0) == 0.0

    def test_known_distance_boston_nyc(self):
        # Boston (42.3601, -71.0589) to NYC (40.7128, -74.0060) ≈ 190 miles
        dist = haversine_miles(42.3601, -71.0589, 40.7128, -74.0060)
        assert 185 < dist < 195

    def test_symmetry(self):
        d1 = haversine_miles(42.0, -71.0, 34.0, -118.0)
        d2 = haversine_miles(34.0, -118.0, 42.0, -71.0)
        assert math.isclose(d1, d2, rel_tol=1e-9)

    def test_short_distance(self):
        # Two points ~1 mile apart
        dist = haversine_miles(42.3370, -71.1061, 42.3514, -71.1061)
        assert 0.8 < dist < 1.2

    def test_returns_float(self):
        result = haversine_miles(0.0, 0.0, 0.0, 1.0)
        assert isinstance(result, float)


# ---------------------------------------------------------------------------
# PatientProfile.summary
# ---------------------------------------------------------------------------

class TestPatientProfileSummary:
    def test_summary_contains_disease(self, als_patient):
        assert "Amyotrophic Lateral Sclerosis" in als_patient.summary()

    def test_summary_contains_age(self, als_patient):
        assert "52" in als_patient.summary()

    def test_summary_contains_onset_months(self, als_patient):
        assert "18" in als_patient.summary()

    def test_summary_contains_diagnosis_months(self, als_patient):
        assert "12" in als_patient.summary()

    def test_summary_contains_zip(self, als_patient):
        assert "02115" in als_patient.summary()

    def test_summary_contains_radius(self, als_patient):
        assert "100" in als_patient.summary()

    def test_summary_contains_benchmarks(self, als_patient):
        text = als_patient.summary()
        assert "alsfrs_r" in text
        assert "38" in text

    def test_summary_no_benchmarks(self, minimal_patient):
        assert "Benchmarks" not in minimal_patient().summary()

    def test_summary_phase_labels(self, minimal_patient):
        text = minimal_patient(phases=["2", "3", "0", "na"]).summary()
        assert "Phase 2" in text
        assert "Phase 3" in text
        assert "Early Phase 1" in text
        assert "Not Applicable" in text

    def test_summary_study_type_defaults(self, minimal_patient):
        text = minimal_patient().summary()
        assert "Clinical trials" in text
        assert "Observational" not in text
        assert "EAP" not in text

    def test_summary_include_observational(self, minimal_patient):
        assert "Observational" in minimal_patient(include_observational=True).summary()

    def test_summary_include_eap(self, minimal_patient):
        assert "EAP" in minimal_patient(include_eap=True).summary()

    def test_summary_returns_string(self, als_patient):
        assert isinstance(als_patient.summary(), str)

    def test_summary_no_phases(self, minimal_patient):
        assert "Phases:" not in minimal_patient().summary()


# ---------------------------------------------------------------------------
# geocode_zip — httpx mocked
# ---------------------------------------------------------------------------

class TestGeocodeZip:
    def test_returns_lat_lon_from_response(self, httpx_mock):
        httpx_mock.add_response(json=[{"lat": "42.3370", "lon": "-71.1061"}])
        lat, lon = geocode_zip("02115", "US")
        assert lat == pytest.approx(42.3370)
        assert lon == pytest.approx(-71.1061)

    def test_empty_results_raises_value_error(self, httpx_mock):
        httpx_mock.add_response(json=[])
        with pytest.raises(ValueError, match="Cannot geocode"):
            geocode_zip("00000", "US")