Update app.py
Browse files
app.py
CHANGED
|
@@ -42,7 +42,6 @@ def decode_sqlite_text(x):
|
|
| 42 |
def fetch_available_standards():
|
| 43 |
global db_registry
|
| 44 |
db_registry = []
|
| 45 |
-
|
| 46 |
for file_name in os.listdir(UPLOAD_DIR):
|
| 47 |
if file_name.endswith(".db"):
|
| 48 |
name = file_name.replace(".db", "")
|
|
@@ -53,19 +52,14 @@ def fetch_available_standards():
|
|
| 53 |
"standard": parts[0],
|
| 54 |
"version": parts[1]
|
| 55 |
})
|
| 56 |
-
|
| 57 |
return sorted(list(set([d["standard"] for d in db_registry])))
|
| 58 |
|
| 59 |
-
# ==========================================
|
| 60 |
-
# ๐ก [์ค๋งํธ ์ค์ ํ์ ๊ธฐ๋ฅ์ด ํ์ฌ๋ ๋ฐ์ดํฐ ๋ก๋]
|
| 61 |
-
# ==========================================
|
| 62 |
def fetch_database_records(std, ver, cat, table_type):
|
| 63 |
try:
|
| 64 |
db_path = os.path.join(UPLOAD_DIR, f"{std}_{ver}.db")
|
| 65 |
conn = sqlite3.connect(db_path)
|
| 66 |
conn.text_factory = decode_sqlite_text
|
| 67 |
|
| 68 |
-
# 1. ์ค์ DB ํ
์ด๋ธ๊ณผ ์ด(Column) ์ด๋ฆ์ ๋จผ์ ํ์ด๋ด
๋๋ค.
|
| 69 |
tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
|
| 70 |
valid_tables = [t for t in tables if t.lower() not in ['mapping_registry', 'mapping_table', 'table_config', 'sqlite_sequence']]
|
| 71 |
main_table = f"{std}_{ver}"
|
|
@@ -79,7 +73,6 @@ def fetch_database_records(std, ver, cat, table_type):
|
|
| 79 |
cols = pd.read_sql(f"PRAGMA table_info([{main_table}])", conn)['name'].tolist()
|
| 80 |
lower_cols = [c.lower() for c in cols]
|
| 81 |
|
| 82 |
-
# 2. Table_Config๋ฅผ ์ฝ์ด์ต๋๋ค.
|
| 83 |
conn_map = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 84 |
config_df = pd.read_sql("SELECT * FROM Table_Config WHERE TRIM(Table_Type)=?", conn_map, params=[table_type.strip()])
|
| 85 |
conn_map.close()
|
|
@@ -87,7 +80,6 @@ def fetch_database_records(std, ver, cat, table_type):
|
|
| 87 |
if config_df.empty:
|
| 88 |
return pd.DataFrame({"Error": [f"Table_Config์์ '{table_type}' ์ค์ ์ ์ฐพ์ ์ ์์ต๋๋ค."]}), []
|
| 89 |
|
| 90 |
-
# ๐ก [ํต์ฌ ํด๊ฒฐ] Table_Config์ 'Main'์ด ์ฌ๋ฌ ๊ฐ์ผ ๊ฒฝ์ฐ, ์ค์ DB ์ด ์ด๋ฆ๊ณผ ์ผ์นํ๋ ๊ฒ์ ๋๋ํ๊ฒ ์ฐพ์๋
๋๋ค!
|
| 91 |
matched_config = None
|
| 92 |
for _, row in config_df.iterrows():
|
| 93 |
anchors_test = [x.strip().lower() for x in str(row['Anchor_Column']).split(',')]
|
|
@@ -96,15 +88,14 @@ def fetch_database_records(std, ver, cat, table_type):
|
|
| 96 |
break
|
| 97 |
|
| 98 |
if matched_config is None:
|
| 99 |
-
matched_config = config_df.iloc[0]
|
| 100 |
|
| 101 |
anchors = [x.strip() for x in matched_config['Anchor_Column'].split(',')]
|
| 102 |
displays = [x.strip() for x in matched_config['Display_Columns'].split(',')] if pd.notna(matched_config['Display_Columns']) else anchors
|
| 103 |
|
| 104 |
-
# 3. ์ฟผ๋ฆฌ ์คํ ๋ฐ ์นดํ
๊ณ ๋ฆฌ ํํฐ๋ง
|
| 105 |
query = f"SELECT * FROM [{main_table}]"
|
| 106 |
-
|
| 107 |
conditions = []
|
|
|
|
| 108 |
if cat and cat != "ALL":
|
| 109 |
if "." in cat and 'chapter' in lower_cols and 'category' in lower_cols:
|
| 110 |
ch, ca = cat.split(".", 1)
|
|
@@ -121,7 +112,6 @@ def fetch_database_records(std, ver, cat, table_type):
|
|
| 121 |
df = pd.read_sql(query, conn)
|
| 122 |
conn.close()
|
| 123 |
|
| 124 |
-
# 4. ํ๋ฉด์ ๋ณด์ฌ์ค ์ด ์ ๋ฆฌ
|
| 125 |
final_cols = []
|
| 126 |
for d in displays:
|
| 127 |
for c in df.columns:
|
|
@@ -179,7 +169,7 @@ def generate_html_diff(text1, text2):
|
|
| 179 |
return text1, text2
|
| 180 |
|
| 181 |
# ==========================================
|
| 182 |
-
# 2. UI Component Handlers
|
| 183 |
# ==========================================
|
| 184 |
def load_initial_standards():
|
| 185 |
return gr.Dropdown(choices=fetch_available_standards())
|
|
@@ -187,8 +177,11 @@ def load_initial_standards():
|
|
| 187 |
def update_version_dropdown(standard):
|
| 188 |
if not standard:
|
| 189 |
return gr.Dropdown(choices=[])
|
| 190 |
-
versions =
|
| 191 |
-
|
|
|
|
|
|
|
|
|
|
| 192 |
|
| 193 |
def update_base_category_dropdown(standard, version):
|
| 194 |
if not standard or not version:
|
|
@@ -196,8 +189,8 @@ def update_base_category_dropdown(standard, version):
|
|
| 196 |
|
| 197 |
choices = ["ALL"]
|
| 198 |
status_value = ""
|
| 199 |
-
|
| 200 |
db_path = os.path.join(UPLOAD_DIR, f"{standard}_{version}.db")
|
|
|
|
| 201 |
if not os.path.exists(db_path):
|
| 202 |
return gr.update(choices=choices), gr.update(value="")
|
| 203 |
|
|
@@ -258,15 +251,29 @@ def update_comp_standard_dropdown(base_std, base_ver):
|
|
| 258 |
conn.close()
|
| 259 |
|
| 260 |
mapped_stds = sorted(df['Comp_std'].dropna().unique().tolist()) if not df.empty else []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
return gr.update(choices=mapped_stds, value=None, interactive=bool(mapped_stds))
|
| 262 |
except Exception:
|
| 263 |
-
return gr.update(choices=[], value=None, interactive=
|
| 264 |
|
| 265 |
def update_comp_version_dropdown(base_std, base_ver, comp_std):
|
| 266 |
if not all([base_std, base_ver, comp_std]):
|
| 267 |
return gr.update(choices=[], value=None, interactive=False)
|
| 268 |
|
| 269 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 270 |
conn = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 271 |
query = "SELECT DISTINCT TRIM(Comp_ver) AS Comp_ver FROM Mapping_registry WHERE TRIM(Base_std)=TRIM(?) AND TRIM(Base_ver)=TRIM(?) AND TRIM(Comp_std)=TRIM(?)"
|
| 272 |
df = pd.read_sql(query, conn, params=[base_std, base_ver, comp_std])
|
|
@@ -283,9 +290,10 @@ def update_comp_category_dropdown(base_std, base_ver, base_cat, comp_std, comp_v
|
|
| 283 |
|
| 284 |
base_type = "Main" if not base_cat or base_cat == "ALL" or "." in base_cat else base_cat
|
| 285 |
status_value = ""
|
|
|
|
|
|
|
| 286 |
|
| 287 |
try:
|
| 288 |
-
comp_db_path = os.path.join(UPLOAD_DIR, f"{comp_std}_{comp_ver}.db")
|
| 289 |
if os.path.exists(comp_db_path):
|
| 290 |
c_conn = sqlite3.connect(comp_db_path)
|
| 291 |
c_tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", c_conn)['name'].tolist()
|
|
@@ -303,16 +311,42 @@ def update_comp_category_dropdown(base_std, base_ver, base_cat, comp_std, comp_v
|
|
| 303 |
status_value = str(status_df.iloc[0]['Status'])
|
| 304 |
except Exception:
|
| 305 |
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
|
|
|
|
| 307 |
conn = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 308 |
query = """
|
| 309 |
SELECT DISTINCT TRIM(Comp_Type) AS Comp_Type
|
| 310 |
FROM Mapping_registry
|
| 311 |
-
WHERE TRIM(Base_std)=TRIM(?)
|
| 312 |
-
AND TRIM(Base_ver)=TRIM(?)
|
| 313 |
-
AND TRIM(Base_Type)=TRIM(?)
|
| 314 |
-
AND TRIM(Comp_std)=TRIM(?)
|
| 315 |
-
AND TRIM(Comp_ver)=TRIM(?)
|
| 316 |
"""
|
| 317 |
df = pd.read_sql(query, conn, params=[base_std, base_ver, base_type, comp_std, comp_ver])
|
| 318 |
conn.close()
|
|
@@ -324,21 +358,19 @@ def update_comp_category_dropdown(base_std, base_ver, base_cat, comp_std, comp_v
|
|
| 324 |
final_choices = []
|
| 325 |
if "Main" in allowed_types:
|
| 326 |
final_choices.append("ALL")
|
| 327 |
-
if os.path.exists(comp_db_path):
|
| 328 |
c_conn = sqlite3.connect(comp_db_path)
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 332 |
-
|
| 333 |
-
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
|
| 337 |
-
|
| 338 |
-
|
| 339 |
-
|
| 340 |
-
if ch and ca and ch.lower() not in ['none', 'nan'] and ca.lower() not in ['none', 'nan']:
|
| 341 |
-
final_choices.append(f"{ch}.{ca}")
|
| 342 |
c_conn.close()
|
| 343 |
|
| 344 |
for t in allowed_types:
|
|
@@ -356,7 +388,7 @@ def reset_comp_selections():
|
|
| 356 |
return gr.update(value=None), gr.update(choices=[], value=None), gr.update(choices=[], value=None), gr.update(value="")
|
| 357 |
|
| 358 |
# ==========================================
|
| 359 |
-
# 3. Core Search Logic
|
| 360 |
# ==========================================
|
| 361 |
def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat, mapped_only, diff_only):
|
| 362 |
try:
|
|
@@ -431,79 +463,79 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 431 |
df_base['merge_key'] = df_base[real_anchors_b].apply(lambda row: '-'.join([clean_key_val(x) for x in row]), axis=1)
|
| 432 |
df_comp['merge_key'] = df_comp[real_anchors_c].apply(lambda row: '-'.join([clean_key_val(x) for x in row]), axis=1)
|
| 433 |
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
SELECT Target_Table FROM Mapping_registry
|
| 437 |
-
WHERE TRIM(Base_std)=? AND TRIM(Base_ver)=? AND TRIM(Comp_std)=? AND TRIM(Comp_ver)=?
|
| 438 |
-
LIMIT 1
|
| 439 |
-
"""
|
| 440 |
-
reg_df = pd.read_sql(registry_query, conn_map, params=[base_std.strip(), base_ver.strip(), comp_std.strip(), comp_ver.strip()])
|
| 441 |
-
|
| 442 |
-
target_table_name = "Mapping_table"
|
| 443 |
-
if not reg_df.empty and pd.notna(reg_df.iloc[0]['Target_Table']):
|
| 444 |
-
val = str(reg_df.iloc[0]['Target_Table']).strip()
|
| 445 |
-
if val and val.lower() not in ["none", "nan"]:
|
| 446 |
-
target_table_name = val
|
| 447 |
|
| 448 |
-
|
| 449 |
-
|
| 450 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 451 |
|
| 452 |
-
|
| 453 |
-
|
| 454 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 455 |
conn_map.close()
|
| 456 |
-
return pd.DataFrame({"Error": [f"๋งคํ '{target_table_name}'์ ์ฌ๋ ๋ฐ ์คํจํ์ต๋๋ค. ํ
์ด๋ธ ์ด๋ฆ์ ํ์ธํ์ธ์: {str(sql_e)}"]})
|
| 457 |
-
conn_map.close()
|
| 458 |
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
if 'base_type' in cols_fw_lower and 'comp_type' in cols_fw_lower:
|
| 462 |
-
b_col = cols_fw_lower['base_type']
|
| 463 |
-
c_col = cols_fw_lower['comp_type']
|
| 464 |
-
|
| 465 |
-
df_fw[b_col] = df_fw[b_col].fillna('Main').astype(str).str.strip().str.upper()
|
| 466 |
-
df_fw[c_col] = df_fw[c_col].fillna('Main').astype(str).str.strip().str.upper()
|
| 467 |
-
df_fw = df_fw[(df_fw[b_col] == type_b.strip().upper()) & (df_fw[c_col] == type_c.strip().upper())]
|
| 468 |
|
| 469 |
-
if
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
if not df_rv.empty:
|
| 482 |
-
df_rv = df_rv[[b_sec, c_sec]].rename(columns={b_sec: 'Comp_section', c_sec: 'Base_section'})
|
| 483 |
-
else:
|
| 484 |
-
df_rv = pd.DataFrame(columns=['Base_section', 'Comp_section'])
|
| 485 |
-
|
| 486 |
-
df_mapping = pd.concat([df_fw, df_rv], ignore_index=True)
|
| 487 |
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
df_mapping['Comp_section'] = df_mapping['Comp_section'].astype(str).str.replace('\n', ',').str.split(',')
|
| 491 |
-
df_mapping = df_mapping.explode('Base_section').explode('Comp_section')
|
| 492 |
|
| 493 |
-
|
| 494 |
-
|
| 495 |
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
|
|
|
| 500 |
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
|
| 506 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
|
| 508 |
rename_b = {c: f"{c}_{base_ver}" for c in df_base.columns if c != 'merge_key'}
|
| 509 |
df_base = df_base.rename(columns=rename_b)
|
|
@@ -637,7 +669,6 @@ with gr.Blocks() as demo:
|
|
| 637 |
base_reset_btn.click(fn=reset_base_selections, inputs=None, outputs=[base_standard, base_version, base_category, base_status])
|
| 638 |
comp_reset_btn.click(fn=reset_comp_selections, inputs=None, outputs=[comp_standard, comp_version, comp_category, comp_status])
|
| 639 |
|
| 640 |
-
|
| 641 |
# ==========================================
|
| 642 |
# 5. Application Styling (CSS)
|
| 643 |
# ==========================================
|
|
@@ -649,17 +680,14 @@ css = """
|
|
| 649 |
.reset-btn {
|
| 650 |
margin-bottom: 10px !important;
|
| 651 |
}
|
| 652 |
-
|
| 653 |
table {
|
| 654 |
table-layout: auto !important;
|
| 655 |
width: max-content !important;
|
| 656 |
min-width: 100% !important;
|
| 657 |
}
|
| 658 |
-
|
| 659 |
th, td {
|
| 660 |
min-width: 150px;
|
| 661 |
}
|
| 662 |
-
|
| 663 |
table:has(th:nth-last-child(2):first-child),
|
| 664 |
table:has(th:nth-last-child(3):first-child),
|
| 665 |
table:has(th:nth-last-child(4):first-child),
|
|
@@ -668,7 +696,6 @@ table:has(th:nth-last-child(6):first-child) {
|
|
| 668 |
table-layout: fixed !important;
|
| 669 |
width: 100% !important;
|
| 670 |
}
|
| 671 |
-
|
| 672 |
table th:nth-last-child(2):first-child, table td:nth-last-child(2):first-child { width: 15% !important; min-width: 0 !important; }
|
| 673 |
table th:nth-last-child(1), table td:nth-last-child(1) { width: 85% !important; min-width: 0 !important; }
|
| 674 |
|
|
@@ -724,12 +751,10 @@ td img {
|
|
| 724 |
display: block;
|
| 725 |
max-width: none !important;
|
| 726 |
}
|
| 727 |
-
|
| 728 |
#search_row {
|
| 729 |
align-items: center !important;
|
| 730 |
margin-bottom: 5px !important;
|
| 731 |
}
|
| 732 |
-
|
| 733 |
#diff_cb_item, #mapped_cb_item {
|
| 734 |
margin-top: 0 !important;
|
| 735 |
padding-left: 15px !important;
|
|
|
|
| 42 |
def fetch_available_standards():
|
| 43 |
global db_registry
|
| 44 |
db_registry = []
|
|
|
|
| 45 |
for file_name in os.listdir(UPLOAD_DIR):
|
| 46 |
if file_name.endswith(".db"):
|
| 47 |
name = file_name.replace(".db", "")
|
|
|
|
| 52 |
"standard": parts[0],
|
| 53 |
"version": parts[1]
|
| 54 |
})
|
|
|
|
| 55 |
return sorted(list(set([d["standard"] for d in db_registry])))
|
| 56 |
|
|
|
|
|
|
|
|
|
|
| 57 |
def fetch_database_records(std, ver, cat, table_type):
|
| 58 |
try:
|
| 59 |
db_path = os.path.join(UPLOAD_DIR, f"{std}_{ver}.db")
|
| 60 |
conn = sqlite3.connect(db_path)
|
| 61 |
conn.text_factory = decode_sqlite_text
|
| 62 |
|
|
|
|
| 63 |
tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
|
| 64 |
valid_tables = [t for t in tables if t.lower() not in ['mapping_registry', 'mapping_table', 'table_config', 'sqlite_sequence']]
|
| 65 |
main_table = f"{std}_{ver}"
|
|
|
|
| 73 |
cols = pd.read_sql(f"PRAGMA table_info([{main_table}])", conn)['name'].tolist()
|
| 74 |
lower_cols = [c.lower() for c in cols]
|
| 75 |
|
|
|
|
| 76 |
conn_map = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 77 |
config_df = pd.read_sql("SELECT * FROM Table_Config WHERE TRIM(Table_Type)=?", conn_map, params=[table_type.strip()])
|
| 78 |
conn_map.close()
|
|
|
|
| 80 |
if config_df.empty:
|
| 81 |
return pd.DataFrame({"Error": [f"Table_Config์์ '{table_type}' ์ค์ ์ ์ฐพ์ ์ ์์ต๋๋ค."]}), []
|
| 82 |
|
|
|
|
| 83 |
matched_config = None
|
| 84 |
for _, row in config_df.iterrows():
|
| 85 |
anchors_test = [x.strip().lower() for x in str(row['Anchor_Column']).split(',')]
|
|
|
|
| 88 |
break
|
| 89 |
|
| 90 |
if matched_config is None:
|
| 91 |
+
matched_config = config_df.iloc[0]
|
| 92 |
|
| 93 |
anchors = [x.strip() for x in matched_config['Anchor_Column'].split(',')]
|
| 94 |
displays = [x.strip() for x in matched_config['Display_Columns'].split(',')] if pd.notna(matched_config['Display_Columns']) else anchors
|
| 95 |
|
|
|
|
| 96 |
query = f"SELECT * FROM [{main_table}]"
|
|
|
|
| 97 |
conditions = []
|
| 98 |
+
|
| 99 |
if cat and cat != "ALL":
|
| 100 |
if "." in cat and 'chapter' in lower_cols and 'category' in lower_cols:
|
| 101 |
ch, ca = cat.split(".", 1)
|
|
|
|
| 112 |
df = pd.read_sql(query, conn)
|
| 113 |
conn.close()
|
| 114 |
|
|
|
|
| 115 |
final_cols = []
|
| 116 |
for d in displays:
|
| 117 |
for c in df.columns:
|
|
|
|
| 169 |
return text1, text2
|
| 170 |
|
| 171 |
# ==========================================
|
| 172 |
+
# 2. UI Component Handlers (์ค๋งํธ ๋๋กญ๋ค์ด ์ ์ฉ)
|
| 173 |
# ==========================================
|
| 174 |
def load_initial_standards():
|
| 175 |
return gr.Dropdown(choices=fetch_available_standards())
|
|
|
|
| 177 |
def update_version_dropdown(standard):
|
| 178 |
if not standard:
|
| 179 |
return gr.Dropdown(choices=[])
|
| 180 |
+
versions = []
|
| 181 |
+
for file_name in os.listdir(UPLOAD_DIR):
|
| 182 |
+
if file_name.startswith(standard + "_") and file_name.endswith(".db"):
|
| 183 |
+
versions.append(file_name.replace(standard + "_", "").replace(".db", ""))
|
| 184 |
+
return gr.Dropdown(choices=sorted(list(set(versions))))
|
| 185 |
|
| 186 |
def update_base_category_dropdown(standard, version):
|
| 187 |
if not standard or not version:
|
|
|
|
| 189 |
|
| 190 |
choices = ["ALL"]
|
| 191 |
status_value = ""
|
|
|
|
| 192 |
db_path = os.path.join(UPLOAD_DIR, f"{standard}_{version}.db")
|
| 193 |
+
|
| 194 |
if not os.path.exists(db_path):
|
| 195 |
return gr.update(choices=choices), gr.update(value="")
|
| 196 |
|
|
|
|
| 251 |
conn.close()
|
| 252 |
|
| 253 |
mapped_stds = sorted(df['Comp_std'].dropna().unique().tolist()) if not df.empty else []
|
| 254 |
+
|
| 255 |
+
# ๐ก [ํต์ฌ] ์๊ธฐ ์์ (๊ฐ์ ๋ฒ๊ท)์ ๋ ์ง์คํธ๋ฆฌ์ ์์ด๋ ํญ์ ๊ณ ๋ฅผ ์ ์๊ฒ ๋ฌด์กฐ๊ฑด ์ถ๊ฐ!
|
| 256 |
+
if base_std not in mapped_stds:
|
| 257 |
+
mapped_stds.append(base_std)
|
| 258 |
+
mapped_stds.sort()
|
| 259 |
+
|
| 260 |
return gr.update(choices=mapped_stds, value=None, interactive=bool(mapped_stds))
|
| 261 |
except Exception:
|
| 262 |
+
return gr.update(choices=[base_std], value=None, interactive=True)
|
| 263 |
|
| 264 |
def update_comp_version_dropdown(base_std, base_ver, comp_std):
|
| 265 |
if not all([base_std, base_ver, comp_std]):
|
| 266 |
return gr.update(choices=[], value=None, interactive=False)
|
| 267 |
|
| 268 |
try:
|
| 269 |
+
# ๐ก [ํต์ฌ] ๊ฐ์ ๋ฒ๊ท๋ฉด ๋ก์ปฌ ํ์ผ ๋ชฉ๋ก์์ ์ ์ฒด ๋ฒ์ ์ ์ง์ ๋ถ๋ฌ์ต๋๋ค.
|
| 270 |
+
if base_std.strip() == comp_std.strip():
|
| 271 |
+
versions = []
|
| 272 |
+
for file_name in os.listdir(UPLOAD_DIR):
|
| 273 |
+
if file_name.startswith(comp_std + "_") and file_name.endswith(".db"):
|
| 274 |
+
versions.append(file_name.replace(comp_std + "_", "").replace(".db", ""))
|
| 275 |
+
return gr.update(choices=sorted(list(set(versions))), value=None, interactive=True)
|
| 276 |
+
|
| 277 |
conn = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 278 |
query = "SELECT DISTINCT TRIM(Comp_ver) AS Comp_ver FROM Mapping_registry WHERE TRIM(Base_std)=TRIM(?) AND TRIM(Base_ver)=TRIM(?) AND TRIM(Comp_std)=TRIM(?)"
|
| 279 |
df = pd.read_sql(query, conn, params=[base_std, base_ver, comp_std])
|
|
|
|
| 290 |
|
| 291 |
base_type = "Main" if not base_cat or base_cat == "ALL" or "." in base_cat else base_cat
|
| 292 |
status_value = ""
|
| 293 |
+
comp_db_path = os.path.join(UPLOAD_DIR, f"{comp_std}_{comp_ver}.db")
|
| 294 |
+
c_main_table = None
|
| 295 |
|
| 296 |
try:
|
|
|
|
| 297 |
if os.path.exists(comp_db_path):
|
| 298 |
c_conn = sqlite3.connect(comp_db_path)
|
| 299 |
c_tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", c_conn)['name'].tolist()
|
|
|
|
| 311 |
status_value = str(status_df.iloc[0]['Status'])
|
| 312 |
except Exception:
|
| 313 |
pass
|
| 314 |
+
c_conn.close()
|
| 315 |
+
|
| 316 |
+
# ๐ก [ํต์ฌ] ๊ฐ์ ๋ฒ๊ท์ธ ๊ฒฝ์ฐ ์นดํ
๊ณ ๋ฆฌ๋ ์ค์ค๋ก ์ค์บํด์ ๋์์ค๋๋ค.
|
| 317 |
+
if base_std.strip() == comp_std.strip():
|
| 318 |
+
final_choices = ["ALL"]
|
| 319 |
+
if os.path.exists(comp_db_path) and c_main_table:
|
| 320 |
+
c_conn = sqlite3.connect(comp_db_path)
|
| 321 |
+
cols = pd.read_sql(f"PRAGMA table_info([{c_main_table}])", c_conn)['name'].tolist()
|
| 322 |
+
lower_cols = [c.lower() for c in cols]
|
| 323 |
+
if 'chapter' in lower_cols and 'category' in lower_cols:
|
| 324 |
+
ch_col = cols[lower_cols.index('chapter')]
|
| 325 |
+
ca_col = cols[lower_cols.index('category')]
|
| 326 |
+
c_df = pd.read_sql(f"SELECT DISTINCT [{ch_col}], [{ca_col}] FROM [{c_main_table}]", c_conn)
|
| 327 |
+
for _, row in c_df.iterrows():
|
| 328 |
+
ch = str(row[ch_col]).strip()
|
| 329 |
+
ca = str(row[ca_col]).strip()
|
| 330 |
+
if ch and ca and ch.lower() not in ['none', 'nan'] and ca.lower() not in ['none', 'nan']:
|
| 331 |
+
final_choices.append(f"{ch}.{ca}")
|
| 332 |
+
|
| 333 |
+
pattern = re.compile(f"^{comp_std}[_\\s-]*{comp_ver}[_\\s-]*", re.IGNORECASE)
|
| 334 |
+
for t in c_valid_tables:
|
| 335 |
+
if t == c_main_table: continue
|
| 336 |
+
short_name = pattern.sub("", t).strip(" _")
|
| 337 |
+
if short_name and short_name not in final_choices:
|
| 338 |
+
final_choices.append(short_name)
|
| 339 |
+
elif t not in final_choices:
|
| 340 |
+
final_choices.append(t)
|
| 341 |
+
c_conn.close()
|
| 342 |
+
return gr.update(choices=final_choices, value=final_choices[0] if final_choices else None, interactive=True), gr.update(value=status_value)
|
| 343 |
|
| 344 |
+
# ๊ธฐ์กด ๋ ์ง์คํธ๋ฆฌ ๋ก์ง
|
| 345 |
conn = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 346 |
query = """
|
| 347 |
SELECT DISTINCT TRIM(Comp_Type) AS Comp_Type
|
| 348 |
FROM Mapping_registry
|
| 349 |
+
WHERE TRIM(Base_std)=TRIM(?) AND TRIM(Base_ver)=TRIM(?) AND TRIM(Base_Type)=TRIM(?) AND TRIM(Comp_std)=TRIM(?) AND TRIM(Comp_ver)=TRIM(?)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
"""
|
| 351 |
df = pd.read_sql(query, conn, params=[base_std, base_ver, base_type, comp_std, comp_ver])
|
| 352 |
conn.close()
|
|
|
|
| 358 |
final_choices = []
|
| 359 |
if "Main" in allowed_types:
|
| 360 |
final_choices.append("ALL")
|
| 361 |
+
if os.path.exists(comp_db_path) and c_main_table:
|
| 362 |
c_conn = sqlite3.connect(comp_db_path)
|
| 363 |
+
cols = pd.read_sql(f"PRAGMA table_info([{c_main_table}])", c_conn)['name'].tolist()
|
| 364 |
+
lower_cols = [c.lower() for c in cols]
|
| 365 |
+
if 'chapter' in lower_cols and 'category' in lower_cols:
|
| 366 |
+
ch_col = cols[lower_cols.index('chapter')]
|
| 367 |
+
ca_col = cols[lower_cols.index('category')]
|
| 368 |
+
c_df = pd.read_sql(f"SELECT DISTINCT [{ch_col}], [{ca_col}] FROM [{c_main_table}]", c_conn)
|
| 369 |
+
for _, row in c_df.iterrows():
|
| 370 |
+
ch = str(row[ch_col]).strip()
|
| 371 |
+
ca = str(row[ca_col]).strip()
|
| 372 |
+
if ch and ca and ch.lower() not in ['none', 'nan'] and ca.lower() not in ['none', 'nan']:
|
| 373 |
+
final_choices.append(f"{ch}.{ca}")
|
|
|
|
|
|
|
| 374 |
c_conn.close()
|
| 375 |
|
| 376 |
for t in allowed_types:
|
|
|
|
| 388 |
return gr.update(value=None), gr.update(choices=[], value=None), gr.update(choices=[], value=None), gr.update(value="")
|
| 389 |
|
| 390 |
# ==========================================
|
| 391 |
+
# 3. Core Search Logic (ํฉ์งํฉ ๋น๊ต ์ ์ฉ ์๋ฃ)
|
| 392 |
# ==========================================
|
| 393 |
def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat, mapped_only, diff_only):
|
| 394 |
try:
|
|
|
|
| 463 |
df_base['merge_key'] = df_base[real_anchors_b].apply(lambda row: '-'.join([clean_key_val(x) for x in row]), axis=1)
|
| 464 |
df_comp['merge_key'] = df_comp[real_anchors_c].apply(lambda row: '-'.join([clean_key_val(x) for x in row]), axis=1)
|
| 465 |
|
| 466 |
+
# ๐ก [ํต์ฌ] ๊ฐ์ ๋ฒ๊ท์ธ ๊ฒฝ์ฐ ์ฅ๋ถ๋ฅผ ๊ฑฐ์น์ง ์๊ณ ์์ชฝ ์กฐํญ์ 'ํฉ์งํฉ'์ ๋ง๋ญ๋๋ค.
|
| 467 |
+
is_same_std = (base_std.strip().upper() == comp_std.strip().upper() and type_b.strip().upper() == type_c.strip().upper())
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 468 |
|
| 469 |
+
if is_same_std:
|
| 470 |
+
all_keys = list(set(df_base['merge_key']).union(set(df_comp['merge_key'])))
|
| 471 |
+
bridge = pd.DataFrame({'Base_section': all_keys, 'Comp_section': all_keys})
|
| 472 |
+
else:
|
| 473 |
+
conn_map = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 474 |
+
registry_query = """
|
| 475 |
+
SELECT Target_Table FROM Mapping_registry
|
| 476 |
+
WHERE TRIM(Base_std)=? AND TRIM(Base_ver)=? AND TRIM(Comp_std)=? AND TRIM(Comp_ver)=?
|
| 477 |
+
LIMIT 1
|
| 478 |
+
"""
|
| 479 |
+
reg_df = pd.read_sql(registry_query, conn_map, params=[base_std.strip(), base_ver.strip(), comp_std.strip(), comp_ver.strip()])
|
| 480 |
|
| 481 |
+
target_table_name = "Mapping_table"
|
| 482 |
+
if not reg_df.empty and pd.notna(reg_df.iloc[0]['Target_Table']):
|
| 483 |
+
val = str(reg_df.iloc[0]['Target_Table']).strip()
|
| 484 |
+
if val and val.lower() not in ["none", "nan"]:
|
| 485 |
+
target_table_name = val
|
| 486 |
+
|
| 487 |
+
try:
|
| 488 |
+
q_fw = f"SELECT * FROM [{target_table_name}] WHERE TRIM(Base_std)=? AND TRIM(Base_ver)=? AND TRIM(Comp_std)=? AND TRIM(Comp_ver)=?"
|
| 489 |
+
df_fw = pd.read_sql(q_fw, conn_map, params=[base_std.strip(), base_ver.strip(), comp_std.strip(), comp_ver.strip()])
|
| 490 |
+
|
| 491 |
+
q_rv = f"SELECT * FROM [{target_table_name}] WHERE TRIM(Comp_std)=? AND TRIM(Comp_ver)=? AND TRIM(Base_std)=? AND TRIM(Base_ver)=?"
|
| 492 |
+
df_rv = pd.read_sql(q_rv, conn_map, params=[base_std.strip(), base_ver.strip(), comp_std.strip(), comp_ver.strip()])
|
| 493 |
+
except Exception as sql_e:
|
| 494 |
+
conn_map.close()
|
| 495 |
+
return pd.DataFrame({"Error": [f"๋งคํ ์ฅ๋ถ '{target_table_name}'์ ์ฌ๋ ๋ฐ ์คํจํ์ต๋๋ค. ํ
์ด๋ธ ์ด๋ฆ์ ํ์ธํ์ธ์: {str(sql_e)}"]})
|
| 496 |
conn_map.close()
|
|
|
|
|
|
|
| 497 |
|
| 498 |
+
cols_fw_lower = {c.lower(): c for c in df_fw.columns}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 499 |
|
| 500 |
+
if 'base_type' in cols_fw_lower and 'comp_type' in cols_fw_lower:
|
| 501 |
+
b_col = cols_fw_lower['base_type']
|
| 502 |
+
c_col = cols_fw_lower['comp_type']
|
| 503 |
+
|
| 504 |
+
df_fw[b_col] = df_fw[b_col].fillna('Main').astype(str).str.strip().str.upper()
|
| 505 |
+
df_fw[c_col] = df_fw[c_col].fillna('Main').astype(str).str.strip().str.upper()
|
| 506 |
+
df_fw = df_fw[(df_fw[b_col] == type_b.strip().upper()) & (df_fw[c_col] == type_c.strip().upper())]
|
| 507 |
+
|
| 508 |
+
if not df_rv.empty:
|
| 509 |
+
df_rv[b_col] = df_rv[b_col].fillna('Main').astype(str).str.strip().str.upper()
|
| 510 |
+
df_rv[c_col] = df_rv[c_col].fillna('Main').astype(str).str.strip().str.upper()
|
| 511 |
+
df_rv = df_rv[(df_rv[c_col] == type_b.strip().upper()) & (df_rv[b_col] == type_c.strip().upper())]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 512 |
|
| 513 |
+
b_sec = cols_fw_lower.get('base_section', 'Base_section')
|
| 514 |
+
c_sec = cols_fw_lower.get('comp_section', 'Comp_section')
|
|
|
|
|
|
|
| 515 |
|
| 516 |
+
if b_sec not in df_fw.columns or c_sec not in df_fw.columns:
|
| 517 |
+
return pd.DataFrame({"Error": [f"'{target_table_name}' ์ฅ๋ถ์ '{b_sec}' ๋๋ '{c_sec}' ์ด์ด ์์ต๋๋ค. ๋์๋ฌธ์๋ฅผ ํ์ธํ์ธ์."]})
|
| 518 |
|
| 519 |
+
df_fw = df_fw[[b_sec, c_sec]].rename(columns={b_sec: 'Base_section', c_sec: 'Comp_section'})
|
| 520 |
+
if not df_rv.empty:
|
| 521 |
+
df_rv = df_rv[[b_sec, c_sec]].rename(columns={b_sec: 'Comp_section', c_sec: 'Base_section'})
|
| 522 |
+
else:
|
| 523 |
+
df_rv = pd.DataFrame(columns=['Base_section', 'Comp_section'])
|
| 524 |
|
| 525 |
+
df_mapping = pd.concat([df_fw, df_rv], ignore_index=True)
|
| 526 |
+
|
| 527 |
+
if not df_mapping.empty:
|
| 528 |
+
df_mapping['Base_section'] = df_mapping['Base_section'].astype(str).str.replace('\n', ',').str.split(',')
|
| 529 |
+
df_mapping['Comp_section'] = df_mapping['Comp_section'].astype(str).str.replace('\n', ',').str.split(',')
|
| 530 |
+
df_mapping = df_mapping.explode('Base_section').explode('Comp_section')
|
| 531 |
+
|
| 532 |
+
df_mapping['Base_section'] = df_mapping['Base_section'].apply(clean_key_val)
|
| 533 |
+
df_mapping['Comp_section'] = df_mapping['Comp_section'].apply(clean_key_val)
|
| 534 |
+
|
| 535 |
+
df_mapping = df_mapping[(df_mapping['Base_section'] != "") & (df_mapping['Comp_section'] != "")]
|
| 536 |
+
bridge = df_mapping.dropna().drop_duplicates()
|
| 537 |
+
else:
|
| 538 |
+
bridge = pd.DataFrame(columns=['Base_section', 'Comp_section'])
|
| 539 |
|
| 540 |
rename_b = {c: f"{c}_{base_ver}" for c in df_base.columns if c != 'merge_key'}
|
| 541 |
df_base = df_base.rename(columns=rename_b)
|
|
|
|
| 669 |
base_reset_btn.click(fn=reset_base_selections, inputs=None, outputs=[base_standard, base_version, base_category, base_status])
|
| 670 |
comp_reset_btn.click(fn=reset_comp_selections, inputs=None, outputs=[comp_standard, comp_version, comp_category, comp_status])
|
| 671 |
|
|
|
|
| 672 |
# ==========================================
|
| 673 |
# 5. Application Styling (CSS)
|
| 674 |
# ==========================================
|
|
|
|
| 680 |
.reset-btn {
|
| 681 |
margin-bottom: 10px !important;
|
| 682 |
}
|
|
|
|
| 683 |
table {
|
| 684 |
table-layout: auto !important;
|
| 685 |
width: max-content !important;
|
| 686 |
min-width: 100% !important;
|
| 687 |
}
|
|
|
|
| 688 |
th, td {
|
| 689 |
min-width: 150px;
|
| 690 |
}
|
|
|
|
| 691 |
table:has(th:nth-last-child(2):first-child),
|
| 692 |
table:has(th:nth-last-child(3):first-child),
|
| 693 |
table:has(th:nth-last-child(4):first-child),
|
|
|
|
| 696 |
table-layout: fixed !important;
|
| 697 |
width: 100% !important;
|
| 698 |
}
|
|
|
|
| 699 |
table th:nth-last-child(2):first-child, table td:nth-last-child(2):first-child { width: 15% !important; min-width: 0 !important; }
|
| 700 |
table th:nth-last-child(1), table td:nth-last-child(1) { width: 85% !important; min-width: 0 !important; }
|
| 701 |
|
|
|
|
| 751 |
display: block;
|
| 752 |
max-width: none !important;
|
| 753 |
}
|
|
|
|
| 754 |
#search_row {
|
| 755 |
align-items: center !important;
|
| 756 |
margin-bottom: 5px !important;
|
| 757 |
}
|
|
|
|
| 758 |
#diff_cb_item, #mapped_cb_item {
|
| 759 |
margin-top: 0 !important;
|
| 760 |
padding-left: 15px !important;
|