timmers's picture
GEMEO world-model — initial release (module + NeuralSurv ckpt + RareBench v49 + KG embeddings)
089d665 verified
"""Visualization payload — formats the patient subgraph for react-force-graph.
The raras-app /grafo page renders nodes/links via react-force-graph-3d.
This module produces the exact JSON shape that component expects, so the
front-end can drop in `<ForceGraph data={twin.viz_data} />`.
Color map is consistent with raras-app conventions:
Patient #00d4ff
Phenotype #f59e0b
Disease #ef4444
Gene #10b981
Drug #8b5cf6
Lab #6366f1
"""
from __future__ import annotations
from typing import Optional
from .types import VizData, Subgraph
_COLOR = {
"Patient": "#00d4ff",
"Phenotype": "#f59e0b",
"Disease": "#ef4444",
"Gene": "#10b981",
"Drug": "#8b5cf6",
"Lab": "#6366f1",
"Snapshot": "#a78bfa",
}
_GROUP = {
"Patient": 0, "Phenotype": 1, "Disease": 2, "Gene": 3,
"Drug": 4, "Lab": 5, "Snapshot": 6,
}
_VAL = {
"Patient": 30, "Disease": 18, "Gene": 12, "Phenotype": 10,
"Drug": 14, "Lab": 8, "Snapshot": 9,
}
def from_subgraph(sg: Subgraph, *, center_id: Optional[str] = None) -> VizData:
"""Convert a `Subgraph` into a force-graph-friendly payload."""
nodes = []
for n in sg.nodes:
nodes.append({
"id": n.id,
"name": n.name,
"group": _GROUP.get(n.label, 9),
"label": n.label,
"val": _VAL.get(n.label, 8) * (0.5 + n.weight),
"color": _COLOR.get(n.label, "#94a3b8"),
"hpo": n.code if n.label == "Phenotype" else None,
"orpha": n.code if n.label == "Disease" else None,
"symbol": n.code if n.label == "Gene" else None,
"rxcui": n.code if n.label == "Drug" else None,
"weight": n.weight,
**(n.extra or {}),
})
links = []
for e in sg.edges:
links.append({
"source": e.source,
"target": e.target,
"label": e.rel,
"value": max(0.5, e.weight),
"evidence": e.evidence,
})
legend = {label: {"color": color, "group": _GROUP.get(label, 9)} for label, color in _COLOR.items()}
return VizData(
nodes=nodes,
links=links,
center_id=center_id,
legend=legend,
)