Skip to content

Commit c85b51d

Browse files
authored
Make philosophy graph layers semantic (#120)
1 parent 7267bfd commit c85b51d

4 files changed

Lines changed: 121 additions & 7 deletions

File tree

ToS/derived-exports/philosophy_graph_projection.min.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

ToS/derived-exports/tos_corpus_index.min.json

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

scripts/philosophy_graph_projection_common.py

Lines changed: 102 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,62 @@
2727
"scripts/validate_philosophy_graph_projection.py",
2828
"tests/test_philosophy_graph_projection.py",
2929
)
30+
PREDICATE_GRAPH_LAYERS = {
31+
"belongs_to_genre": {"conceptual-relation"},
32+
"canonized_by": {"canonical-relation", "transmission-relation"},
33+
"commented_by": {"transmission-relation"},
34+
"contested_by": {"conceptual-relation", "evidence-relation"},
35+
"contains_dossier": {"source-relation"},
36+
"contains_row": {"source-relation"},
37+
"contains_table": {"source-relation"},
38+
"contains_view": {"source-relation"},
39+
"develops_concept": {"conceptual-relation"},
40+
"figure_anchor": {"historical-relation", "source-relation"},
41+
"fragments_preserved_by": {"evidence-relation", "transmission-relation"},
42+
"has_atlas": {"source-relation"},
43+
"has_candidate_node": {"candidate-relation"},
44+
"has_node_type_pressure": {"candidate-relation", "evidence-relation"},
45+
"has_prepared_dossier": {"evidence-relation", "source-relation"},
46+
"has_relation_pressure": {"candidate-relation", "evidence-relation"},
47+
"has_section": {"source-relation"},
48+
"has_view_section": {"source-relation"},
49+
"influences": {"conceptual-relation", "historical-relation"},
50+
"institutionalized_in": {"historical-relation"},
51+
"polemicizes_with": {"conceptual-relation", "evidence-relation"},
52+
"preserved_in": {"evidence-relation", "transmission-relation"},
53+
"preserves_in": {"evidence-relation", "transmission-relation"},
54+
"receives_from": {"transmission-relation"},
55+
"survives_as": {"evidence-relation", "historical-relation", "transmission-relation"},
56+
"translated_into": {"transmission-relation"},
57+
"transforms_concept": {"conceptual-relation"},
58+
"transmits_to": {"transmission-relation"},
59+
"uncertain_relation": {"evidence-relation"},
60+
"uses_language": {"source-relation"},
61+
"uses_script": {"source-relation"},
62+
}
63+
NODE_TYPE_GRAPH_LAYERS = {
64+
"atlas": {"source-relation"},
65+
"atlas-node-type": {"source-relation"},
66+
"atlas-relation-kind": {"source-relation"},
67+
"atlas-section": {"source-relation"},
68+
"candidate-endpoint": {"candidate-relation", "evidence-relation"},
69+
"candidate-node": {"candidate-relation"},
70+
"domain-root": {"source-relation"},
71+
"graph-view": {"source-relation"},
72+
"master-table": {"source-relation"},
73+
"master-table-row": {"historical-relation", "source-relation"},
74+
"prepared-dossier": {"evidence-relation", "source-relation"},
75+
"view-section": {"source-relation"},
76+
}
77+
KNOWN_GRAPH_LAYERS = {
78+
"source-relation",
79+
"historical-relation",
80+
"conceptual-relation",
81+
"transmission-relation",
82+
"evidence-relation",
83+
"candidate-relation",
84+
"canonical-relation",
85+
}
3086

3187

3288
def repo_ref(path: Path) -> str:
@@ -59,6 +115,35 @@ def _as_string_set(values: Any) -> set[str]:
59115
return {str(item) for item in values if isinstance(item, str) and item}
60116

61117

118+
def _node_semantic_layers(node: dict[str, Any]) -> set[str]:
119+
layers = set(NODE_TYPE_GRAPH_LAYERS.get(str(node.get("node_type") or ""), {"source-relation"}))
120+
properties = node.get("properties")
121+
if isinstance(properties, dict):
122+
if str(properties.get("canon_status") or "") == "pre-canon":
123+
layers.add("candidate-relation")
124+
if str(properties.get("authority_posture") or "").endswith("_candidate"):
125+
layers.add("candidate-relation")
126+
if str(properties.get("priority") or "").strip():
127+
layers.add("evidence-relation")
128+
return layers & KNOWN_GRAPH_LAYERS
129+
130+
131+
def _edge_semantic_layers(edge: dict[str, Any]) -> set[str]:
132+
predicate = str(edge.get("predicate_id") or "")
133+
layers = set(PREDICATE_GRAPH_LAYERS.get(predicate, {"source-relation"}))
134+
properties = edge.get("properties")
135+
if isinstance(properties, dict):
136+
if str(properties.get("canon_status") or "") == "pre-canon":
137+
layers.add("candidate-relation")
138+
if str(properties.get("authority_posture") or "").endswith("_candidate"):
139+
layers.add("candidate-relation")
140+
if str(properties.get("endpoint_resolution") or "") == "unresolved":
141+
layers.add("evidence-relation")
142+
if str(properties.get("confidence") or "") in {"низкий", "low"}:
143+
layers.add("evidence-relation")
144+
return layers & KNOWN_GRAPH_LAYERS
145+
146+
62147
def _node_matches_filters(node: dict[str, Any], filters: dict[str, Any]) -> bool:
63148
node_type = str(node.get("node_type") or "")
64149
label = str(node.get("label") or "")
@@ -704,16 +789,29 @@ def build_payload() -> dict[str, Any]:
704789
view_id = str(view.get("view_id") or "")
705790
graph_layers = _as_string_set(view.get("graph_layers"))
706791
view_nodes, view_edges, view_diagnostics = _pick_view_material(view, atlas_nodes_by_id, atlas_edges)
792+
view_node_layers: dict[str, set[str]] = {
793+
str(node["node_id"]): _node_semantic_layers(node)
794+
for node in view_nodes
795+
}
796+
view_edge_layers: dict[str, set[str]] = {}
797+
for edge in view_edges:
798+
edge_id = str(edge["edge_id"])
799+
edge_layers = _edge_semantic_layers(edge)
800+
view_edge_layers[edge_id] = edge_layers
801+
for endpoint_key in ("from_id", "to_id"):
802+
endpoint_id = str(edge.get(endpoint_key) or "")
803+
if endpoint_id in view_node_layers:
804+
view_node_layers[endpoint_id].update(edge_layers)
707805
for node in view_nodes:
708806
node_id = str(node["node_id"])
709807
membership = node_membership.setdefault(node_id, {"view_ids": set(), "graph_layers": set()})
710808
membership["view_ids"].add(view_id)
711-
membership["graph_layers"].update(graph_layers)
809+
membership["graph_layers"].update(view_node_layers.get(node_id) or _node_semantic_layers(node))
712810
for edge in view_edges:
713811
edge_id = str(edge["edge_id"])
714812
membership = edge_membership.setdefault(edge_id, {"view_ids": set(), "graph_layers": set()})
715813
membership["view_ids"].add(view_id)
716-
membership["graph_layers"].update(graph_layers)
814+
membership["graph_layers"].update(view_edge_layers.get(edge_id) or _edge_semantic_layers(edge))
717815
diagnostics.extend(view_diagnostics)
718816
views.append(
719817
{
@@ -736,15 +834,15 @@ def build_payload() -> dict[str, Any]:
736834
_projection_node(
737835
node,
738836
view_ids={view_id},
739-
graph_layers=graph_layers,
837+
graph_layers=view_node_layers.get(str(node["node_id"])) or _node_semantic_layers(node),
740838
)
741839
for node in view_nodes
742840
],
743841
"edges": [
744842
_projection_edge(
745843
edge,
746844
view_ids={view_id},
747-
graph_layers=graph_layers,
845+
graph_layers=view_edge_layers.get(str(edge["edge_id"])) or _edge_semantic_layers(edge),
748846
)
749847
for edge in view_edges
750848
],

tests/test_philosophy_graph_projection.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,11 +73,27 @@ def test_global_nodes_and_edges_carry_view_and_layer_membership(self) -> None:
7373
self.assertIn("chronology", nodes["atlas-row:A01"]["view_ids"])
7474
self.assertTrue(nodes["atlas-row:A01"]["graph_layers"])
7575
self.assertIn("chronology", edges["edge:row:A01:has-dossier:A01"]["view_ids"])
76-
self.assertIn("historical-relation", edges["edge:row:A01:has-dossier:A01"]["graph_layers"])
76+
self.assertEqual(
77+
set(edges["edge:row:A01:has-dossier:A01"]["graph_layers"]),
78+
{"evidence-relation", "source-relation"},
79+
)
7780
self.assertIn("candidate-node:table-i-a01-node-001", nodes)
81+
self.assertIn("candidate-relation", nodes["candidate-node:table-i-a01-node-001"]["graph_layers"])
7882
self.assertIn("edge:candidate-relation:table-i-a01-relation-001", edges)
7983
self.assertIn("script-decipherment", edges["edge:candidate-relation:table-i-a01-relation-001"]["view_ids"])
8084

85+
def test_layer_counts_are_semantic_not_view_wide(self) -> None:
86+
payload = self.load_projection()
87+
layer_counts = {row["layer_id"]: row for row in payload["layer_counts"]}
88+
self.assertLess(layer_counts["canonical-relation"]["edge_count"], layer_counts["candidate-relation"]["edge_count"])
89+
self.assertLess(layer_counts["historical-relation"]["node_count"], layer_counts["evidence-relation"]["node_count"])
90+
packets = {packet["view_id"]: packet for packet in payload["review_packets"]}
91+
chronology_layers = {row["layer_id"]: row for row in packets["chronology"]["layer_counts"]}
92+
self.assertNotEqual(
93+
chronology_layers["evidence-relation"]["node_count"],
94+
chronology_layers["historical-relation"]["node_count"],
95+
)
96+
8197
def test_clusters_preserve_membership_and_source_refs(self) -> None:
8298
payload = self.load_projection()
8399
node_ids = {node["node_id"] for node in payload["nodes"]}

0 commit comments

Comments
 (0)