"""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 ``. 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, )