Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion backend/app/knowledge_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,14 +315,18 @@ 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 {}

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:
Expand All @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions backend/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "frontend",
"private": true,
"version": "0.72.0",
"version": "0.73.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
10 changes: 9 additions & 1 deletion frontend/src/App.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
],
Expand Down Expand Up @@ -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)" }),
Expand Down
13 changes: 9 additions & 4 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<string, string> = {
verify_pending: "Not yet checked",
verify_corroborated: "Corroborated",
Expand Down Expand Up @@ -721,7 +726,7 @@ function KeymanPanel({
<li key={`${node.node_type_code}:${node.node_id}`}>
<button
className="keyman-select"
aria-label={`Related nodes for ${node.label ?? node.node_id}`}
aria-label={`Related nodes for ${caption}`}
onClick={() => handleSelectEntity(node.node_id, node.label ?? node.node_id)}
>
{caption}
Expand Down
2 changes: 2 additions & 0 deletions frontend/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ export interface RelatedNode {
label?: string;
person_side_code?: string;
person_side_label?: string;
entity_level_code?: string;
entity_level_label?: string;
ontology_iri?: string;
ontology_label?: string;
}
Expand Down
2 changes: 1 addition & 1 deletion lineageweave/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,4 @@
"sentence_excerpts",
]

__version__ = "0.72.0"
__version__ = "0.73.0"
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "lineageweave"
version = "0.72.0"
version = "0.73.0"
description = "Reconstructs git-branch-style lineage DAGs from scattered short records using multi-channel score fusion and LLM adjudication."
readme = "README.md"
license = { text = "MIT" }
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.