diff --git a/backend/app/services/knowledge/ragcore.py b/backend/app/services/knowledge/ragcore.py index 2cd885c..a24e478 100644 --- a/backend/app/services/knowledge/ragcore.py +++ b/backend/app/services/knowledge/ragcore.py @@ -53,15 +53,15 @@ def _deduplicate_sources(sources: list[SourceCard]) -> list[SourceCard]: RAGcore document/version UUIDs change across uploads, so they are not useful deduplication keys. Human-visible citation identity is the normalized title, - section and excerpt. + section. Chunks from the same unsectioned document collapse into one card; distinct + named sections remain independently citable. """ - seen: set[tuple[str, str, str]] = set() + seen: set[tuple[str, str]] = set() unique: list[SourceCard] = [] for source in sources: key = ( source.title.strip().casefold(), source.section.strip().casefold(), - " ".join(source.excerpt.split()).casefold(), ) if key in seen: continue diff --git a/backend/tests/test_knowledge.py b/backend/tests/test_knowledge.py index 944920f..2a987dc 100644 --- a/backend/tests/test_knowledge.py +++ b/backend/tests/test_knowledge.py @@ -297,7 +297,7 @@ def test_ragcore_sources_deduplicate_reuploaded_versions_and_cap_cards(monkeypat "title": "Damage procedure" if index < 2 else f"Procedure {index}", "section": "Return", "excerpt": ( - "Record visible damage before release." + f"Record visible damage before release, chunk {index}." if index < 2 else f"Unique procedure evidence {index}." ), diff --git a/frontend/e2e/guided-demo-full.spec.ts b/frontend/e2e/guided-demo-full.spec.ts index 7123d0b..8489f25 100644 --- a/frontend/e2e/guided-demo-full.spec.ts +++ b/frontend/e2e/guided-demo-full.spec.ts @@ -53,10 +53,10 @@ test("full guided demo walkthrough, start to finish, restoring the environment a await test.step("step 4: handle the newly created data-quality issue", async () => { await expect(page).toHaveURL(/\/data-quality$/); await expect(page.getByRole("heading", { name: "4. Bekijk en behandel" })).toBeVisible(); - const firstIssueLink = page.locator(".data-table tbody tr").first().locator("a"); - await firstIssueLink.click(); + const odometerIssue = page.getByRole("row", { name: /Afwijkende kilometerstand/ }); + await odometerIssue.locator("a").click(); await expect(page.getByText("Wat is er mis")).toBeVisible(); - // The newest issue is the odometer regression this return just created. + // SLA ordering deliberately outranks recency, so select the issue by its rule. await page.getByRole("radio", { name: /Laatst bevestigde stand behouden/ }).check(); await page.getByRole("button", { name: "Probleem oplossen" }).click(); await expect(page.getByText(/Probleem .* opgelost/)).toBeVisible(); diff --git a/frontend/e2e/interactive-elements.spec.ts b/frontend/e2e/interactive-elements.spec.ts index f90eb8e..1542d21 100644 --- a/frontend/e2e/interactive-elements.spec.ts +++ b/frontend/e2e/interactive-elements.spec.ts @@ -60,8 +60,10 @@ test("vehicles page: status filter and attention-only checkbox both work", async await expect(page).toHaveURL(/attention_only=true/); await expect(page.getByLabel("Attention only")).toBeChecked(); await expect(page.locator(".data-table tbody tr").first()).toBeVisible(); - const attentionCells = await page.locator(".data-table tbody tr td:nth-child(6)").allTextContents(); - expect(attentionCells.every((c) => c.includes("Needs attention"))).toBeTruthy(); + const rows = page.locator(".data-table tbody tr"); + await expect(rows.locator(".attention-flag")).toHaveCount(await rows.count()); + const attentionReasons = await rows.locator(".attention-flag").allTextContents(); + expect(attentionReasons.every((reason) => reason.trim().length > 0)).toBeTruthy(); }); test("vehicles page: free-text search actually filters the rendered rows", async ({ page }) => {