""" Transform IBM Bob architecture analysis into React Flow graph data. """ from __future__ import annotations NODE_COLORS = { "frontend": "#3B82F6", "backend": "#8B5CF6", "database": "#10B981", "api": "#F59E0B", "middleware": "#6366F1", "utility": "#64748B", "auth": "#EF4444", "payment": "#F97316", "notification": "#06B6D4", "code": "#84CC16", "service": "#EC4899", "queue": "#F97316", "cloud": "#14B8A6", } def build_workflow_from_analysis(analysis: dict) -> dict: """Convert architecture analysis into workflow nodes and edges.""" workflows = _ensure_list(analysis.get("business_workflows", [])) if not workflows: return {"nodes": [], "edges": []} # Take the first workflow (unified project workflow) workflow = workflows[0] if workflows else {} steps = _ensure_list(workflow.get("steps", [])) services_involved = _ensure_list(workflow.get("services_involved", [])) nodes = [] edges = [] # Create nodes from workflow steps for idx, step in enumerate(steps): step_text = str(step) # Infer node type from step text node_type = "process" color = "#3B82F6" if any(word in step_text.lower() for word in ["start", "trigger", "initiate", "begin"]): node_type = "start" color = "#10B981" elif any(word in step_text.lower() for word in ["check", "validate", "verify", "decision"]): node_type = "decision" color = "#F59E0B" elif any(word in step_text.lower() for word in ["database", "store", "save", "persist"]): node_type = "database" color = "#10B981" elif any(word in step_text.lower() for word in ["api", "request", "call", "fetch"]): node_type = "api" color = "#06B6D4" elif any(word in step_text.lower() for word in ["auth", "login", "authenticate"]): node_type = "auth" color = "#EF4444" elif any(word in step_text.lower() for word in ["notify", "alert", "email", "message"]): node_type = "notification" color = "#8B5CF6" nodes.append({ "id": f"workflow_step_{idx}", "type": "workflowNode", "position": { "x": 150 + (idx % 3) * 280, "y": 100 + (idx // 3) * 180 }, "data": { "label": step_text[:40], # Truncate long labels "nodeType": node_type, "color": color, "description": step_text, } }) # Create edge to next step if idx < len(steps) - 1: edges.append({ "id": f"workflow_edge_{idx}", "source": f"workflow_step_{idx}", "target": f"workflow_step_{idx + 1}", "type": "workflowEdge", "animated": True, }) return { "nodes": nodes, "edges": edges, "workflow_name": workflow.get("name", "Project Workflow"), } def _ensure_list(value): if value is None: return [] if isinstance(value, list): return value if isinstance(value, tuple) or isinstance(value, set): return list(value) return [value] def build_react_flow_graph(analysis: dict) -> dict: """Convert architecture analysis to React Flow nodes and edges with improved layout.""" nodes = [] edges = [] services = _ensure_list(analysis.get("core_services", [])) dependencies = _ensure_list(analysis.get("critical_dependencies", [])) workflows = _ensure_list(analysis.get("business_workflows", [])) layer_order = ["frontend", "api", "middleware", "backend", "database", "auth", "utility", "notification"] group_order = ["pages", "ui_domain", "client_core", "api_router", "backend_module", "ai", "backend_core", ""] buckets: dict[tuple[str, str], list[dict]] = {} for service in services: layer_type = service.get("type", "utility") group = service.get("group") or "" buckets.setdefault((layer_type, group), []).append(service) y_offset = 80 layer_spacing = 240 node_spacing = 220 for layer_type in layer_order: layer_groups = [group for (layer, group) in buckets if layer == layer_type] ordered_groups = [group for group in group_order if group in layer_groups] ordered_groups.extend([group for group in layer_groups if group not in ordered_groups]) for group in ordered_groups: layer_services = buckets.get((layer_type, group), []) if not layer_services: continue total_width = len(layer_services) * node_spacing x_start = max(80, (1280 - total_width) // 2) for idx, service in enumerate(layer_services): service_id = service.get("id") or f"service_{len(nodes)}" nodes.append( { "id": service_id, "type": "serviceNode", "position": {"x": x_start + (idx * node_spacing), "y": y_offset}, "data": { "label": service.get("name", service_id), "type": layer_type, "description": service.get("description", ""), "critical": service.get("critical", False), "files": [str(file) for file in _ensure_list(service.get("files", [])) if file], "color": NODE_COLORS.get(layer_type, "#64748B"), "group": group, }, } ) y_offset += layer_spacing # Build edges with better styling known_ids = {node["id"] for node in nodes} for index, dep in enumerate(dependencies): source = dep.get("from") target = dep.get("to") if source not in known_ids or target not in known_ids: continue edge_type = dep.get("type", "imports") edges.append({ "id": f"e{index}", "source": source, "target": target, "type": "default", "label": dep.get("description", edge_type)[:30], # Truncate long labels "animated": edge_type in {"api_call", "event"}, "style": { "stroke": "#6366F1" if edge_type == "api_call" else "#64748B", "strokeWidth": 2 if dep.get("critical") else 1.5, }, "data": {"edgeType": edge_type}, }) return { "nodes": nodes, "edges": edges, "workflows": workflows, "mermaid_diagram": analysis.get("mermaid_diagram", ""), "metadata": { "architecture_type": analysis.get("architecture_type", "Unknown"), "summary": analysis.get("summary", ""), "entry_points": analysis.get("entry_points", []), "tech_decisions": analysis.get("tech_decisions", []), "mermaid_diagram": analysis.get("mermaid_diagram", ""), }, }