File size: 4,326 Bytes
0908f70
 
 
 
 
 
 
 
 
 
 
 
 
 
b76f199
0908f70
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
"""HTTP tests for POST /content/similar (library + draft overlap)."""

import pytest

from app.db.models import Document, IngestStatus
from app.models.schemas import SearchResult


class _FakeVS:
    """Vector store returning fixed chunks (no FAISS on disk)."""

    def __init__(self, rows: list[SearchResult]) -> None:
        self._rows = rows

    def search(self, query: str, tenant_id: str, k: int = 10, **kwargs: object) -> list[SearchResult]:
        return self._rows


@pytest.mark.asyncio
async def test_content_similar_returns_library_and_filename(
    async_client: object,
    test_db: object,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    doc_id = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
    tenant = "tenant_test"
    doc = Document(
        id=doc_id,
        tenant_id=tenant,
        filename="rics_guidance.pdf",
        file_path="/tmp/rics_guidance.pdf",
        status=IngestStatus.complete,
    )
    test_db.add(doc)
    await test_db.commit()

    hits = [
        SearchResult(
            chunk_id="chunk-1",
            doc_id=doc_id,
            tenant_id=tenant,
            text="Approved Document guidance on damp proof courses and minimum heights",
            score=0.35,
            section_type="paragraph",
        ),
    ]
    monkeypatch.setattr(
        "app.services.content_similarity.get_vectorstore",
        lambda: _FakeVS(hits),
    )

    res = await async_client.post(
        "/content/similar",
        json={
            "text": "damp proof course minimum height regulations above ground level",
            "section_code": "E4",
            "peer_sections": {},
        },
        headers={"X-Tenant-ID": tenant},
    )
    assert res.status_code == 200
    data = res.json()
    assert len(data["library_matches"]) == 1
    assert data["library_matches"][0]["document_id"] == doc_id
    assert data["library_matches"][0]["filename"] == "rics_guidance.pdf"
    assert "damp" in data["library_matches"][0]["snippet"].lower()


@pytest.mark.asyncio
async def test_content_similar_excludes_document_ids(
    async_client: object,
    test_db: object,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    doc_id = "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb"
    tenant = "tenant_test"
    doc = Document(
        id=doc_id,
        tenant_id=tenant,
        filename="survey.docx",
        file_path="/tmp/survey.docx",
        status=IngestStatus.complete,
    )
    test_db.add(doc)
    await test_db.commit()

    hits = [
        SearchResult(
            chunk_id="c1",
            doc_id=doc_id,
            tenant_id=tenant,
            text="Only chunk from excluded survey file",
            score=0.9,
            section_type="paragraph",
        ),
    ]
    monkeypatch.setattr(
        "app.services.content_similarity.get_vectorstore",
        lambda: _FakeVS(hits),
    )

    res = await async_client.post(
        "/content/similar",
        json={
            "text": "matching text long enough for library search twelve",
            "section_code": "D",
            "peer_sections": {},
            "exclude_document_ids": [doc_id],
        },
        headers={"X-Tenant-ID": tenant},
    )
    assert res.status_code == 200
    data = res.json()
    assert data["library_matches"] == []
    assert "excluded" in data["message"].lower() or "filters" in data["message"].lower()


@pytest.mark.asyncio
async def test_content_similar_draft_overlap_without_library(
    async_client: object,
    monkeypatch: pytest.MonkeyPatch,
) -> None:
    monkeypatch.setattr(
        "app.services.content_similarity.get_vectorstore",
        lambda: _FakeVS([]),
    )

    text = (
        "planning permission required for loft conversion and rear extension "
        "under permitted development rules"
    )
    peer = (
        "planning permission required for loft conversion and rear extension "
        "under permitted development rules 2024 update"
    )

    res = await async_client.post(
        "/content/similar",
        json={
            "text": text,
            "section_code": "E2",
            "peer_sections": {"I1": peer},
        },
        headers={"X-Tenant-ID": "tenant_test"},
    )
    assert res.status_code == 200
    data = res.json()
    assert data["draft_overlaps"]
    assert data["draft_overlaps"][0]["other_section_code"] == "I1"