Update app.py
Browse files
app.py
CHANGED
|
@@ -245,7 +245,6 @@ def fetch_database_records(standard, version, selection, table_type):
|
|
| 245 |
conn.text_factory = decode_sqlite_text
|
| 246 |
|
| 247 |
anchor_col_config, display_setting = get_table_config(table_type)
|
| 248 |
-
# 앵커가 쉼표로 여러 개일 경우 리스트로 분할
|
| 249 |
anchor_cols_config = [x.strip() for x in anchor_col_config.split(',')]
|
| 250 |
|
| 251 |
tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
|
|
@@ -287,7 +286,6 @@ def fetch_database_records(standard, version, selection, table_type):
|
|
| 287 |
|
| 288 |
df = df.drop(columns=[c for c in df.columns if c.lower() == "version"], errors="ignore")
|
| 289 |
|
| 290 |
-
# 대소문자 무시하고 실제 앵커 컬럼명 찾기 (다중 앵커 지원)
|
| 291 |
real_anchors = []
|
| 292 |
for ac in anchor_cols_config:
|
| 293 |
real_ac = next((c for c in df.columns if c.lower() == ac.lower()), ac)
|
|
@@ -297,7 +295,6 @@ def fetch_database_records(standard, version, selection, table_type):
|
|
| 297 |
cols_to_show = [c.strip() for c in display_setting.split(',')]
|
| 298 |
actual_cols_to_show = [c for c in df.columns if next((True for req in cols_to_show if c.lower() == req.lower()), False)]
|
| 299 |
|
| 300 |
-
# 설정된 출력 열에 앵커들이 없으면 강제 삽입
|
| 301 |
for ra in reversed(real_anchors):
|
| 302 |
if ra in df.columns and ra not in actual_cols_to_show:
|
| 303 |
actual_cols_to_show.insert(0, ra)
|
|
@@ -309,7 +306,7 @@ def fetch_database_records(standard, version, selection, table_type):
|
|
| 309 |
df[col] = df[col].apply(convert_blob_to_html_img)
|
| 310 |
|
| 311 |
conn.close()
|
| 312 |
-
return df, real_anchors
|
| 313 |
|
| 314 |
except Exception as e:
|
| 315 |
import traceback
|
|
@@ -322,7 +319,6 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 322 |
is_main = base_cat == "ALL" or (base_cat and base_cat[0].isdigit() and "." in base_cat)
|
| 323 |
table_type = "Main" if is_main else base_cat
|
| 324 |
|
| 325 |
-
# 시각적 중복 제거 함수 (폭포수 방식: 상위 컬럼이 같을 때만 하위 컬럼 지움)
|
| 326 |
def apply_visual_merge(df, cols):
|
| 327 |
if not df.empty and len(cols) > 1:
|
| 328 |
is_dup = pd.Series([True] * len(df), index=df.index)
|
|
@@ -334,17 +330,13 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 334 |
df.loc[is_dup, col] = ""
|
| 335 |
return df
|
| 336 |
|
| 337 |
-
#
|
| 338 |
-
# 1. 단일 조회
|
| 339 |
-
# ----------------------------------------
|
| 340 |
if base_std and base_ver and base_cat and (not comp_std or not comp_ver or not comp_cat):
|
| 341 |
df, _ = fetch_database_records(base_std, base_ver, base_cat, table_type)
|
| 342 |
if "Error" in df.columns or "Info" in df.columns: return df
|
| 343 |
return apply_visual_merge(df, df.columns)
|
| 344 |
|
| 345 |
-
#
|
| 346 |
-
# 2. 비교 조회
|
| 347 |
-
# ----------------------------------------
|
| 348 |
if all([base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat]):
|
| 349 |
df_base, real_anchors_b = fetch_database_records(base_std, base_ver, base_cat, table_type)
|
| 350 |
df_comp, real_anchors_c = fetch_database_records(comp_std, comp_ver, comp_cat, table_type)
|
|
@@ -357,11 +349,9 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 357 |
for ra in real_anchors_c:
|
| 358 |
if ra not in df_comp.columns: return pd.DataFrame({"Error": [f"비교 열 '{ra}'이 데이터에 없습니다."]})
|
| 359 |
|
| 360 |
-
# 다중 앵커를 하이픈(-)으로 연결해 단일 merge_key 생성 (예: "5.1-M6")
|
| 361 |
df_base['merge_key'] = df_base[real_anchors_b].apply(lambda row: '-'.join(row.values.astype(str)), axis=1).str.replace(" ", "")
|
| 362 |
df_comp['merge_key'] = df_comp[real_anchors_c].apply(lambda row: '-'.join(row.values.astype(str)), axis=1).str.replace(" ", "")
|
| 363 |
|
| 364 |
-
# 브릿지 매핑 복원
|
| 365 |
conn_map = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 366 |
query_fw = "SELECT Base_section, Comp_section FROM Mapping_table WHERE TRIM(Base_std)=? AND TRIM(Base_ver)=? AND TRIM(Comp_std)=? AND TRIM(Comp_ver)=?"
|
| 367 |
df_fw = pd.read_sql(query_fw, conn_map, params=[base_std.strip(), base_ver.strip(), comp_std.strip(), comp_ver.strip()])
|
|
@@ -412,7 +402,6 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 412 |
merged['comp_idx'] = merged['comp_idx'].fillna(float('inf'))
|
| 413 |
merged = merged.sort_values(['base_idx', 'comp_idx'])
|
| 414 |
|
| 415 |
-
# N열 동적 조립 및 Diff 처리
|
| 416 |
result_rows = []
|
| 417 |
b_cols_renamed = list(rename_b.values())
|
| 418 |
c_cols_renamed = list(rename_c.values())
|
|
@@ -427,20 +416,22 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 427 |
for c in c_cols_renamed:
|
| 428 |
row_dict[c] = str(row.get(c)) if has_c and not pd.isna(row.get(c)) else ""
|
| 429 |
|
| 430 |
-
|
| 431 |
-
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
|
| 435 |
-
|
| 436 |
-
|
| 437 |
-
|
|
|
|
|
|
|
|
|
|
| 438 |
|
| 439 |
result_rows.append(row_dict)
|
| 440 |
|
| 441 |
final_df = pd.DataFrame(result_rows)
|
| 442 |
|
| 443 |
-
# 좌/우 각각 시각적 병합(Cascading Blanking) 적용
|
| 444 |
final_df = apply_visual_merge(final_df, b_cols_renamed)
|
| 445 |
final_df = apply_visual_merge(final_df, c_cols_renamed)
|
| 446 |
|
|
@@ -512,14 +503,41 @@ with gr.Blocks() as demo:
|
|
| 512 |
css = """
|
| 513 |
table {
|
| 514 |
table-layout: fixed !important;
|
| 515 |
-
width:
|
| 516 |
-
min-width: 100%;
|
| 517 |
}
|
| 518 |
|
|
|
|
| 519 |
th, td {
|
| 520 |
min-width: 150px;
|
| 521 |
}
|
| 522 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
thead th {
|
| 524 |
font-size: 18px !important;
|
| 525 |
position: sticky;
|
|
|
|
| 245 |
conn.text_factory = decode_sqlite_text
|
| 246 |
|
| 247 |
anchor_col_config, display_setting = get_table_config(table_type)
|
|
|
|
| 248 |
anchor_cols_config = [x.strip() for x in anchor_col_config.split(',')]
|
| 249 |
|
| 250 |
tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
|
|
|
|
| 286 |
|
| 287 |
df = df.drop(columns=[c for c in df.columns if c.lower() == "version"], errors="ignore")
|
| 288 |
|
|
|
|
| 289 |
real_anchors = []
|
| 290 |
for ac in anchor_cols_config:
|
| 291 |
real_ac = next((c for c in df.columns if c.lower() == ac.lower()), ac)
|
|
|
|
| 295 |
cols_to_show = [c.strip() for c in display_setting.split(',')]
|
| 296 |
actual_cols_to_show = [c for c in df.columns if next((True for req in cols_to_show if c.lower() == req.lower()), False)]
|
| 297 |
|
|
|
|
| 298 |
for ra in reversed(real_anchors):
|
| 299 |
if ra in df.columns and ra not in actual_cols_to_show:
|
| 300 |
actual_cols_to_show.insert(0, ra)
|
|
|
|
| 306 |
df[col] = df[col].apply(convert_blob_to_html_img)
|
| 307 |
|
| 308 |
conn.close()
|
| 309 |
+
return df, real_anchors
|
| 310 |
|
| 311 |
except Exception as e:
|
| 312 |
import traceback
|
|
|
|
| 319 |
is_main = base_cat == "ALL" or (base_cat and base_cat[0].isdigit() and "." in base_cat)
|
| 320 |
table_type = "Main" if is_main else base_cat
|
| 321 |
|
|
|
|
| 322 |
def apply_visual_merge(df, cols):
|
| 323 |
if not df.empty and len(cols) > 1:
|
| 324 |
is_dup = pd.Series([True] * len(df), index=df.index)
|
|
|
|
| 330 |
df.loc[is_dup, col] = ""
|
| 331 |
return df
|
| 332 |
|
| 333 |
+
# 단일 조회
|
|
|
|
|
|
|
| 334 |
if base_std and base_ver and base_cat and (not comp_std or not comp_ver or not comp_cat):
|
| 335 |
df, _ = fetch_database_records(base_std, base_ver, base_cat, table_type)
|
| 336 |
if "Error" in df.columns or "Info" in df.columns: return df
|
| 337 |
return apply_visual_merge(df, df.columns)
|
| 338 |
|
| 339 |
+
# 비교 조회
|
|
|
|
|
|
|
| 340 |
if all([base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat]):
|
| 341 |
df_base, real_anchors_b = fetch_database_records(base_std, base_ver, base_cat, table_type)
|
| 342 |
df_comp, real_anchors_c = fetch_database_records(comp_std, comp_ver, comp_cat, table_type)
|
|
|
|
| 349 |
for ra in real_anchors_c:
|
| 350 |
if ra not in df_comp.columns: return pd.DataFrame({"Error": [f"비교 열 '{ra}'이 데이터에 없습니다."]})
|
| 351 |
|
|
|
|
| 352 |
df_base['merge_key'] = df_base[real_anchors_b].apply(lambda row: '-'.join(row.values.astype(str)), axis=1).str.replace(" ", "")
|
| 353 |
df_comp['merge_key'] = df_comp[real_anchors_c].apply(lambda row: '-'.join(row.values.astype(str)), axis=1).str.replace(" ", "")
|
| 354 |
|
|
|
|
| 355 |
conn_map = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
|
| 356 |
query_fw = "SELECT Base_section, Comp_section FROM Mapping_table WHERE TRIM(Base_std)=? AND TRIM(Base_ver)=? AND TRIM(Comp_std)=? AND TRIM(Comp_ver)=?"
|
| 357 |
df_fw = pd.read_sql(query_fw, conn_map, params=[base_std.strip(), base_ver.strip(), comp_std.strip(), comp_ver.strip()])
|
|
|
|
| 402 |
merged['comp_idx'] = merged['comp_idx'].fillna(float('inf'))
|
| 403 |
merged = merged.sort_values(['base_idx', 'comp_idx'])
|
| 404 |
|
|
|
|
| 405 |
result_rows = []
|
| 406 |
b_cols_renamed = list(rename_b.values())
|
| 407 |
c_cols_renamed = list(rename_c.values())
|
|
|
|
| 416 |
for c in c_cols_renamed:
|
| 417 |
row_dict[c] = str(row.get(c)) if has_c and not pd.isna(row.get(c)) else ""
|
| 418 |
|
| 419 |
+
# [해결 1] Diff 적용: 원본 컬럼명에 'description'이 포함되어 있으면 모두 적용!
|
| 420 |
+
if has_b and has_c:
|
| 421 |
+
for orig_col in rename_b.keys():
|
| 422 |
+
if 'description' in orig_col.lower():
|
| 423 |
+
b_col_name = rename_b[orig_col]
|
| 424 |
+
c_col_name = rename_c.get(orig_col)
|
| 425 |
+
|
| 426 |
+
if c_col_name and c_col_name in row_dict:
|
| 427 |
+
b_val, c_val = row_dict[b_col_name], row_dict[c_col_name]
|
| 428 |
+
if b_val and c_val and "<img" not in b_val and "<img" not in c_val and b_val != c_val:
|
| 429 |
+
row_dict[b_col_name], row_dict[c_col_name] = generate_html_diff(b_val, c_val)
|
| 430 |
|
| 431 |
result_rows.append(row_dict)
|
| 432 |
|
| 433 |
final_df = pd.DataFrame(result_rows)
|
| 434 |
|
|
|
|
| 435 |
final_df = apply_visual_merge(final_df, b_cols_renamed)
|
| 436 |
final_df = apply_visual_merge(final_df, c_cols_renamed)
|
| 437 |
|
|
|
|
| 503 |
css = """
|
| 504 |
table {
|
| 505 |
table-layout: fixed !important;
|
| 506 |
+
width: 100% !important;
|
|
|
|
| 507 |
}
|
| 508 |
|
| 509 |
+
/* 7열 이상의 다중 열에 대한 기본 Fallback (가로 스크롤 허용) */
|
| 510 |
th, td {
|
| 511 |
min-width: 150px;
|
| 512 |
}
|
| 513 |
|
| 514 |
+
/* [해결 2] 작성하셨던 기존 2~6 컬럼 비율 완전 복구 (min-width 무시 설정 추가) */
|
| 515 |
+
/* 2컬럼 */
|
| 516 |
+
table th:nth-last-child(2):first-child, table td:nth-last-child(2):first-child { width: 15% !important; min-width: 0 !important; }
|
| 517 |
+
table th:nth-last-child(1), table td:nth-last-child(1) { width: 85% !important; min-width: 0 !important; }
|
| 518 |
+
|
| 519 |
+
/* 4컬럼 */
|
| 520 |
+
table th:nth-child(1):nth-last-child(4), table td:nth-child(1):nth-last-child(4) { width: 7% !important; min-width: 0 !important; }
|
| 521 |
+
table th:nth-child(2):nth-last-child(3), table td:nth-child(2):nth-last-child(3) { width: 43% !important; min-width: 0 !important; }
|
| 522 |
+
table th:nth-child(3):nth-last-child(2), table td:nth-child(3):nth-last-child(2) { width: 7% !important; min-width: 0 !important; }
|
| 523 |
+
table th:nth-child(4):nth-last-child(1), table td:nth-child(4):nth-last-child(1) { width: 43% !important; min-width: 0 !important; }
|
| 524 |
+
|
| 525 |
+
/* 5컬럼 */
|
| 526 |
+
table th:nth-child(1):nth-last-child(5), table td:nth-child(1):nth-last-child(5) { width: 15% !important; min-width: 0 !important; }
|
| 527 |
+
table th:nth-child(2):nth-last-child(4), table td:nth-child(2):nth-last-child(4) { width: 10% !important; min-width: 0 !important; }
|
| 528 |
+
table th:nth-child(3):nth-last-child(3), table td:nth-child(3):nth-last-child(3) { width: 30% !important; min-width: 0 !important; }
|
| 529 |
+
table th:nth-child(4):nth-last-child(2), table td:nth-child(4):nth-last-child(2) { width: 10% !important; min-width: 0 !important; }
|
| 530 |
+
table th:nth-child(5):nth-last-child(1), table td:nth-child(5):nth-last-child(1) { width: 35% !important; min-width: 0 !important; }
|
| 531 |
+
|
| 532 |
+
/* 6컬럼 */
|
| 533 |
+
table th:nth-child(1):nth-last-child(6), table td:nth-child(1):nth-last-child(6) { width: 8% !important; min-width: 0 !important; }
|
| 534 |
+
table th:nth-child(2):nth-last-child(5), table td:nth-child(2):nth-last-child(5) { width: 25% !important; min-width: 0 !important; }
|
| 535 |
+
table th:nth-child(3):nth-last-child(4), table td:nth-child(3):nth-last-child(4) { width: 8% !important; min-width: 0 !important; }
|
| 536 |
+
table th:nth-child(4):nth-last-child(3), table td:nth-child(4):nth-last-child(3) { width: 25% !important; min-width: 0 !important; }
|
| 537 |
+
table th:nth-child(5):nth-last-child(2), table td:nth-child(5):nth-last-child(2) { width: 8% !important; min-width: 0 !important; }
|
| 538 |
+
table th:nth-child(6):nth-last-child(1), table td:nth-child(6):nth-last-child(1) { width: 26% !important; min-width: 0 !important; }
|
| 539 |
+
|
| 540 |
+
/* 기본 테이블 & 스크롤 설정 */
|
| 541 |
thead th {
|
| 542 |
font-size: 18px !important;
|
| 543 |
position: sticky;
|