Skip to content

Commit 571658b

Browse files
authored
Create export_json.py
1 parent 19e0406 commit 571658b

1 file changed

Lines changed: 196 additions & 0 deletions

File tree

neuro_registry/export_json.py

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
"""
2+
export_json.py — Export registry snapshot to data/registry.json
3+
---------------------------------------------------------------
4+
Reads the LadybugDB graph and writes a single JSON file that the
5+
GitHub Pages frontend consumes. Runs as the last step in the CI
6+
workflow after every schema ingestion.
7+
8+
Output shape:
9+
{
10+
"generated_at": "2025-...",
11+
"sources": [
12+
{ "label": "schema.org", "version": "1.0.0", "class_count": 38 }
13+
],
14+
"classes": [
15+
{
16+
"uid": "...",
17+
"iri": "https://schema.org/Person",
18+
"uri": "https://registry.sensein.io/obj/Person/v/1.0.0",
19+
"name": "Person",
20+
"definition": "A person...",
21+
"version": "1.0.0",
22+
"abstract": false,
23+
"source": "schema.org",
24+
"properties": [
25+
{
26+
"uid": "...",
27+
"iri": "https://schema.org/name",
28+
"name": "name",
29+
"definition": "...",
30+
"datatype": "xsd:string",
31+
"range_uri": "",
32+
"multivalued": false,
33+
"required": false,
34+
"source": "schema.org"
35+
}
36+
],
37+
"subclass_of": ["https://schema.org/Thing"],
38+
"alignments": [
39+
{
40+
"target_uid": "...",
41+
"target_name": "Investigator",
42+
"target_iri": "https://schema.org/Person",
43+
"target_source": "bbqs",
44+
"distance": 0.0,
45+
"method": "iri"
46+
}
47+
]
48+
}
49+
]
50+
}
51+
52+
Usage:
53+
python export_json.py
54+
python export_json.py --db ./registry.lbug --out data/registry.json
55+
"""
56+
57+
from __future__ import annotations
58+
import datetime, json, os
59+
from pathlib import Path
60+
61+
import click
62+
import ladybug as lb
63+
64+
DB_PATH = "./registry.lbug"
65+
OUT_PATH = "data/registry.json"
66+
67+
68+
def export(conn: lb.Connection) -> dict:
69+
# ---- sources -----------------------------------------------------------
70+
src_result = conn.execute(
71+
"MATCH (s:SchemaSource) RETURN s.uid, s.label, s.version"
72+
)
73+
source_rows = src_result.get_all()
74+
75+
source_class_counts: dict[str, int] = {}
76+
for row in source_rows:
77+
_, label, _ = row
78+
r = conn.execute(
79+
"MATCH (n:SchemaClass {source_label: $src}) RETURN count(n)",
80+
{"src": label}
81+
)
82+
source_class_counts[label] = r.get_next()[0]
83+
84+
sources = [
85+
{
86+
"label": row[1],
87+
"version": row[2] or "1.0.0",
88+
"class_count": source_class_counts.get(row[1], 0),
89+
}
90+
for row in source_rows
91+
]
92+
93+
# ---- classes -----------------------------------------------------------
94+
cls_result = conn.execute("""
95+
MATCH (n:SchemaClass)
96+
RETURN n.uid, n.iri, n.uri, n.name, n.definition,
97+
n.version, n.abstract, n.source_label
98+
ORDER BY n.source_label, n.name
99+
""")
100+
101+
classes = []
102+
for row in cls_result.get_all():
103+
uid, iri, uri, name, definition, version, abstract, source = row
104+
105+
# properties
106+
prop_result = conn.execute("""
107+
MATCH (c:SchemaClass {uid: $uid})-[:HAS_PROPERTY]->(p:SchemaProperty)
108+
RETURN p.uid, p.iri, p.name, p.definition,
109+
p.datatype, p.range_uri, p.multivalued,
110+
p.required, p.source_label
111+
ORDER BY p.name
112+
""", {"uid": uid})
113+
114+
properties = [
115+
{
116+
"uid": r[0],
117+
"iri": r[1] or "",
118+
"name": r[2] or "",
119+
"definition": r[3] or "",
120+
"datatype": r[4] or "",
121+
"range_uri": r[5] or "",
122+
"multivalued": bool(r[6]),
123+
"required": bool(r[7]),
124+
"source": r[8] or "",
125+
}
126+
for r in prop_result.get_all()
127+
]
128+
129+
# subclass_of
130+
sub_result = conn.execute("""
131+
MATCH (c:SchemaClass {uid: $uid})-[:SUBCLASS_OF]->(p:SchemaClass)
132+
RETURN p.iri
133+
""", {"uid": uid})
134+
subclass_of = [r[0] for r in sub_result.get_all() if r[0]]
135+
136+
# alignments
137+
align_result = conn.execute("""
138+
MATCH (c:SchemaClass {uid: $uid})-[a:ALIGNED_TO]->(t:SchemaClass)
139+
RETURN t.uid, t.name, t.iri, t.source_label, a.distance, a.method
140+
ORDER BY a.distance
141+
""", {"uid": uid})
142+
143+
alignments = [
144+
{
145+
"target_uid": r[0],
146+
"target_name": r[1] or "",
147+
"target_iri": r[2] or "",
148+
"target_source": r[3] or "",
149+
"distance": float(r[4]) if r[4] is not None else 1.0,
150+
"method": r[5] or "",
151+
}
152+
for r in align_result.get_all()
153+
]
154+
155+
classes.append({
156+
"uid": uid,
157+
"iri": iri or "",
158+
"uri": uri or "",
159+
"name": name or "",
160+
"definition": definition or "",
161+
"version": version or "1.0.0",
162+
"abstract": bool(abstract),
163+
"source": source or "",
164+
"properties": properties,
165+
"subclass_of": subclass_of,
166+
"alignments": alignments,
167+
})
168+
169+
return {
170+
"generated_at": datetime.datetime.now(datetime.UTC).isoformat(),
171+
"sources": sources,
172+
"classes": classes,
173+
}
174+
175+
176+
@click.command()
177+
@click.option("--db", default=DB_PATH, show_default=True,
178+
help="Path to LadybugDB file.")
179+
@click.option("--out", default=OUT_PATH, show_default=True,
180+
help="Output JSON path.")
181+
def cli(db: str, out: str) -> None:
182+
"""Export registry snapshot to JSON for the GitHub Pages frontend."""
183+
conn = lb.Connection(lb.Database(db))
184+
data = export(conn)
185+
186+
out_path = Path(out)
187+
out_path.parent.mkdir(parents=True, exist_ok=True)
188+
out_path.write_text(json.dumps(data, indent=2))
189+
190+
nc = len(data["classes"])
191+
ns = len(data["sources"])
192+
click.echo(f"Exported {nc} classes from {ns} sources → {out_path}")
193+
194+
195+
if __name__ == "__main__":
196+
cli()

0 commit comments

Comments
 (0)