diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 37c10f5b..032659fc 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -322,7 +322,8 @@ Keyman sides are labeled from `common_lookup_value` (`Our side`, `Plant`, `Company`) so the popup never shows raw `our_side` / `plant` codes when a label exists. Related-node person chips use the same side label (`Ada West (Our side)`), not the ontology class -(`Ada West (Person)`). +(`Ada West (Person)`). Related-node organization chips use the +entity-level label (`Demo Corp (Company)`), not `Organization`. `GET /api/posts` and `GET /api/posts/{post_id}` include `voc_type_label` / `visibility_label` from `common_lookup_value` so diff --git a/CHANGELOG.md b/CHANGELOG.md index 67e87e9b..9d23cbdf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [0.73.0] - 2026-08-16 + +### Changed + +- Related-node organization chips use the `entity_level` lookup + label instead of the ontology class. After `make seed`, walking + from Ada West shows "Demo Corp (Company)" -- not "Demo Corp + (Organization)". The payload now carries `entity_level_label` + from `common_lookup_value`. Missing lookups fall back to the + code. The same caption is the button accessible name. + ## [0.72.0] - 2026-08-16 ### Changed diff --git a/backend/app/knowledge_graph.py b/backend/app/knowledge_graph.py index 6b7fc60d..32a3b1b2 100644 --- a/backend/app/knowledge_graph.py +++ b/backend/app/knowledge_graph.py @@ -315,7 +315,8 @@ async def hydrate_related_nodes( corps = { str(row["corporate_entity_id"]): row for row in await conn.fetch( - "select corporate_entity_id, entity_name from corporate_entity where corporate_entity_id = any($1::uuid[])", + "select corporate_entity_id, entity_name, entity_level_code " + "from corporate_entity where corporate_entity_id = any($1::uuid[])", corp_ids, ) } if corp_ids else {} @@ -323,6 +324,9 @@ async def hydrate_related_nodes( side_labels = await labels_for_codes( conn, [row["person_side_code"] for row in people.values()] ) + level_labels = await labels_for_codes( + conn, [row["entity_level_code"] for row in corps.values()] + ) payload: list[dict[str, Any]] = [] for node_type_code, node_id, score in parsed: @@ -340,7 +344,10 @@ async def hydrate_related_nodes( elif node_type_code == NODE_POST and node_id in posts: item["label"] = posts[node_id]["post_title"] elif node_type_code == NODE_CORPORATE_ENTITY and node_id in corps: + level = corps[node_id]["entity_level_code"] item["label"] = corps[node_id]["entity_name"] + item["entity_level_code"] = level + item["entity_level_label"] = level_labels.get(level, level) else: continue payload.append(item) diff --git a/backend/tests/test_api.py b/backend/tests/test_api.py index 1abca6b4..00cf78b4 100644 --- a/backend/tests/test_api.py +++ b/backend/tests/test_api.py @@ -887,6 +887,15 @@ def test_related_keymen_use_rwr_and_hide_invisible_posts(client, demo_analyst_to assert counterpart["person_side_label"] == "Counterparty" own_post = by_id[seeded_db["own_private_post_id"]] assert own_post["ontology_label"] == "Post" + corp_nodes = [ + node for node in body["related"] if node["node_type_code"] == "node_corporate_entity" + ] + assert corp_nodes + assert all(node.get("entity_level_label") for node in corp_nodes) + if seeded_db["own_corp_id"] in related_ids: + own_corp = by_id[seeded_db["own_corp_id"]] + assert own_corp["entity_level_code"] == "company" + assert own_corp["entity_level_label"] == "Company" def test_related_corporate_entity_uses_rwr_and_hides_invisible_posts( diff --git a/frontend/package.json b/frontend/package.json index 0f1fa4ce..4bd6a4c6 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,7 +1,7 @@ { "name": "frontend", "private": true, - "version": "0.72.0", + "version": "0.73.0", "type": "module", "scripts": { "dev": "vite", diff --git a/frontend/src/App.test.tsx b/frontend/src/App.test.tsx index 72e7c72f..2816927e 100644 --- a/frontend/src/App.test.tsx +++ b/frontend/src/App.test.tsx @@ -553,6 +553,8 @@ describe("App, authenticated", () => { ontology_iri: "https://contextualwisdomlab.github.io/lineageweave/ontology#Organization", ontology_label: "Organization", label: "Demo Corp", + entity_level_code: "company", + entity_level_label: "Company", relevance: 0.2, }, ], @@ -1004,7 +1006,13 @@ describe("App, authenticated", () => { await userEvent.click(await screen.findByRole("button", { name: "View post: Public post" })); await userEvent.click(screen.getByRole("button", { name: "Related nodes for Ada West" })); await waitFor(() => expect(screen.getByText("Related to Ada West")).toBeInTheDocument()); - await userEvent.click(screen.getByRole("button", { name: "Related nodes for Demo Corp" })); + expect( + screen.getByRole("button", { name: "Related nodes for Demo Corp (Company)" }), + ).toBeInTheDocument(); + expect(screen.getByText("Related to Ada West").closest(".related-keymen")).not.toHaveTextContent( + "Demo Corp (Organization)", + ); + await userEvent.click(screen.getByRole("button", { name: "Related nodes for Demo Corp (Company)" })); await waitFor(() => expect(screen.getByText("Related to Demo Corp")).toBeInTheDocument()); expect( screen.getByRole("button", { name: "Related nodes for Ada West (Our side)" }), diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 57f5a518..37316da5 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -466,6 +466,8 @@ function VocEvidenceSection({ } const NODE_PERSON = "node_person"; +const NODE_POST = "node_post"; +const NODE_CORPORATE_ENTITY = "node_corporate_entity"; function relatedNodeCaption(node: RelatedNode): string { const name = node.label ?? node.node_id; @@ -475,12 +477,15 @@ function relatedNodeCaption(node: RelatedNode): string { return `${name} (${side})`; } } + if (node.node_type_code === NODE_CORPORATE_ENTITY) { + const level = node.entity_level_label ?? node.entity_level_code; + if (level) { + return `${name} (${level})`; + } + } return `${name} (${node.ontology_label ?? node.node_type_code})`; } -const NODE_POST = "node_post"; -const NODE_CORPORATE_ENTITY = "node_corporate_entity"; - const VERIFICATION_BADGE: Record = { verify_pending: "Not yet checked", verify_corroborated: "Corroborated", @@ -721,7 +726,7 @@ function KeymanPanel({