import unittest
from evaluation import fixture, assert_disjoint_patients, memorizer_accuracy, expected_counts


class EvaluationTests(unittest.TestCase):
    def test_leaky_fixture(self):
        rows = fixture()
        train, test = rows[::2], rows[1::2]
        self.assertEqual(memorizer_accuracy(train, test), 1)
        with self.assertRaisesRegex(ValueError, "overlap: 40"):
            assert_disjoint_patients(train, test)

    def test_group_fixture(self):
        rows = fixture()
        train, test = rows[:40], rows[40:]
        assert_disjoint_patients(train, test)
        self.assertEqual(memorizer_accuracy(train, test), 0.5)

    def test_independent_counts_oracle(self):
        counts = expected_counts(10000, 0.01, 0.9, 0.95)
        for key, value in [("true_positive", 90), ("false_positive", 495),
                           ("false_negative", 10), ("true_negative", 9405)]:
            self.assertAlmostEqual(counts[key], value)
        self.assertAlmostEqual(counts["precision"], 90 / 585)

    def test_no_predicted_positives(self):
        self.assertIsNone(expected_counts(100, 0, 1, 1)["precision"])

    def test_perfect_classifier(self):
        self.assertEqual(expected_counts(100, 0.1, 1, 1)["precision"], 1)

    def test_accounting(self):
        for prevalence in (0, 0.01, 0.5, 1):
            counts = expected_counts(1000, prevalence, 0.8, 0.9)
            self.assertAlmostEqual(sum(v for k, v in counts.items() if k != "precision"), 1000)

    def test_invalid_values(self):
        for args in [(0, .1, .9, .9), (True, .1, .9, .9), (100, 2, .9, .9),
                     (100, .1, float("nan"), .9), (100, .1, .9, True)]:
            with self.subTest(args=args), self.assertRaises(ValueError):
                expected_counts(*args)
        with self.assertRaises(ValueError):
            memorizer_accuracy([], fixture())


if __name__ == "__main__":
    unittest.main()
