ginipick commited on
Commit
9d8b6f1
ยท
verified ยท
1 Parent(s): 6f05c05

Upload process_flow_generator.py

Browse files
Files changed (1) hide show
  1. process_flow_generator.py +207 -0
process_flow_generator.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import graphviz
2
+ import json
3
+ from tempfile import NamedTemporaryFile
4
+ import os
5
+
6
+ def generate_process_flow_diagram(json_input: str, output_format: str) -> str:
7
+ """
8
+ Generates a Process Flow Diagram (Flowchart) from JSON input.
9
+
10
+ Args:
11
+ json_input (str): A JSON string describing the process flow structure.
12
+ It must follow the Expected JSON Format Example below.
13
+
14
+ Expected JSON Format Example:
15
+ {
16
+ "start_node": "Start Inference Request",
17
+ "nodes": [
18
+ {
19
+ "id": "user_input",
20
+ "label": "Receive User Input (Data)",
21
+ "type": "io"
22
+ },
23
+ {
24
+ "id": "preprocess_data",
25
+ "label": "Preprocess Data",
26
+ "type": "process"
27
+ },
28
+ {
29
+ "id": "validate_data",
30
+ "label": "Validate Data Format/Type",
31
+ "type": "decision"
32
+ },
33
+ {
34
+ "id": "data_valid_yes",
35
+ "label": "Data Valid?",
36
+ "type": "decision"
37
+ },
38
+ {
39
+ "id": "load_model",
40
+ "label": "Load AI Model (if not cached)",
41
+ "type": "process"
42
+ },
43
+ {
44
+ "id": "run_inference",
45
+ "label": "Run AI Model Inference",
46
+ "type": "process"
47
+ },
48
+ {
49
+ "id": "postprocess_output",
50
+ "label": "Postprocess Model Output",
51
+ "type": "process"
52
+ },
53
+ {
54
+ "id": "send_response",
55
+ "label": "Send Response to User",
56
+ "type": "io"
57
+ },
58
+ {
59
+ "id": "log_error",
60
+ "label": "Log Error & Notify User",
61
+ "type": "process"
62
+ },
63
+ {
64
+ "id": "end_inference_process",
65
+ "label": "End Inference Process",
66
+ "type": "end"
67
+ }
68
+ ],
69
+ "connections": [
70
+ {"from": "start_node", "to": "user_input", "label": "Request"},
71
+ {"from": "user_input", "to": "preprocess_data", "label": "Data Received"},
72
+ {"from": "preprocess_data", "to": "validate_data", "label": "Cleaned"},
73
+ {"from": "validate_data", "to": "data_valid_yes", "label": "Check"},
74
+ {"from": "data_valid_yes", "to": "load_model", "label": "Yes"},
75
+ {"from": "data_valid_yes", "to": "log_error", "label": "No"},
76
+ {"from": "load_model", "to": "run_inference", "label": "Model Ready"},
77
+ {"from": "run_inference", "to": "postprocess_output", "label": "Output Generated"},
78
+ {"from": "postprocess_output", "to": "send_response", "label": "Ready"},
79
+ {"from": "send_response", "to": "end_inference_process", "label": "Response Sent"},
80
+ {"from": "log_error", "to": "end_inference_process", "label": "Error Handled"}
81
+ ]
82
+ }
83
+
84
+ Returns:
85
+ str: The filepath to the generated PNG image file.
86
+ """
87
+ try:
88
+ if not json_input.strip():
89
+ return "Error: Empty input"
90
+
91
+ data = json.loads(json_input)
92
+
93
+ # Validate required top-level keys for a flowchart
94
+ if 'start_node' not in data or 'nodes' not in data or 'connections' not in data:
95
+ raise ValueError("Missing required fields: 'start_node', 'nodes', or 'connections'")
96
+
97
+ # Define specific node shapes for flowchart types
98
+ node_shapes = {
99
+ "process": "box", # Rectangle for processes
100
+ "decision": "diamond", # Diamond for decisions
101
+ "start": "oval", # Oval for start
102
+ "end": "oval", # Oval for end
103
+ "io": "parallelogram", # Input/Output
104
+ "document": "note", # Document symbol
105
+ "default": "box" # Fallback
106
+ }
107
+
108
+ # ํ•œ๊ธ€ ํฐํŠธ ์„ค์ •
109
+ # GDFONTPATH๊ฐ€ ์„ค์ •๋˜์–ด ์žˆ์œผ๋ฉด ํฐํŠธ ํŒŒ์ผ๋ช…(ํ™•์žฅ์ž ์ œ์™ธ) ์‚ฌ์šฉ
110
+ korean_font = 'NanumGothic-Regular'
111
+
112
+ dot = graphviz.Digraph(
113
+ name='ProcessFlowDiagram',
114
+ format='png',
115
+ graph_attr={
116
+ 'rankdir': 'TB', # Top-to-Bottom flow is common for flowcharts
117
+ 'splines': 'ortho', # Straight lines with 90-degree bends
118
+ 'bgcolor': 'white', # White background
119
+ 'pad': '0.5', # Padding around the graph
120
+ 'nodesep': '0.6', # Spacing between nodes on same rank
121
+ 'ranksep': '0.8', # Spacing between ranks
122
+ 'fontname': korean_font, # ๊ทธ๋ž˜ํ”„ ์ „์ฒด ํ•œ๊ธ€ ํฐํŠธ
123
+ 'charset': 'UTF-8' # UTF-8 ์ธ์ฝ”๋”ฉ
124
+ },
125
+ node_attr={
126
+ 'fontname': korean_font # ๋ชจ๋“  ๋…ธ๋“œ์˜ ๊ธฐ๋ณธ ํฐํŠธ
127
+ },
128
+ edge_attr={
129
+ 'fontname': korean_font # ๋ชจ๋“  ์—ฃ์ง€์˜ ๊ธฐ๋ณธ ํฐํŠธ
130
+ }
131
+ )
132
+
133
+ base_color = '#19191a' # Hardcoded base color
134
+
135
+ fill_color_for_nodes = base_color
136
+ font_color_for_nodes = 'white' if base_color == '#19191a' or base_color.lower() in ['#000000', '#19191a'] else 'black'
137
+
138
+ # Store all nodes by ID for easy lookup
139
+ all_defined_nodes = {node['id']: node for node in data['nodes']}
140
+
141
+ # Add start node explicitly
142
+ start_node_id = data['start_node']
143
+ dot.node(
144
+ start_node_id,
145
+ start_node_id, # Label is typically the ID itself for start/end
146
+ shape=node_shapes['start'],
147
+ style='filled,rounded',
148
+ fillcolor='#2196F3', # A distinct blue for Start
149
+ fontcolor='white',
150
+ fontsize='14',
151
+ fontname=korean_font # ํ•œ๊ธ€ ํฐํŠธ ์ถ”๊ฐ€
152
+ )
153
+
154
+ # Add all other nodes (process, decision, etc.)
155
+ for node_id, node_info in all_defined_nodes.items():
156
+ if node_id == start_node_id: # Skip if it's the start node, already added
157
+ continue
158
+
159
+ node_type = node_info.get("type", "default")
160
+ shape = node_shapes.get(node_type, "box")
161
+
162
+ node_label = node_info['label']
163
+
164
+ # Use distinct color for end node if it exists
165
+ if node_type == 'end':
166
+ dot.node(
167
+ node_id,
168
+ node_label,
169
+ shape=shape,
170
+ style='filled,rounded',
171
+ fillcolor='#F44336', # A distinct red for End
172
+ fontcolor='white',
173
+ fontsize='14',
174
+ fontname=korean_font # ํ•œ๊ธ€ ํฐํŠธ ์ถ”๊ฐ€
175
+ )
176
+ else: # Regular process, decision, etc. nodes use the selected base color
177
+ dot.node(
178
+ node_id,
179
+ node_label,
180
+ shape=shape,
181
+ style='filled,rounded',
182
+ fillcolor=fill_color_for_nodes,
183
+ fontcolor=font_color_for_nodes,
184
+ fontsize='14',
185
+ fontname=korean_font # ํ•œ๊ธ€ ํฐํŠธ ์ถ”๊ฐ€
186
+ )
187
+
188
+ # Add connections (edges)
189
+ for connection in data['connections']:
190
+ dot.edge(
191
+ connection['from'],
192
+ connection['to'],
193
+ label=connection.get('label', ''),
194
+ color='#4a4a4a', # Dark gray for lines
195
+ fontcolor='#4a4a4a',
196
+ fontsize='10',
197
+ fontname=korean_font # ํ•œ๊ธ€ ํฐํŠธ ์ถ”๊ฐ€
198
+ )
199
+
200
+ with NamedTemporaryFile(delete=False, suffix=f'.{output_format}') as tmp:
201
+ dot.render(tmp.name, format=output_format, cleanup=True)
202
+ return f"{tmp.name}.{output_format}"
203
+
204
+ except json.JSONDecodeError:
205
+ return "Error: Invalid JSON format"
206
+ except Exception as e:
207
+ return f"Error: {str(e)}"