Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,297 +8,479 @@ import os
|
|
| 8 |
from streamlit_sortables import sort_items
|
| 9 |
import hashlib
|
| 10 |
|
| 11 |
-
#
|
| 12 |
-
# ๊ธฐ๋ณธ ์ค์
|
| 13 |
-
#
|
| 14 |
-
st.set_page_config(page_title="
|
| 15 |
-
|
| 16 |
-
# ------------------------------
|
| 17 |
-
# ์ ์ญ ์คํ์ผ
|
| 18 |
-
# ------------------------------
|
| 19 |
st.markdown("""
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
}
|
| 39 |
-
.domain-box {
|
| 40 |
-
border-radius: 8px;
|
| 41 |
-
padding: 8px;
|
| 42 |
-
margin: 6px 4px;
|
| 43 |
-
}
|
| 44 |
-
.domain-header {
|
| 45 |
-
font-weight: 600;
|
| 46 |
-
font-size: 15px;
|
| 47 |
-
text-align: center;
|
| 48 |
-
margin-bottom: 6px;
|
| 49 |
-
}
|
| 50 |
-
</style>
|
| 51 |
""", unsafe_allow_html=True)
|
| 52 |
|
| 53 |
-
#
|
| 54 |
# ์ธ์
์ด๊ธฐํ
|
| 55 |
-
#
|
| 56 |
-
|
| 57 |
-
if key not in st.session_state:
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
|
|
|
|
|
|
|
|
|
| 64 |
|
| 65 |
-
#
|
| 66 |
-
#
|
| 67 |
-
#
|
| 68 |
-
|
|
|
|
|
|
|
| 69 |
st.session_state.page = page
|
| 70 |
st.rerun()
|
| 71 |
|
| 72 |
-
def
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
if kind == "csv":
|
| 74 |
return df.to_csv(index=False).encode("utf-8-sig")
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
|
|
|
| 83 |
G = nx.DiGraph()
|
| 84 |
color_map = {"P": "#A7C7E7", "D": "#FFE8A3", "E": "#A8E6CF", "R": "#FFD3B6", "O": "#FFAAA5"}
|
| 85 |
for _, row in df.iterrows():
|
| 86 |
code = row["code"]
|
| 87 |
label = row["name"]
|
| 88 |
-
lifecycle = row.get("
|
| 89 |
color = color_map.get(lifecycle, "#CFCFCF")
|
| 90 |
G.add_node(code, label=label, color=color)
|
| 91 |
-
for dep in row.get("depends_on", "").split(","):
|
| 92 |
dep = dep.strip()
|
| 93 |
if dep:
|
| 94 |
G.add_edge(dep, code)
|
| 95 |
-
nt = Network(height="550px", width="100%", directed=True, bgcolor="#FFFFFF", font_color="#
|
| 96 |
nt.from_nx(G)
|
| 97 |
tmp_path = tempfile.NamedTemporaryFile(delete=False, suffix=".html").name
|
| 98 |
nt.save_graph(tmp_path)
|
| 99 |
-
|
| 100 |
-
html = f.read()
|
| 101 |
os.remove(tmp_path)
|
| 102 |
return html
|
| 103 |
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 117 |
if st.session_state.page == "๋๋ฉ์ธ ์ค์ ":
|
| 118 |
st.title("1๏ธโฃ ๋๋ฉ์ธ ์ค์ ")
|
| 119 |
-
st.markdown(""
|
| 120 |
-
|
| 121 |
-
์: ์ด์๊ด๋ฆฌ / ๊ต์ก์ด์ / ์ง๋จ๊ฐ๋ฐ / ๋ฐ์ดํฐ๋ถ์
|
| 122 |
-
""")
|
| 123 |
cols = st.columns(4)
|
| 124 |
new_domains = []
|
| 125 |
for i in range(4):
|
| 126 |
with cols[i]:
|
| 127 |
-
d = st.text_input(
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
|
|
|
|
|
|
|
|
|
|
| 131 |
if st.button("โก๏ธ ๋ค์: ์
๋ฌด ๋ฐ์ฐ"):
|
| 132 |
-
|
|
|
|
|
|
|
|
|
|
| 133 |
|
| 134 |
-
#
|
| 135 |
-
# 2
|
| 136 |
-
#
|
| 137 |
elif st.session_state.page == "์
๋ฌด ๋ฐ์ฐ":
|
| 138 |
st.title("2๏ธโฃ ์
๋ฌด ๋ฐ์ฐ")
|
| 139 |
-
st.markdown("๊ฐ ๋๋ฉ์ธ๋ณ
|
| 140 |
for d in st.session_state.domains + ["๊ธฐํ"]:
|
| 141 |
st.subheader(f"๐ {d}")
|
| 142 |
-
text = st.text_area(
|
| 143 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 144 |
if text:
|
| 145 |
-
|
|
|
|
| 146 |
if st.button("โก๏ธ ๋ค์: ๊ทธ๋ฃน ์กฐ์ "):
|
| 147 |
goto("๊ทธ๋ฃน ์กฐ์ ")
|
| 148 |
|
| 149 |
-
#
|
| 150 |
-
# 3
|
| 151 |
-
#
|
| 152 |
elif st.session_state.page == "๊ทธ๋ฃน ์กฐ์ ":
|
| 153 |
st.title("3๏ธโฃ ์
๋ฌด ๊ทธ๋ฃน ์กฐ์ ")
|
| 154 |
-
st.markdown("
|
| 155 |
st.divider()
|
| 156 |
|
| 157 |
domains = st.session_state.domains + ["๊ธฐํ"]
|
| 158 |
updated = {d: list(st.session_state.grouped_tasks.get(d, [])) for d in domains}
|
| 159 |
-
|
| 160 |
cols = st.columns(len(domains))
|
| 161 |
for i, d in enumerate(domains):
|
| 162 |
with cols[i]:
|
| 163 |
-
bg =
|
| 164 |
st.markdown(f"<div class='domain-box' style='background-color:{bg};'>", unsafe_allow_html=True)
|
| 165 |
st.markdown(f"<div class='domain-header'>๐ฆ {d}</div>", unsafe_allow_html=True)
|
|
|
|
| 166 |
sorted_tasks = sort_items(updated[d], direction="vertical", key=f"sort_{d}")
|
| 167 |
for t in sorted_tasks:
|
| 168 |
c1, c2, c3 = st.columns([4, 1, 1])
|
| 169 |
with c1:
|
| 170 |
st.markdown(f"<div class='task-card'>{t}</div>", unsafe_allow_html=True)
|
| 171 |
with c2:
|
| 172 |
-
if st.button("๐", key=
|
| 173 |
-
updated[d].remove(t)
|
| 174 |
-
st.rerun()
|
| 175 |
with c3:
|
| 176 |
-
move_to = st.selectbox(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 177 |
if move_to != "(์ด๋)":
|
| 178 |
updated[move_to].append(t)
|
| 179 |
updated[d].remove(t)
|
| 180 |
st.rerun()
|
|
|
|
| 181 |
new_t = st.text_input(f"{d} ์ ์
๋ฌด", key=f"add_{i}")
|
| 182 |
if st.button(f"โ ์ถ๊ฐ ({d})", key=f"btn_add_{i}") and new_t.strip():
|
| 183 |
-
updated[d].append(new_t.strip())
|
| 184 |
-
|
| 185 |
st.markdown("</div>", unsafe_allow_html=True)
|
|
|
|
| 186 |
st.session_state.grouped_tasks = updated
|
| 187 |
c1, c2 = st.columns(2)
|
| 188 |
-
if c1.button("โฌ
๏ธ ์ด์ : ์
๋ฌด ๋ฐ์ฐ"):
|
| 189 |
-
|
| 190 |
-
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
#
|
| 194 |
-
# 4๏ธโฃ ์์กด์ฑ ํ๋จ
|
| 195 |
-
# ------------------------------
|
| 196 |
elif st.session_state.page == "์์กด์ฑ ํ๋จ":
|
| 197 |
st.title("4๏ธโฃ ์์กด์ฑ ํ๋จ")
|
| 198 |
-
st.markdown("๊ฐ ์
๋ฌด ๊ฐ
|
| 199 |
st.divider()
|
| 200 |
-
|
| 201 |
deps = {}
|
| 202 |
-
|
|
|
|
| 203 |
st.subheader(f"๐ {d}")
|
| 204 |
tasks = st.session_state.grouped_tasks.get(d, [])
|
| 205 |
all_tasks = [t for td in st.session_state.grouped_tasks.values() for t in td]
|
|
|
|
| 206 |
for t in tasks:
|
| 207 |
-
key =
|
| 208 |
-
selected = st.multiselect(
|
| 209 |
-
|
| 210 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 211 |
st.session_state.dependencies = deps
|
|
|
|
| 212 |
c1, c2 = st.columns(2)
|
| 213 |
-
if c1.button("โฌ
๏ธ ์ด์ : ๊ทธ๋ฃน ์กฐ์ "):
|
| 214 |
-
|
| 215 |
-
if c2.button("โก๏ธ ๋ค์: ์ฐ์ถ๋ฌผ ์ ์"):
|
| 216 |
-
goto("์ฐ์ถ๋ฌผ ์ ์")
|
| 217 |
|
| 218 |
-
#
|
| 219 |
-
# 5
|
| 220 |
-
#
|
| 221 |
elif st.session_state.page == "์ฐ์ถ๋ฌผ ์ ์":
|
| 222 |
st.title("5๏ธโฃ ์ฐ์ถ๋ฌผ ์ ์")
|
| 223 |
-
st.markdown("๊ฐ ์
๋ฌด
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
c1, c2 = st.columns(2)
|
| 232 |
-
if c1.button("โฌ
๏ธ ์ด์ : ์์กด์ฑ ํ๋จ"):
|
| 233 |
-
|
| 234 |
-
|
| 235 |
-
|
| 236 |
-
|
| 237 |
-
#
|
| 238 |
-
# 6๏ธโฃ ์ต์ข
์ ๋ฆฌ + ์ฝ๋ํ ๋จ๊ณ
|
| 239 |
-
# ------------------------------
|
| 240 |
elif st.session_state.page == "์ต์ข
์ ๋ฆฌ":
|
| 241 |
-
st.title("6๏ธโฃ ์ต์ข
์ ๋ฆฌ ๋ฐ ์
๋ฌด ์ฝ๋
|
| 242 |
-
st.markdown("
|
| 243 |
st.divider()
|
| 244 |
|
| 245 |
-
type_opts = ["COMM", "CEO", "MULTI", "DEEP", "ADHOC"]
|
| 246 |
cycle_opts = ["P", "D", "E", "R", "O"]
|
| 247 |
time_opts = ["T", "FE"]
|
| 248 |
|
| 249 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 250 |
for d, tasks in st.session_state.grouped_tasks.items():
|
| 251 |
-
|
|
|
|
|
|
|
| 252 |
for t in tasks:
|
| 253 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
with c1:
|
| 255 |
-
|
| 256 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 257 |
with c2:
|
| 258 |
-
|
| 259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
with c3:
|
| 261 |
-
|
| 262 |
-
|
| 263 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 264 |
|
| 265 |
-
|
|
|
|
|
|
|
| 266 |
|
| 267 |
-
|
| 268 |
-
|
| 269 |
-
|
| 270 |
-
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
|
| 274 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 275 |
"domain": d,
|
| 276 |
-
"
|
| 277 |
"name": t,
|
| 278 |
-
"
|
| 279 |
-
"
|
| 280 |
-
"
|
| 281 |
-
"
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
df = pd.DataFrame(rows)
|
| 286 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
|
| 288 |
-
# ๊ทธ๋ํ
|
| 289 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 290 |
st.components.v1.html(html, height=600, scrolling=True)
|
| 291 |
|
| 292 |
-
#
|
| 293 |
-
csv_data = export_file(df, "csv")
|
| 294 |
-
xlsx_data = export_file(df, "xlsx")
|
| 295 |
c1, c2 = st.columns(2)
|
| 296 |
-
c1.download_button("โฌ๏ธ CSV ๋ค์ด๋ก๋",
|
| 297 |
-
c2.download_button("โฌ๏ธ Excel ๋ค์ด๋ก๋",
|
| 298 |
|
| 299 |
if st.button("๐ ์ฒ์์ผ๋ก ๋์๊ฐ๊ธฐ"):
|
| 300 |
-
for k in ["page", "domains", "grouped_tasks", "dependencies", "outputs", "code_map"]:
|
| 301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
goto("๋๋ฉ์ธ ์ค์ ")
|
| 303 |
|
| 304 |
-
|
|
|
|
|
|
|
|
|
| 8 |
from streamlit_sortables import sort_items
|
| 9 |
import hashlib
|
| 10 |
|
| 11 |
+
# =========================
|
| 12 |
+
# ๊ธฐ๋ณธ ์ค์ & ์ ์ญ ์คํ์ผ
|
| 13 |
+
# =========================
|
| 14 |
+
st.set_page_config(page_title="Team Task Structuring Assistant (P-D-E-R-O)", layout="wide")
|
| 15 |
+
|
|
|
|
|
|
|
|
|
|
| 16 |
st.markdown("""
|
| 17 |
+
<style>
|
| 18 |
+
html, body, [class*="css"] {
|
| 19 |
+
font-size: 14px !important;
|
| 20 |
+
font-family: "Noto Sans KR", "Helvetica", sans-serif;
|
| 21 |
+
}
|
| 22 |
+
.stButton>button {
|
| 23 |
+
border-radius: 6px; padding: 4px 10px; font-size: 13px;
|
| 24 |
+
}
|
| 25 |
+
.task-card {
|
| 26 |
+
background-color: #fff; border: 1px solid #ddd; border-radius: 6px;
|
| 27 |
+
padding: 6px 10px; margin-bottom: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.05);
|
| 28 |
+
font-size: 13px;
|
| 29 |
+
}
|
| 30 |
+
.domain-box { border-radius: 8px; padding: 8px; margin: 6px 4px; }
|
| 31 |
+
.domain-header { font-weight: 600; font-size: 15px; text-align: center; margin-bottom: 6px; }
|
| 32 |
+
.helper-note { color:#444; font-size:12px; }
|
| 33 |
+
footer { visibility: hidden; }
|
| 34 |
+
</style>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 35 |
""", unsafe_allow_html=True)
|
| 36 |
|
| 37 |
+
# =========================
|
| 38 |
# ์ธ์
์ด๊ธฐํ
|
| 39 |
+
# =========================
|
| 40 |
+
def _init(key, default):
|
| 41 |
+
if key not in st.session_state: st.session_state[key] = default
|
| 42 |
+
|
| 43 |
+
_init("page", "๋๋ฉ์ธ ์ค์ ")
|
| 44 |
+
_init("domains", [])
|
| 45 |
+
_init("grouped_tasks", {}) # {domain: [task, ...]}
|
| 46 |
+
_init("dependencies", {}) # {"domain::task": [task_name, ...]}
|
| 47 |
+
_init("outputs", {}) # {"domain::task": output}
|
| 48 |
+
_init("code_map", {}) # {"domain::task": {...}}
|
| 49 |
+
_init("seq_registry", {}) # {(domain_code, cycle): max_seq}
|
| 50 |
+
_init("domain_defaults", {}) # {domain_code: {"cycle": "E", "time": "T"}}
|
| 51 |
|
| 52 |
+
# =========================
|
| 53 |
+
# ์ ํธ
|
| 54 |
+
# =========================
|
| 55 |
+
PALETTE = ["#E3F2FD", "#E8F5E9", "#FFF8E1", "#FCE4EC", "#E0F7FA"]
|
| 56 |
+
|
| 57 |
+
def goto(page: str):
|
| 58 |
st.session_state.page = page
|
| 59 |
st.rerun()
|
| 60 |
|
| 61 |
+
def safe_key(domain: str, task: str, prefix: str) -> str:
|
| 62 |
+
h = hashlib.md5(f"{domain}::{task}".encode()).hexdigest()[:8]
|
| 63 |
+
return f"{prefix}_{h}"
|
| 64 |
+
|
| 65 |
+
def export_file(df: pd.DataFrame, kind="csv"):
|
| 66 |
if kind == "csv":
|
| 67 |
return df.to_csv(index=False).encode("utf-8-sig")
|
| 68 |
+
bio = BytesIO()
|
| 69 |
+
with pd.ExcelWriter(bio, engine="openpyxl") as w:
|
| 70 |
+
df.to_excel(w, index=False, sheet_name="tasks")
|
| 71 |
+
bio.seek(0)
|
| 72 |
+
return bio.getvalue()
|
| 73 |
+
|
| 74 |
+
def draw_dependency_graph(df: pd.DataFrame):
|
| 75 |
+
if df.empty:
|
| 76 |
+
return "<div style='padding:8px;color:#666;'>๊ทธ๋ํ๋ฅผ ํ์ํ ๋ฐ์ดํฐ๊ฐ ์์ต๋๋ค.</div>"
|
| 77 |
G = nx.DiGraph()
|
| 78 |
color_map = {"P": "#A7C7E7", "D": "#FFE8A3", "E": "#A8E6CF", "R": "#FFD3B6", "O": "#FFAAA5"}
|
| 79 |
for _, row in df.iterrows():
|
| 80 |
code = row["code"]
|
| 81 |
label = row["name"]
|
| 82 |
+
lifecycle = row.get("cycle", "E")
|
| 83 |
color = color_map.get(lifecycle, "#CFCFCF")
|
| 84 |
G.add_node(code, label=label, color=color)
|
| 85 |
+
for dep in str(row.get("depends_on", "")).split(","):
|
| 86 |
dep = dep.strip()
|
| 87 |
if dep:
|
| 88 |
G.add_edge(dep, code)
|
| 89 |
+
nt = Network(height="550px", width="100%", directed=True, bgcolor="#FFFFFF", font_color="#222")
|
| 90 |
nt.from_nx(G)
|
| 91 |
tmp_path = tempfile.NamedTemporaryFile(delete=False, suffix=".html").name
|
| 92 |
nt.save_graph(tmp_path)
|
| 93 |
+
html = open(tmp_path, "r", encoding="utf-8").read()
|
|
|
|
| 94 |
os.remove(tmp_path)
|
| 95 |
return html
|
| 96 |
|
| 97 |
+
def map_domain_to_code(d: str) -> str:
|
| 98 |
+
table = {
|
| 99 |
+
"๊ณตํต": "COMM", "์ ๋ต": "COMM",
|
| 100 |
+
"CEO": "CEO", "CEO์๋ฒ ์ด": "CEO",
|
| 101 |
+
"๋ฆฌ๋์ญ์๋ฒ ์ด": "MULTI", "๋ฆฌ๋์ญ": "MULTI",
|
| 102 |
+
"๋ฆฌ๋์ญ์ฌ์ธต": "DEEP", "์ฌ์ธต์ง๋จ": "DEEP",
|
| 103 |
+
"์์": "ADHOC", "์์ฒญ": "ADHOC", "์์์์ฒญ์ง๋จ": "ADHOC",
|
| 104 |
+
"๊ต์ก์ด์": "EDU", "๊ต์ก": "EDU",
|
| 105 |
+
"๋ฐ์ดํฐ๋ถ์": "DATA", "๋ถ์": "DATA",
|
| 106 |
+
"์ด์๊ด๋ฆฌ": "OPS", "์ด์": "OPS",
|
| 107 |
+
}
|
| 108 |
+
for k, v in table.items():
|
| 109 |
+
if k in d: return v
|
| 110 |
+
return d[:4].upper()
|
| 111 |
+
|
| 112 |
+
def ensure_unique_list(seq):
|
| 113 |
+
seen, out = set(), []
|
| 114 |
+
for x in seq:
|
| 115 |
+
if x not in seen:
|
| 116 |
+
seen.add(x)
|
| 117 |
+
out.append(x)
|
| 118 |
+
return out
|
| 119 |
+
|
| 120 |
+
# =========================
|
| 121 |
+
# ์๋จ ์งํ ํ์๋ฐ
|
| 122 |
+
# =========================
|
| 123 |
+
STEPS = ["๋๋ฉ์ธ ์ค์ ", "์
๋ฌด ๋ฐ์ฐ", "๊ทธ๋ฃน ์กฐ์ ", "์์กด์ฑ ํ๋จ", "์ฐ์ถ๋ฌผ ์ ์", "์ต์ข
์ ๋ฆฌ"]
|
| 124 |
+
idx = STEPS.index(st.session_state.page) if st.session_state.page in STEPS else 0
|
| 125 |
+
st.progress((idx + 1) / len(STEPS), text=f"๋จ๊ณ {idx+1}/{len(STEPS)} : {st.session_state.page}")
|
| 126 |
+
|
| 127 |
+
# =========================
|
| 128 |
+
# 1) ๋๋ฉ์ธ ์ค์
|
| 129 |
+
# =========================
|
| 130 |
if st.session_state.page == "๋๋ฉ์ธ ์ค์ ":
|
| 131 |
st.title("1๏ธโฃ ๋๋ฉ์ธ ์ค์ ")
|
| 132 |
+
st.markdown("ํ์ ์ฃผ์ ์
๋ฌด ๋๋ฉ์ธ์ 3~4๊ฐ ์ ์ํ์ธ์. (์: ๊ณตํต, ๋ฆฌ๋์ญ์๋ฒ ์ด, ๊ต์ก์ด์, ๋ฐ์ดํฐ๋ถ์ ๋ฑ)")
|
| 133 |
+
|
|
|
|
|
|
|
| 134 |
cols = st.columns(4)
|
| 135 |
new_domains = []
|
| 136 |
for i in range(4):
|
| 137 |
with cols[i]:
|
| 138 |
+
d = st.text_input(
|
| 139 |
+
f"๋๋ฉ์ธ {i+1}",
|
| 140 |
+
st.session_state.domains[i] if i < len(st.session_state.domains) else ""
|
| 141 |
+
)
|
| 142 |
+
if d: new_domains.append(d.strip())
|
| 143 |
+
st.session_state.domains = ensure_unique_list([d for d in new_domains if d])
|
| 144 |
+
|
| 145 |
if st.button("โก๏ธ ๋ค์: ์
๋ฌด ๋ฐ์ฐ"):
|
| 146 |
+
if not st.session_state.domains:
|
| 147 |
+
st.warning("๋๋ฉ์ธ์ ์ต์ 1๊ฐ ์ด์ ์
๋ ฅํ์ธ์.")
|
| 148 |
+
else:
|
| 149 |
+
goto("์
๋ฌด ๋ฐ์ฐ")
|
| 150 |
|
| 151 |
+
# =========================
|
| 152 |
+
# 2) ์
๋ฌด ๋ฐ์ฐ
|
| 153 |
+
# =========================
|
| 154 |
elif st.session_state.page == "์
๋ฌด ๋ฐ์ฐ":
|
| 155 |
st.title("2๏ธโฃ ์
๋ฌด ๋ฐ์ฐ")
|
| 156 |
+
st.markdown("๊ฐ ๋๋ฉ์ธ๋ณ ์ค์ ์ํ ์ค์ธ ์
๋ฌด๋ฅผ ๊ฐ๋ฅํ ํ ๋ง์ด ์ ์ด๋ณด์ธ์. (์ค๋ฐ๊ฟ์ผ๋ก ๊ตฌ๋ถ)")
|
| 157 |
for d in st.session_state.domains + ["๊ธฐํ"]:
|
| 158 |
st.subheader(f"๐ {d}")
|
| 159 |
+
text = st.text_area(
|
| 160 |
+
f"{d} ์
๋ฌด",
|
| 161 |
+
key=f"tasks_{d}",
|
| 162 |
+
height=150,
|
| 163 |
+
placeholder="์: ๋ฆฌ๋์ญ ์ง๋จ ๋ฆฌํฌํธ ์์ฑ\n๊ต์ก ๊ธฐํ\n๋ฐ์ดํฐ ๋ถ์ ๋ฑ"
|
| 164 |
+
)
|
| 165 |
if text:
|
| 166 |
+
tasks = [t.strip() for t in text.split("\n") if t.strip()]
|
| 167 |
+
st.session_state.grouped_tasks[d] = ensure_unique_list(tasks)
|
| 168 |
if st.button("โก๏ธ ๋ค์: ๊ทธ๋ฃน ์กฐ์ "):
|
| 169 |
goto("๊ทธ๋ฃน ์กฐ์ ")
|
| 170 |
|
| 171 |
+
# =========================
|
| 172 |
+
# 3) ๊ทธ๋ฃน ์กฐ์
|
| 173 |
+
# =========================
|
| 174 |
elif st.session_state.page == "๊ทธ๋ฃน ์กฐ์ ":
|
| 175 |
st.title("3๏ธโฃ ์
๋ฌด ๊ทธ๋ฃน ์กฐ์ ")
|
| 176 |
+
st.markdown("๋๋ฉ์ธ๋ณ ์
๋ฌด๋ฅผ ์ ๋ฆฌํ๊ณ ์์๋ฅผ ๋ฐ๊พธ๊ฑฐ๋ ๋ค๋ฅธ ๋๋ฉ์ธ์ผ๋ก ์ด๋ํ์ธ์.")
|
| 177 |
st.divider()
|
| 178 |
|
| 179 |
domains = st.session_state.domains + ["๊ธฐํ"]
|
| 180 |
updated = {d: list(st.session_state.grouped_tasks.get(d, [])) for d in domains}
|
| 181 |
+
|
| 182 |
cols = st.columns(len(domains))
|
| 183 |
for i, d in enumerate(domains):
|
| 184 |
with cols[i]:
|
| 185 |
+
bg = PALETTE[i % len(PALETTE)]
|
| 186 |
st.markdown(f"<div class='domain-box' style='background-color:{bg};'>", unsafe_allow_html=True)
|
| 187 |
st.markdown(f"<div class='domain-header'>๐ฆ {d}</div>", unsafe_allow_html=True)
|
| 188 |
+
|
| 189 |
sorted_tasks = sort_items(updated[d], direction="vertical", key=f"sort_{d}")
|
| 190 |
for t in sorted_tasks:
|
| 191 |
c1, c2, c3 = st.columns([4, 1, 1])
|
| 192 |
with c1:
|
| 193 |
st.markdown(f"<div class='task-card'>{t}</div>", unsafe_allow_html=True)
|
| 194 |
with c2:
|
| 195 |
+
if st.button("๐", key=safe_key(d, t, "del")):
|
| 196 |
+
updated[d].remove(t); st.rerun()
|
|
|
|
| 197 |
with c3:
|
| 198 |
+
move_to = st.selectbox(
|
| 199 |
+
"โ",
|
| 200 |
+
["(์ด๋)"] + [x for x in domains if x != d],
|
| 201 |
+
key=safe_key(d, t, "mv")
|
| 202 |
+
)
|
| 203 |
if move_to != "(์ด๋)":
|
| 204 |
updated[move_to].append(t)
|
| 205 |
updated[d].remove(t)
|
| 206 |
st.rerun()
|
| 207 |
+
|
| 208 |
new_t = st.text_input(f"{d} ์ ์
๋ฌด", key=f"add_{i}")
|
| 209 |
if st.button(f"โ ์ถ๊ฐ ({d})", key=f"btn_add_{i}") and new_t.strip():
|
| 210 |
+
updated[d].append(new_t.strip()); st.rerun()
|
| 211 |
+
|
| 212 |
st.markdown("</div>", unsafe_allow_html=True)
|
| 213 |
+
|
| 214 |
st.session_state.grouped_tasks = updated
|
| 215 |
c1, c2 = st.columns(2)
|
| 216 |
+
if c1.button("โฌ
๏ธ ์ด์ : ์
๋ฌด ๋ฐ์ฐ"): goto("์
๋ฌด ๋ฐ์ฐ")
|
| 217 |
+
if c2.button("โก๏ธ ๋ค์: ์์กด์ฑ ํ๋จ"): goto("์์กด์ฑ ํ๋จ")
|
| 218 |
+
|
| 219 |
+
# =========================
|
| 220 |
+
# 4) ์์กด์ฑ ํ๋จ
|
| 221 |
+
# =========================
|
|
|
|
|
|
|
| 222 |
elif st.session_state.page == "์์กด์ฑ ํ๋จ":
|
| 223 |
st.title("4๏ธโฃ ์์กด์ฑ ํ๋จ")
|
| 224 |
+
st.markdown("๊ฐ ์
๋ฌด ๊ฐ ์ ํ๊ด๊ณ(์์กด์ฑ)๋ฅผ ๋๋ฉ์ธ๋ณ๋ก ์ค์ ํ์ธ์.")
|
| 225 |
st.divider()
|
| 226 |
+
|
| 227 |
deps = {}
|
| 228 |
+
domains = st.session_state.domains + ["๊ธฐํ"]
|
| 229 |
+
for i, d in enumerate(domains):
|
| 230 |
st.subheader(f"๐ {d}")
|
| 231 |
tasks = st.session_state.grouped_tasks.get(d, [])
|
| 232 |
all_tasks = [t for td in st.session_state.grouped_tasks.values() for t in td]
|
| 233 |
+
|
| 234 |
for t in tasks:
|
| 235 |
+
key = safe_key(d, t, "deps")
|
| 236 |
+
selected = st.multiselect(
|
| 237 |
+
f"'{t}' ์ด์ ์
๋ฌด",
|
| 238 |
+
[x for x in all_tasks if x != t],
|
| 239 |
+
default=st.session_state.dependencies.get(f"{d}::{t}", []),
|
| 240 |
+
key=key
|
| 241 |
+
)
|
| 242 |
+
deps[f"{d}::{t}"] = selected
|
| 243 |
st.session_state.dependencies = deps
|
| 244 |
+
|
| 245 |
c1, c2 = st.columns(2)
|
| 246 |
+
if c1.button("โฌ
๏ธ ์ด์ : ๊ทธ๋ฃน ์กฐ์ "): goto("๊ทธ๋ฃน ์กฐ์ ")
|
| 247 |
+
if c2.button("โก๏ธ ๋ค์: ์ฐ์ถ๋ฌผ ์ ์"): goto("์ฐ์ถ๋ฌผ ์ ์")
|
|
|
|
|
|
|
| 248 |
|
| 249 |
+
# =========================
|
| 250 |
+
# 5) ์ฐ์ถ๋ฌผ ์ ์ (๊ฐ๊ฒฐ ๋งคํ UI)
|
| 251 |
+
# =========================
|
| 252 |
elif st.session_state.page == "์ฐ์ถ๋ฌผ ์ ์":
|
| 253 |
st.title("5๏ธโฃ ์ฐ์ถ๋ฌผ ์ ์")
|
| 254 |
+
st.markdown("๊ฐ ์
๋ฌด์ ๋์๋๋ ์ฐ์ถ๋ฌผ์ ํ์์ ์ง์ ์
๋ ฅํ์ธ์.")
|
| 255 |
+
st.divider()
|
| 256 |
+
|
| 257 |
+
updated_outputs = {}
|
| 258 |
+
for i, (d, tasks) in enumerate(st.session_state.grouped_tasks.items()):
|
| 259 |
+
bg = PALETTE[i % len(PALETTE)]
|
| 260 |
+
st.markdown(f"<div style='background-color:{bg}; padding:10px; border-radius:8px;'>", unsafe_allow_html=True)
|
| 261 |
+
st.markdown(f"### ๐ {d}")
|
| 262 |
+
|
| 263 |
+
rows = [{"์
๋ฌด๋ช
": t, "์ฐ์ถ๋ฌผ": st.session_state.outputs.get(f"{d}::{t}", "")} for t in tasks]
|
| 264 |
+
df = pd.DataFrame(rows)
|
| 265 |
+
edited = st.data_editor(
|
| 266 |
+
df, key=f"editor_{i}", hide_index=True, num_rows="fixed",
|
| 267 |
+
column_config={
|
| 268 |
+
"์
๋ฌด๋ช
": st.column_config.TextColumn(disabled=True),
|
| 269 |
+
"์ฐ์ถ๋ฌผ": st.column_config.TextColumn(placeholder="์: ๋ฆฌํฌํธ/๋์๋ณด๋/๊ต์ก์๋ฃ/์คํฌ๋ฆฝํธ ๋ฑ")
|
| 270 |
+
},
|
| 271 |
+
use_container_width=True
|
| 272 |
+
)
|
| 273 |
+
for _, row in edited.iterrows():
|
| 274 |
+
updated_outputs[f"{d}::{row['์
๋ฌด๋ช
']}"] = row["์ฐ์ถ๋ฌผ"]
|
| 275 |
+
|
| 276 |
+
st.markdown("<div class='helper-note'>Tip: ์ฐ์ถ๋ฌผ์ ๋ณด๊ณ ์/๋์๋ณด๋/์์คํ
/๋ฐ์ดํฐ/๊ต์ก์๋ฃ ๋ฑ์ผ๋ก ๊ฐ๋จํ ์ ์ด๋ ์ถฉ๋ถํฉ๋๋ค.</div>", unsafe_allow_html=True)
|
| 277 |
+
st.markdown("</div>", unsafe_allow_html=True)
|
| 278 |
+
|
| 279 |
+
st.session_state.outputs = updated_outputs
|
| 280 |
c1, c2 = st.columns(2)
|
| 281 |
+
if c1.button("โฌ
๏ธ ์ด์ : ์์กด์ฑ ํ๋จ"): goto("์์กด์ฑ ํ๋จ")
|
| 282 |
+
if c2.button("โก๏ธ ๋ค์: ์ต์ข
์ ๋ฆฌ"): goto("์ต์ข
์ ๋ฆฌ")
|
| 283 |
+
|
| 284 |
+
# =========================
|
| 285 |
+
# 6) ์ต์ข
์ ๋ฆฌ (์ง๋ฌธํ ๋ณด์กฐ + ๊ธฐ๋ณธ๊ฐ + ์ฝ๋ ์์ฑ)
|
| 286 |
+
# =========================
|
|
|
|
|
|
|
| 287 |
elif st.session_state.page == "์ต์ข
์ ๋ฆฌ":
|
| 288 |
+
st.title("6๏ธโฃ ์ต์ข
์ ๋ฆฌ ๋ฐ ์
๋ฌด ์ฝ๋ ์์ฑ")
|
| 289 |
+
st.markdown("5๋ฌธํญ ์ฒดํฌ๋ก **์ฌ์ดํด ์ถ์ฒ**์ ๋ฐ๊ณ , **์๊ฐ๊ธฐํธ(T/FE)** ๋ ๋ณ๋๋ก ์ ํํ์ธ์.")
|
| 290 |
st.divider()
|
| 291 |
|
|
|
|
| 292 |
cycle_opts = ["P", "D", "E", "R", "O"]
|
| 293 |
time_opts = ["T", "FE"]
|
| 294 |
|
| 295 |
+
def recommend_cycles(is_p, is_d, is_e, is_r, is_o):
|
| 296 |
+
recs = []
|
| 297 |
+
if is_p: recs.append("P")
|
| 298 |
+
if is_d: recs.append("D")
|
| 299 |
+
if is_e: recs.append("E")
|
| 300 |
+
if is_r: recs.append("R")
|
| 301 |
+
if is_o: recs.append("O")
|
| 302 |
+
return recs
|
| 303 |
+
|
| 304 |
+
# 0) code_map/seq_registry ์ด๊ธฐ ๋๊ธฐํ
|
| 305 |
+
seq_registry = st.session_state.seq_registry
|
| 306 |
+
code_map = st.session_state.code_map
|
| 307 |
+
for task_key, meta in code_map.items():
|
| 308 |
+
pair = (meta.get("domain_code", ""), meta.get("cycle", ""))
|
| 309 |
+
if pair[0] and pair[1]:
|
| 310 |
+
seq_registry[pair] = max(seq_registry.get(pair, 0), meta.get("seq", 0))
|
| 311 |
+
|
| 312 |
+
# ๋๋ฉ์ธ ๊ธฐ๋ณธ๊ฐ(์ ํ์ฌํญ)
|
| 313 |
+
st.markdown("#### โ๏ธ ๋๋ฉ์ธ๋ณ ๊ธฐ๋ณธ๊ฐ (์ ํ)")
|
| 314 |
+
defaults_holder = st.container()
|
| 315 |
+
with defaults_holder:
|
| 316 |
+
for d in st.session_state.grouped_tasks.keys():
|
| 317 |
+
domain_code = map_domain_to_code(d)
|
| 318 |
+
left, mid = st.columns([1.2, 1.2])
|
| 319 |
+
with left:
|
| 320 |
+
cyc_def = st.radio(
|
| 321 |
+
f"{d}({domain_code}) ๊ธฐ๋ณธ ์ฌ์ดํด",
|
| 322 |
+
cycle_opts,
|
| 323 |
+
key=f"default_cycle_{domain_code}",
|
| 324 |
+
horizontal=True,
|
| 325 |
+
index=cycle_opts.index(st.session_state.domain_defaults.get(domain_code, {}).get("cycle", "E"))
|
| 326 |
+
)
|
| 327 |
+
with mid:
|
| 328 |
+
tm_def = st.radio(
|
| 329 |
+
f"{d}({domain_code}) ๊ธฐ๋ณธ ์๊ฐ",
|
| 330 |
+
time_opts,
|
| 331 |
+
key=f"default_time_{domain_code}",
|
| 332 |
+
horizontal=True,
|
| 333 |
+
index=time_opts.index(st.session_state.domain_defaults.get(domain_code, {}).get("time", "T"))
|
| 334 |
+
)
|
| 335 |
+
st.session_state.domain_defaults[domain_code] = {"cycle": cyc_def, "time": tm_def}
|
| 336 |
+
st.caption("๊ธฐ๋ณธ๊ฐ์ โ๋ฏธ์ค์ ์
๋ฌดโ์ ์ด๊ธฐ๊ฐ์ผ๋ก๋ง ์ฌ์ฉ๋ฉ๋๋ค. ์ด๋ฏธ ์ง์ ํ ์
๋ฌด์๋ ์ํฅ์ ์ฃผ์ง ์์ต๋๋ค.")
|
| 337 |
+
st.divider()
|
| 338 |
+
|
| 339 |
+
# 1) ์
๋ฌด ๋จ์ UI & ์ฝ๋ ์์ฑ
|
| 340 |
for d, tasks in st.session_state.grouped_tasks.items():
|
| 341 |
+
domain_code = map_domain_to_code(d)
|
| 342 |
+
st.subheader(f"๐ {d} ({domain_code})")
|
| 343 |
+
|
| 344 |
for t in tasks:
|
| 345 |
+
task_key = f"{d}::{t}"
|
| 346 |
+
prev = code_map.get(task_key, {})
|
| 347 |
+
# ๊ธฐ๋ณธ๊ฐ ์ฐ์ ์์: (๊ธฐ์กด์ ํ) > (๋๋ฉ์ธ ๊ธฐ๋ณธ) > (E/T)
|
| 348 |
+
default_cycle = prev.get("cycle", st.session_state.domain_defaults.get(domain_code, {}).get("cycle", "E"))
|
| 349 |
+
default_time = prev.get("time", st.session_state.domain_defaults.get(domain_code, {}).get("time", "T"))
|
| 350 |
+
|
| 351 |
+
# ํ๋จ ๋ณด์กฐ(5๋ฌธํญ)
|
| 352 |
+
st.markdown(f"**๐งฉ {t}**")
|
| 353 |
+
qcols = st.columns(5)
|
| 354 |
+
with qcols[0]:
|
| 355 |
+
qP = st.checkbox("P: ๋ชฉ์ /์์ /๋์", key=safe_key(d, t, "qP"), value=False)
|
| 356 |
+
with qcols[1]:
|
| 357 |
+
qD = st.checkbox("D: ์ธํ
/์ค๊ณ", key=safe_key(d, t, "qD"), value=False)
|
| 358 |
+
with qcols[2]:
|
| 359 |
+
qE = st.checkbox("E: ์งํ/์์ง", key=safe_key(d, t, "qE"), value=False)
|
| 360 |
+
with qcols[3]:
|
| 361 |
+
qR = st.checkbox("R: ํด์/ํ๊ฐ", key=safe_key(d, t, "qR"), value=False)
|
| 362 |
+
with qcols[4]:
|
| 363 |
+
qO = st.checkbox("O: ๋ฐฐํฌ/๋ฐ์", key=safe_key(d, t, "qO"), value=False)
|
| 364 |
+
|
| 365 |
+
recs = recommend_cycles(qP, qD, qE, qR, qO)
|
| 366 |
+
|
| 367 |
+
# ์ฌ์ดํด/์๊ฐ ์ ํ
|
| 368 |
+
c1, c2, c3 = st.columns([1.4, 0.9, 2.2])
|
| 369 |
+
cycle_key = safe_key(d, t, "cycle")
|
| 370 |
+
time_key = safe_key(d, t, "time")
|
| 371 |
+
|
| 372 |
with c1:
|
| 373 |
+
cyc = st.radio(
|
| 374 |
+
"์ฌ์ดํด",
|
| 375 |
+
cycle_opts,
|
| 376 |
+
key=cycle_key,
|
| 377 |
+
horizontal=True,
|
| 378 |
+
index=cycle_opts.index(default_cycle) if cycle_key not in st.session_state else
|
| 379 |
+
cycle_opts.index(st.session_state[cycle_key])
|
| 380 |
+
)
|
| 381 |
with c2:
|
| 382 |
+
tm = st.radio(
|
| 383 |
+
"์๊ฐ๊ธฐํธ",
|
| 384 |
+
time_opts,
|
| 385 |
+
key=time_key,
|
| 386 |
+
horizontal=True,
|
| 387 |
+
index=time_opts.index(default_time) if time_key not in st.session_state else
|
| 388 |
+
time_opts.index(st.session_state[time_key])
|
| 389 |
+
)
|
| 390 |
with c3:
|
| 391 |
+
if recs:
|
| 392 |
+
st.markdown("**์ถ์ฒ:** " + " ".join([f"`{r}`" for r in recs]))
|
| 393 |
+
bcols = st.columns(len(recs))
|
| 394 |
+
for i, r in enumerate(recs):
|
| 395 |
+
if bcols[i].button(f"์ถ์ฒ ์ ์ฉ({r})", key=safe_key(d, t, f"apply_{r}")):
|
| 396 |
+
st.session_state[cycle_key] = r
|
| 397 |
+
st.rerun()
|
| 398 |
+
else:
|
| 399 |
+
st.caption("์ถ์ฒ ์์: ์ฒดํฌํ ํญ๋ชฉ์ด ์์ต๋๋ค. ์ง์ ์ฌ์ดํด์ ์ ํํ์ธ์.")
|
| 400 |
|
| 401 |
+
# ์ต์ข
๊ฐ(๋ผ๋์ค ์ํ)
|
| 402 |
+
cyc = st.session_state[cycle_key]
|
| 403 |
+
tm = st.session_state[time_key]
|
| 404 |
|
| 405 |
+
pair = (domain_code, cyc)
|
| 406 |
+
# ๋์ผ pair & ๊ธฐ์กด seq ์ ์ง, ์๋๋ฉด ์ ๋ฒํธ
|
| 407 |
+
if prev and prev.get("domain_code") == domain_code and prev.get("cycle") == cyc and prev.get("seq", 0) > 0:
|
| 408 |
+
seq = prev["seq"]
|
| 409 |
+
else:
|
| 410 |
+
seq = seq_registry.get(pair, 0) + 1
|
| 411 |
+
seq_registry[pair] = seq
|
| 412 |
+
|
| 413 |
+
code = f"{domain_code}-{cyc}{seq:02d}-{tm}"
|
| 414 |
+
st.markdown(
|
| 415 |
+
f"<div class='task-card' style='background-color:#F9FAFB;'>"
|
| 416 |
+
f"<small>โก๏ธ ์ฝ๋</small> <b>{code}</b></div>",
|
| 417 |
+
unsafe_allow_html=True
|
| 418 |
+
)
|
| 419 |
+
|
| 420 |
+
code_map[task_key] = {
|
| 421 |
"domain": d,
|
| 422 |
+
"domain_code": domain_code,
|
| 423 |
"name": t,
|
| 424 |
+
"cycle": cyc,
|
| 425 |
+
"time": tm,
|
| 426 |
+
"seq": seq,
|
| 427 |
+
"code": code,
|
| 428 |
+
}
|
| 429 |
+
|
| 430 |
+
# ์ํ ๊ฐฑ์
|
| 431 |
+
st.session_state.seq_registry = seq_registry
|
| 432 |
+
st.session_state.code_map = code_map
|
| 433 |
+
|
| 434 |
+
# 2) ๊ฒฐ๊ณผํ & ๊ทธ๋ํ & ๋ค์ด๋ก๋
|
| 435 |
+
st.divider()
|
| 436 |
+
st.markdown("#### ๐ ์ต์ข
์ฝ๋ ๋ชฉ๋ก")
|
| 437 |
+
rows = []
|
| 438 |
+
for task_key, meta in code_map.items():
|
| 439 |
+
d, t = task_key.split("::", 1)
|
| 440 |
+
deps_names = st.session_state.dependencies.get(task_key, [])
|
| 441 |
+
rows.append({
|
| 442 |
+
"domain": d,
|
| 443 |
+
"domain_code": meta["domain_code"],
|
| 444 |
+
"name": t,
|
| 445 |
+
"cycle": meta["cycle"],
|
| 446 |
+
"time": meta["time"],
|
| 447 |
+
"seq": meta["seq"],
|
| 448 |
+
"code": meta["code"],
|
| 449 |
+
"depends_on": ", ".join(deps_names),
|
| 450 |
+
"output": st.session_state.outputs.get(task_key, ""),
|
| 451 |
+
})
|
| 452 |
df = pd.DataFrame(rows)
|
| 453 |
+
if not df.empty:
|
| 454 |
+
df = df.sort_values(by=["domain_code", "cycle", "seq", "name"]).reset_index(drop=True)
|
| 455 |
+
|
| 456 |
+
st.dataframe(df[["domain", "domain_code", "name", "cycle", "time", "code", "depends_on", "output"]],
|
| 457 |
+
use_container_width=True)
|
| 458 |
|
| 459 |
+
# ๊ทธ๋ํ(์ด๋ฆโ์ฝ๋ ๊ฐ์ ๋งคํ)
|
| 460 |
+
name_to_code = {meta["name"]: meta["code"] for meta in code_map.values()}
|
| 461 |
+
df_graph = df.copy()
|
| 462 |
+
if not df_graph.empty:
|
| 463 |
+
df_graph["depends_on"] = df_graph["depends_on"].apply(
|
| 464 |
+
lambda s: ", ".join([name_to_code.get(x.strip(), x.strip()) for x in s.split(",") if x.strip()]) if s else ""
|
| 465 |
+
)
|
| 466 |
+
html = draw_dependency_graph(df_graph.rename(columns={"code":"code", "name":"name"}) if not df_graph.empty else df_graph)
|
| 467 |
st.components.v1.html(html, height=600, scrolling=True)
|
| 468 |
|
| 469 |
+
# ๋ค์ด๋ก๋
|
|
|
|
|
|
|
| 470 |
c1, c2 = st.columns(2)
|
| 471 |
+
c1.download_button("โฌ๏ธ CSV ๋ค์ด๋ก๋", export_file(df, "csv"), "final_task_codes.csv", "text/csv")
|
| 472 |
+
c2.download_button("โฌ๏ธ Excel ๋ค์ด๋ก๋", export_file(df, "xlsx"), "final_task_codes.xlsx")
|
| 473 |
|
| 474 |
if st.button("๐ ์ฒ์์ผ๋ก ๋์๊ฐ๊ธฐ"):
|
| 475 |
+
for k in ["page", "domains", "grouped_tasks", "dependencies", "outputs", "code_map", "seq_registry"]:
|
| 476 |
+
if k == "domains":
|
| 477 |
+
st.session_state[k] = []
|
| 478 |
+
elif k == "page":
|
| 479 |
+
st.session_state[k] = "๋๋ฉ์ธ ์ค์ "
|
| 480 |
+
else:
|
| 481 |
+
st.session_state[k] = {}
|
| 482 |
goto("๋๋ฉ์ธ ์ค์ ")
|
| 483 |
|
| 484 |
+
# ํธํฐ
|
| 485 |
+
st.markdown("---")
|
| 486 |
+
st.caption("ยฉ 2025 Crystal_MVP")
|