soojeongcrystal commited on
Commit
5f1b0b6
ยท
verified ยท
1 Parent(s): c7196f7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +210 -0
app.py ADDED
@@ -0,0 +1,210 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import pandas as pd
3
+ import networkx as nx
4
+ from pyvis.network import Network
5
+ from io import BytesIO
6
+ import tempfile
7
+ import os
8
+ from streamlit_sortables import sort_items
9
+
10
+ st.set_page_config(page_title="ํŒ€ ์—…๋ฌด ๊ตฌ์กฐํ™” ๋„์šฐ๋ฏธ (P-D-E-R-O)", layout="wide")
11
+
12
+ # ------------------------------
13
+ # ์ดˆ๊ธฐํ™”
14
+ # ------------------------------
15
+ if "page" not in st.session_state:
16
+ st.session_state.page = "๋„๋ฉ”์ธ ์„ค์ •"
17
+ if "domains" not in st.session_state:
18
+ st.session_state.domains = []
19
+ if "tasks" not in st.session_state:
20
+ st.session_state.tasks = []
21
+ if "grouped_tasks" not in st.session_state:
22
+ st.session_state.grouped_tasks = {}
23
+ if "dependencies" not in st.session_state:
24
+ st.session_state.dependencies = {}
25
+ if "outputs" not in st.session_state:
26
+ st.session_state.outputs = {}
27
+
28
+ # ------------------------------
29
+ # ํŽ˜์ด์ง€ ์ด๋™ ํ•จ์ˆ˜
30
+ # ------------------------------
31
+ def goto(page):
32
+ st.session_state.page = page
33
+ st.experimental_rerun()
34
+
35
+ # ------------------------------
36
+ # Helper
37
+ # ------------------------------
38
+ def export_file(df, kind="csv"):
39
+ if kind=="csv":
40
+ return df.to_csv(index=False).encode("utf-8-sig")
41
+ if kind=="xlsx":
42
+ bio = BytesIO()
43
+ with pd.ExcelWriter(bio, engine="openpyxl") as w:
44
+ df.to_excel(w, index=False, sheet_name="tasks")
45
+ bio.seek(0)
46
+ return bio.getvalue()
47
+
48
+ def draw_dependency_graph(df):
49
+ G = nx.DiGraph()
50
+ color_map = {"P": "#A7C7E7", "D": "#FFE8A3", "E": "#A8E6CF", "R": "#FFD3B6", "O": "#FFAAA5"}
51
+ for _, row in df.iterrows():
52
+ code = row["code"]
53
+ label = f"{row['name']}"
54
+ lifecycle = row.get("lifecycle", "E")
55
+ color = color_map.get(lifecycle, "#CFCFCF")
56
+ G.add_node(code, label=label, color=color)
57
+ for dep in row.get("depends_on", "").split(","):
58
+ dep = dep.strip()
59
+ if dep:
60
+ G.add_edge(dep, code)
61
+ nt = Network(height="550px", width="100%", directed=True, bgcolor="#FFFFFF", font_color="#222222")
62
+ nt.from_nx(G)
63
+ tmp_path = tempfile.NamedTemporaryFile(delete=False, suffix=".html").name
64
+ nt.save_graph(tmp_path)
65
+ with open(tmp_path, "r", encoding="utf-8") as f:
66
+ html = f.read()
67
+ os.remove(tmp_path)
68
+ return html
69
+
70
+ # ------------------------------
71
+ # PAGE 1. ๋„๋ฉ”์ธ ์„ค์ •
72
+ # ------------------------------
73
+ if st.session_state.page == "๋„๋ฉ”์ธ ์„ค์ •":
74
+ st.title("1๏ธโƒฃ ๋„๋ฉ”์ธ ์„ค์ •")
75
+ st.markdown("""
76
+ ํŒ€์ด ํ•˜๊ณ  ์žˆ๋Š” ์ผ์„ 3~4๊ฐœ์˜ **๋„๋ฉ”์ธ(์—…๋ฌด ๋ฒ”์ฃผ)** ๋กœ ๋‚˜๋ˆ„์–ด ๋ด…๋‹ˆ๋‹ค.
77
+ ์˜ˆ: ์šด์˜๊ด€๋ฆฌ, ๊ต์œก์šด์˜, ์ง„๋‹จ๊ฐœ๋ฐœ, ๋ฐ์ดํ„ฐ๋ถ„์„, ๊ธฐํƒ€ ๋“ฑ
78
+ """)
79
+ cols = st.columns(4)
80
+ new_domains = []
81
+ for i in range(4):
82
+ with cols[i]:
83
+ d = st.text_input(f"๋„๋ฉ”์ธ {i+1}", st.session_state.domains[i] if i < len(st.session_state.domains) else "")
84
+ if d:
85
+ new_domains.append(d)
86
+ st.session_state.domains = [d for d in new_domains if d]
87
+ if st.button("โžก๏ธ ๋‹ค์Œ: ์—…๋ฌด ๋ฐœ์‚ฐํ•˜๊ธฐ") and st.session_state.domains:
88
+ goto("์—…๋ฌด ๋ฐœ์‚ฐ")
89
+
90
+ # ------------------------------
91
+ # PAGE 2. ์—…๋ฌด ๋ฐœ์‚ฐ (์ž์œ  ์ž…๋ ฅ)
92
+ # ------------------------------
93
+ elif st.session_state.page == "์—…๋ฌด ๋ฐœ์‚ฐ":
94
+ st.title("2๏ธโƒฃ ์—…๋ฌด ์ž์œ  ๋ฐœ์‚ฐ")
95
+ st.markdown("""
96
+ ๊ฐ ๋„๋ฉ”์ธ๋ณ„๋กœ ํŒ€์—์„œ ์‹ค์ œ ํ•˜๊ณ  ์žˆ๋Š” ์ผ์„ ๊ฐ€๋Šฅํ•œ ํ•œ ๋งŽ์ด ์ ์–ด๋ณด์„ธ์š”.
97
+ ํ˜•์‹์€ ์ž์œ ๋กญ์Šต๋‹ˆ๋‹ค. ๋‚˜์ค‘์— ๋ฌถ๊ณ  ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
98
+ """)
99
+ for d in st.session_state.domains + ["๊ธฐํƒ€"]:
100
+ st.subheader(f"๐Ÿ“‚ {d}")
101
+ task_text = st.text_area(f"{d} ๋„๋ฉ”์ธ์˜ ์—…๋ฌด๋“ค", key=f"tasks_{d}", height=150,
102
+ placeholder="์˜ˆ: ๋ฆฌ๋”์‹ญ ์ง„๋‹จ ๊ฒฐ๊ณผ ๋ฆฌํฌํŠธ ์ž‘์„ฑ\n์กฐ์ง๋ฌธํ™” ๊ต์œก ๊ธฐํš\n๋ฐ์ดํ„ฐ ์ •๋ฆฌ ์ž๋™ํ™” ๋“ฑ")
103
+ if task_text:
104
+ st.session_state.grouped_tasks[d] = [t.strip() for t in task_text.split("\n") if t.strip()]
105
+ if st.button("โžก๏ธ ๋‹ค์Œ: ๊ทธ๋ฃน ์กฐ์ • ๋ฐ ์ •๋ฆฌ"):
106
+ goto("๊ทธ๋ฃน ์กฐ์ •")
107
+
108
+ # ------------------------------
109
+ # PAGE 3. ๊ทธ๋ฃน ์กฐ์ • (๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ)
110
+ # ------------------------------
111
+ elif st.session_state.page == "๊ทธ๋ฃน ์กฐ์ •":
112
+ st.title("3๏ธโƒฃ ์—…๋ฌด ๊ทธ๋ฃน ์žฌ์ •๋ ฌ")
113
+ st.markdown("""
114
+ ๋„๋ฉ”์ธ๋ณ„๋กœ ๋ฐœ์‚ฐ๋œ ์—…๋ฌด๋ฅผ **๋“œ๋ž˜๊ทธ ์•ค ๋“œ๋กญ**์œผ๋กœ ๋ฌถ๊ฑฐ๋‚˜ ์ˆœ์„œ๋ฅผ ๋ฐ”๊พธ์„ธ์š”.
115
+ ์ƒˆ ๊ทธ๋ฃน์„ ๋งŒ๋“ค๊ฑฐ๋‚˜ ๋ถˆํ•„์š”ํ•œ ํ•ญ๋ชฉ์€ ์‚ญ์ œํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
116
+ """)
117
+ new_grouped = {}
118
+ for d, tasks in st.session_state.grouped_tasks.items():
119
+ st.subheader(f"๐Ÿ“ฆ {d}")
120
+ sorted_tasks = sort_items(tasks, direction="vertical", key=f"sort_{d}")
121
+ new_grouped[d] = sorted_tasks
122
+ st.session_state.grouped_tasks = new_grouped
123
+ c1, c2 = st.columns(2)
124
+ if c1.button("โฌ…๏ธ ์ด์ „: ๋ฐœ์‚ฐ ๋‹ค์‹œ๋ณด๊ธฐ"):
125
+ goto("์—…๋ฌด ๋ฐœ์‚ฐ")
126
+ if c2.button("โžก๏ธ ๋‹ค์Œ: ์˜์กด์„ฑ ํŒ๋‹จ"):
127
+ goto("์˜์กด์„ฑ ํŒ๋‹จ")
128
+
129
+ # ------------------------------
130
+ # PAGE 4. ์˜์กด์„ฑ ํŒ๋‹จ
131
+ # ------------------------------
132
+ elif st.session_state.page == "์˜์กด์„ฑ ํŒ๋‹จ":
133
+ st.title("4๏ธโƒฃ ์—…๋ฌด ๊ฐ„ ์˜์กด์„ฑ ํŒ๋‹จ")
134
+ st.markdown("""
135
+ ๊ฐ ์—…๋ฌด ๊ฐ„์— **์„ ํ›„ ๊ด€๊ณ„(์˜์กด์„ฑ)** ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ๋ฅผ ์ฒดํฌํ•˜์„ธ์š”.
136
+ ์˜ˆ: โ€œ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘ โ†’ ๋ถ„์„ ๋ฆฌํฌํŠธ ์ž‘์„ฑโ€์ฒ˜๋Ÿผ ์ˆœ์„œ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ.
137
+ """)
138
+ all_tasks = [t for tasks in st.session_state.grouped_tasks.values() for t in tasks]
139
+ dependencies = {}
140
+ for t in all_tasks:
141
+ deps = st.multiselect(f"'{t}' ์ด์ „์— ํ•„์š”ํ•œ ์ž‘์—…", [x for x in all_tasks if x != t],
142
+ default=st.session_state.dependencies.get(t, []))
143
+ dependencies[t] = deps
144
+ st.session_state.dependencies = dependencies
145
+ c1, c2 = st.columns(2)
146
+ if c1.button("โฌ…๏ธ ์ด์ „: ๊ทธ๋ฃน ์กฐ์ •"):
147
+ goto("๊ทธ๋ฃน ์กฐ์ •")
148
+ if c2.button("โžก๏ธ ๋‹ค์Œ: ์‚ฐ์ถœ๋ฌผ ์ •์˜"):
149
+ goto("์‚ฐ์ถœ๋ฌผ ์ •์˜")
150
+
151
+ # ------------------------------
152
+ # PAGE 5. ์‚ฐ์ถœ๋ฌผ ์ •์˜
153
+ # ------------------------------
154
+ elif st.session_state.page == "์‚ฐ์ถœ๋ฌผ ์ •์˜":
155
+ st.title("5๏ธโƒฃ ์‚ฐ์ถœ๋ฌผ(Output) ์ •์˜")
156
+ st.markdown("""
157
+ ๊ฐ ์—…๋ฌด(Task)์˜ ๊ฒฐ๊ณผ๋ฌผ(์‚ฐ์ถœ๋ฌผ)์„ ์ ์–ด์ฃผ์„ธ์š”.
158
+ ์˜ˆ: ๋ณด๊ณ ์„œ, ๋Œ€์‹œ๋ณด๋“œ, ๊ต์œก์ž๋ฃŒ, ์ž๋™ํ™”์Šคํฌ๋ฆฝํŠธ ๋“ฑ
159
+ """)
160
+ outputs = {}
161
+ for d, tasks in st.session_state.grouped_tasks.items():
162
+ st.subheader(f"๐Ÿ“‚ {d}")
163
+ for t in tasks:
164
+ val = st.text_input(f"{t} โ†’ ์‚ฐ์ถœ๋ฌผ", value=st.session_state.outputs.get(t, ""))
165
+ outputs[t] = val
166
+ st.session_state.outputs = outputs
167
+ c1, c2 = st.columns(2)
168
+ if c1.button("โฌ…๏ธ ์ด์ „: ์˜์กด์„ฑ ํŒ๋‹จ"):
169
+ goto("์˜์กด์„ฑ ํŒ๋‹จ")
170
+ if c2.button("โžก๏ธ ๋‹ค์Œ: ์ตœ์ข… ์ •๋ฆฌ"):
171
+ goto("์ตœ์ข… ์ •๋ฆฌ")
172
+
173
+ # ------------------------------
174
+ # PAGE 6. ์ตœ์ข… ์ •๋ฆฌ ๋ฐ ๋‹ค์šด๋กœ๋“œ
175
+ # ------------------------------
176
+ elif st.session_state.page == "์ตœ์ข… ์ •๋ฆฌ":
177
+ st.title("6๏ธโƒฃ ์ตœ์ข… ์ •๋ฆฌ ๋ฐ ๋‹ค์šด๋กœ๋“œ")
178
+ st.markdown("์•„๋ž˜๋Š” ์ „์ฒด ์—…๋ฌด(Task) ์ •๋ฆฌํ‘œ์ž…๋‹ˆ๋‹ค. ์˜์กด์„ฑ ๊ด€๊ณ„๋Š” ๊ทธ๋ž˜ํ”„๋กœ ์‹œ๊ฐํ™”๋ฉ๋‹ˆ๋‹ค.")
179
+
180
+ # ๋ฐ์ดํ„ฐํ”„๋ ˆ์ž„ ๊ตฌ์„ฑ
181
+ rows = []
182
+ for d, tasks in st.session_state.grouped_tasks.items():
183
+ for i, t in enumerate(tasks, 1):
184
+ deps = ",".join(st.session_state.dependencies.get(t, []))
185
+ outp = st.session_state.outputs.get(t, "")
186
+ rows.append({
187
+ "domain": d, "order": i, "name": t, "depends_on": deps, "output": outp,
188
+ "code": f"{d[:3].upper()}-{i:02d}", "lifecycle": "E"
189
+ })
190
+ df = pd.DataFrame(rows)
191
+ st.dataframe(df, use_container_width=True)
192
+
193
+ # ๊ทธ๋ž˜ํ”„ ํ‘œ์‹œ
194
+ html = draw_dependency_graph(df)
195
+ st.components.v1.html(html, height=600, scrolling=True)
196
+
197
+ # ๋‹ค์šด๋กœ๋“œ
198
+ csv_data = export_file(df, "csv")
199
+ xlsx_data = export_file(df, "xlsx")
200
+ c1, c2 = st.columns(2)
201
+ c1.download_button("โฌ‡๏ธ CSV ๋‹ค์šด๋กœ๋“œ", csv_data, "tasks.csv", "text/csv")
202
+ c2.download_button("โฌ‡๏ธ Excel ๋‹ค์šด๋กœ๋“œ", xlsx_data, "tasks.xlsx")
203
+
204
+ if st.button("๐Ÿ”„ ์ฒ˜์Œ์œผ๋กœ ๋Œ์•„๊ฐ€๊ธฐ"):
205
+ for k in ["page","domains","tasks","grouped_tasks","dependencies","outputs"]:
206
+ if k in st.session_state:
207
+ del st.session_state[k]
208
+ goto("๋„๋ฉ”์ธ ์„ค์ •")
209
+
210
+ st.caption("ยฉ 2025 ํŒ€ ์—…๋ฌด ๊ตฌ์กฐํ™” ๋„์šฐ๋ฏธ - Streamlit & Pyvis ๊ธฐ๋ฐ˜")