File size: 2,851 Bytes
c893230
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Tests for notes-coverage guard.

These tests pin the deterministic behaviour of the coverage check that
``_generate_section_text`` and ``run_inspector_tool_loop`` use to decide
whether to regenerate. The whole point of the guard is to be reliable
without an LLM, so the assertions are exact thresholds, not loose bounds.
"""

from __future__ import annotations

from app.generator.notes_coverage import (
    coverage_regenerate_hint,
    coverage_report,
    extract_bullet_facts,
)


def test_extract_bullet_facts_drops_stopword_only() -> None:
    facts = extract_bullet_facts(["the and of"])
    assert facts == []


def test_extract_bullet_facts_keeps_factish_tokens() -> None:
    facts = extract_bullet_facts(["damp at rear elevation; postcode SW1A 1AA"])
    assert len(facts) == 1
    keys = facts[0].key_tokens
    assert "damp" in keys
    assert "rear" in keys
    assert "elevation" in keys
    assert "SW1A1AA" in keys


def test_coverage_report_full_match() -> None:
    bullets = ["roof tiles slipped at south elevation"]
    text = "Roof tiles slipped at the south elevation; replacement recommended."
    rep = coverage_report(bullets, text)
    assert rep.coverage_ratio == 1.0
    assert rep.bullets_poorly_covered == 0
    assert not rep.needs_regenerate


def test_coverage_report_detects_dropped_facts() -> None:
    bullets = [
        "boiler 22 years old, no service record",
        "consumer unit lacks RCD protection on lighting circuits",
        "rising damp at rear hallway",
    ]
    text = "The property is generally well maintained."
    rep = coverage_report(bullets, text)
    assert rep.bullets_total == 3
    assert rep.bullets_poorly_covered == 3
    assert rep.coverage_ratio < 0.3
    assert rep.needs_regenerate
    hint = coverage_regenerate_hint(rep)
    assert "COVERAGE VIOLATION" in hint
    assert "boiler" in hint or "rcd" in hint.lower() or "damp" in hint.lower()


def test_coverage_report_stemming_does_not_misflag() -> None:
    """Bullet says 'cracks' but text says 'cracking' — should still cover."""
    bullets = ["cracks to rear chimney stack"]
    text = "Cracking observed to the rear chimney stack."
    rep = coverage_report(bullets, text)
    assert rep.coverage_ratio >= 0.7


def test_coverage_report_empty_bullets_safe() -> None:
    rep = coverage_report([], "any text")
    assert rep.bullets_total == 0
    assert rep.coverage_ratio == 1.0
    assert not rep.needs_regenerate


def test_coverage_regenerate_hint_lists_missing_tokens() -> None:
    bullets = ["smoke alarm missing in hallway", "lead paint on window frames"]
    text = "General property condition fair."
    rep = coverage_report(bullets, text)
    hint = coverage_regenerate_hint(rep)
    assert "smoke" in hint.lower() or "alarm" in hint.lower()
    assert "lead" in hint.lower() or "paint" in hint.lower()