File size: 5,305 Bytes
c73b6d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import graphviz
import json
from tempfile import NamedTemporaryFile
import os
from graph_generator_utils import add_nodes_and_edges

def generate_synoptic_chart(json_input: str, output_format: str) -> str:
    """
    Generates a synoptic chart (horizontal flowchart) from JSON input.

    Args:
        json_input (str): A JSON string describing the synoptic chart structure.
                          It must follow the Expected JSON Format Example below.

    Expected JSON Format Example:
    {
      "central_node": "AI Project Lifecycle",
      "nodes": [
        {
          "id": "phase1",
          "label": "I. Problem Definition & Data Acquisition",
          "relationship": "Starts with",
          "subnodes": [
            {
              "id": "sub1_1",
              "label": "1. Problem Formulation",
              "relationship": "Involves",
              "subnodes": [
                {"id": "sub1_1_1", "label": "1.1. Identify Business Need", "relationship": "e.g."},
                {"id": "sub1_1_2", "label": "1.2. Define KPIs", "relationship": "e.g."}
              ]
            },
            {
              "id": "sub1_2",
              "label": "2. Data Collection",
              "relationship": "Followed by",
              "subnodes": [
                {"id": "sub1_2_1", "label": "2.1. Source Data", "relationship": "from"},
                {"id": "sub1_2_2", "label": "2.2. Data Cleaning", "relationship": "includes"}
              ]
            }
          ]
        },
        {
          "id": "phase2",
          "label": "II. Model Development",
          "relationship": "Proceeds to",
          "subnodes": [
            {
              "id": "sub2_1",
              "label": "1. Feature Engineering",
              "relationship": "Comprises",
              "subnodes": [
                {"id": "sub2_1_1", "label": "1.1. Feature Selection", "relationship": "e.g."},
                {"id": "sub2_1_2", "label": "1.2. Feature Transformation", "relationship": "e.g."}
              ]
            },
            {
              "id": "sub2_2",
              "label": "2. Model Training",
              "relationship": "Involves",
              "subnodes": [
                {"id": "sub2_2_1", "label": "2.1. Algorithm Selection", "relationship": "uses"},
                {"id": "sub2_2_2", "label": "2.2. Hyperparameter Tuning", "relationship": "optimizes"}
              ]
            }
          ]
        },
        {
          "id": "phase3",
          "label": "III. Evaluation & Deployment",
          "relationship": "Culminates in",
          "subnodes": [
            {
              "id": "sub3_1",
              "label": "1. Model Evaluation",
              "relationship": "Includes",
              "subnodes": [
                {"id": "sub3_1_1", "label": "1.1. Performance Metrics", "relationship": "measures"},
                {"id": "sub3_1_2", "label": "1.2. Bias & Fairness Audits", "relationship": "ensures"}
              ]
            },
            {
              "id": "sub3_2",
              "label": "2. Deployment & Monitoring",
              "relationship": "Requires",
              "subnodes": [
                {"id": "sub3_2_1", "label": "2.1. API/Integration Development", "relationship": "for"},
                {"id": "sub3_2_2", "label": "2.2. Continuous Monitoring", "relationship": "ensures"}
              ]
            }
          ]
        }
      ]
    }

    Returns:
        str: The filepath to the generated PNG image file.
    """
    try:
        if not json_input.strip():
            return "Error: Empty input"
            
        data = json.loads(json_input)
        
        if 'central_node' not in data or 'nodes' not in data:
            raise ValueError("Missing required fields: central_node or nodes")

        dot = graphviz.Digraph(
            name='SynopticChart',
            format='png',
            graph_attr={
                'rankdir': 'LR',        # Left-to-Right layout (horizontal hierarchy)
                'splines': 'ortho',     # Straight lines
                'bgcolor': 'white',     # White background
                'pad': '0.5',           # Padding around the graph
                'ranksep': '0.7',       # Reduced horizontal separation between ranks (columns)
                'nodesep': '0.3'        # Adjusted vertical separation between nodes in the same rank
            }
        )
        
        base_color = '#19191a' 

        dot.node(
            'central',
            data['central_node'],
            shape='box',            # Rectangular shape
            style='filled,rounded', # Filled and rounded corners
            fillcolor=base_color,   # Darkest color
            fontcolor='white',      # White text for dark background
            fontsize='16'           # Larger font for central node
        )
        
        add_nodes_and_edges(dot, 'central', data.get('nodes', []), current_depth=1, base_color=base_color)

        with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
            dot.render(tmp.name, format=output_format, cleanup=True)
            return f"{tmp.name}.{output_format}"

    except json.JSONDecodeError:
        return "Error: Invalid JSON format"
    except Exception as e:
        return f"Error: {str(e)}"