QIDNLF commited on
Commit
ed0e29b
Β·
verified Β·
1 Parent(s): f8e9052

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +65 -125
app.py CHANGED
@@ -82,22 +82,22 @@ def update_base_category_dropdown(standard, version):
82
  conn = sqlite3.connect(db_path)
83
  tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
84
 
 
 
 
85
  main_table = f"{standard}_{version}"
86
- # [볡원] 메인 ν…Œμ΄λΈ” 이름 μ•ˆμ „μž₯치
87
- if main_table not in tables:
88
- main_table = tables[0] if tables else None
89
 
90
- # 1. 뢀속 ν…Œμ΄λΈ”(TableA λ“±) 리슀트 κ°€μ Έμ˜€κΈ°
91
  pattern = re.compile(f"^{standard}[_\\s-]*{version}[_\\s-]*", re.IGNORECASE)
92
- for t in tables:
93
- if t == main_table or t.lower() in ['mapping_registry', 'mapping_table', 'table_config']: continue
94
  short_name = pattern.sub("", t).strip(" _")
95
  if short_name and short_name not in choices:
96
  choices.append(short_name)
97
  elif t not in choices:
98
  choices.append(t)
99
 
100
- # 2. λ³Έλ¬Έ μ‘°ν•­ 리슀트(1. Scope λ“±) κ°€μ Έμ˜€κΈ°
101
  if main_table:
102
  cols = pd.read_sql(f"PRAGMA table_info([{main_table}])", conn)['name'].tolist()
103
  lower_cols = [c.lower() for c in cols]
@@ -109,7 +109,6 @@ def update_base_category_dropdown(standard, version):
109
  for _, row in df.iterrows():
110
  ch = str(row[ch_col]).strip()
111
  ca = str(row[ca_col]).strip()
112
- # [볡원] None, nan κ°’ 필터링
113
  if ch and ca and ch.lower() not in ['none', 'nan'] and ca.lower() not in ['none', 'nan']:
114
  choices.append(f"{ch}.{ca}")
115
  conn.close()
@@ -120,13 +119,11 @@ def update_base_category_dropdown(standard, version):
120
  return gr.update(choices=choices, value=None, interactive=True)
121
 
122
  def update_comp_standard_dropdown(base_std, base_ver):
123
- """κΈ°μ€€ λ²•κ·œ 선택 μ‹œ, 맀핑이 ν—ˆμš©λœ μƒλŒ€ Standard만 λ°˜ν™˜"""
124
  if not base_std or not base_ver:
125
  return gr.Dropdown(choices=[], value=None, interactive=False)
126
 
127
  try:
128
  conn = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
129
- # Registry에 λ“±λ‘λœ 쌍방ν–₯(Base<->Comp) λ§€ν•‘ Standard만 μΆ”μΆœ
130
  query = """
131
  SELECT DISTINCT Comp_std FROM Mapping_registry WHERE Base_std=? AND Base_ver=?
132
  UNION
@@ -136,14 +133,11 @@ def update_comp_standard_dropdown(base_std, base_ver):
136
  conn.close()
137
 
138
  mapped_stds = sorted(df['Comp_std'].dropna().unique().tolist()) if not df.empty else []
139
-
140
- # λ§€ν•‘λœ 것이 μ—†μœΌλ©΄ 선택 λΆˆκ°€ μƒνƒœλ‘œ λ°˜ν™˜
141
  return gr.update(choices=mapped_stds, value=None, interactive=bool(mapped_stds))
142
  except Exception:
143
  return gr.update(choices=[], value=None, interactive=False)
144
 
145
  def update_comp_version_dropdown(base_std, base_ver, comp_std):
146
- """μ„ νƒλœ 비ꡐ Standard에 λŒ€ν•΄, 맀핑이 ν—ˆμš©λœ λ²„μ „λ§Œ λ°˜ν™˜"""
147
  if not all([base_std, base_ver, comp_std]):
148
  return gr.update(choices=[], value=None, interactive=False)
149
 
@@ -166,12 +160,11 @@ def update_comp_category_dropdown(base_std, base_ver, base_cat, comp_std, comp_v
166
  if not all([base_std, base_ver, base_cat, comp_std, comp_ver]):
167
  return gr.update(choices=[], value=None, interactive=False)
168
 
169
- # κΈ°μ€€ λ²•κ·œμ˜ νƒ€μž… νŒλ³„
170
- base_type = "Main" if base_cat == "ALL" or (base_cat and base_cat[0].isdigit() and "." in base_cat) else base_cat
171
 
172
  try:
173
  conn = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
174
- # Registryμ—μ„œ ν—ˆμš©λœ Comp_Type 쑰회
175
  query = "SELECT DISTINCT Comp_Type FROM Mapping_registry WHERE Base_std=? AND Base_ver=? AND Base_Type=? AND Comp_std=? AND Comp_ver=?"
176
  df = pd.read_sql(query, conn, params=[base_std, base_ver, base_type, comp_std, comp_ver])
177
  conn.close()
@@ -181,8 +174,6 @@ def update_comp_category_dropdown(base_std, base_ver, base_cat, comp_std, comp_v
181
  return gr.update(choices=[], value=None)
182
 
183
  final_choices = []
184
-
185
- # [핡심 μˆ˜μ •] Comp_Type이 'Main'인 경우, μ‹€μ œ DBλ₯Ό μ—΄μ–΄μ„œ ALLκ³Ό μ„ΈλΆ€ 쑰항을 κ°€μ Έμ˜΄!
186
  if "Main" in allowed_types:
187
  final_choices.append("ALL")
188
  comp_db_path = os.path.join(UPLOAD_DIR, f"{comp_std}_{comp_ver}.db")
@@ -190,10 +181,11 @@ def update_comp_category_dropdown(base_std, base_ver, base_cat, comp_std, comp_v
190
  if os.path.exists(comp_db_path):
191
  c_conn = sqlite3.connect(comp_db_path)
192
  c_tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", c_conn)['name'].tolist()
 
193
 
194
  c_main_table = f"{comp_std}_{comp_ver}"
195
- if c_main_table not in c_tables:
196
- c_main_table = c_tables[0] if c_tables else None
197
 
198
  if c_main_table:
199
  cols = pd.read_sql(f"PRAGMA table_info([{c_main_table}])", c_conn)['name'].tolist()
@@ -210,10 +202,8 @@ def update_comp_category_dropdown(base_std, base_ver, base_cat, comp_std, comp_v
210
  final_choices.append(f"{ch}.{ca}")
211
  c_conn.close()
212
 
213
- # Main이 μ•„λ‹Œ λ‹€λ₯Έ νƒ€μž…(TableA λ“±)은 κ·ΈλŒ€λ‘œ λͺ©λ‘μ— μΆ”κ°€
214
  for t in allowed_types:
215
- if t != "Main":
216
- final_choices.append(t)
217
 
218
  return gr.update(choices=final_choices, value=final_choices[0] if final_choices else None, interactive=True)
219
 
@@ -293,14 +283,18 @@ def fetch_database_records(standard, version, selection, table_type):
293
  anchor_cols_config = [x.strip() for x in anchor_col_config.split(',')]
294
 
295
  tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
 
 
 
 
296
  main_table = f"{standard}_{version}"
297
- if main_table not in tables:
298
- main_table = tables[0]
299
 
300
  target_table = None
301
  pattern = re.compile(f"^{standard}[_\\s-]*{version}[_\\s-]*", re.IGNORECASE)
302
 
303
- for t in tables:
304
  if t == selection or pattern.sub("", t).strip(" _") == selection:
305
  target_table = t
306
  break
@@ -308,6 +302,9 @@ def fetch_database_records(standard, version, selection, table_type):
308
  if target_table and target_table != main_table:
309
  df = pd.read_sql(f"SELECT * FROM [{target_table}]", conn)
310
  else:
 
 
 
311
  cursor = conn.cursor()
312
  cursor.execute(f"PRAGMA table_info([{main_table}])")
313
  cols = [c[1] for c in cursor.fetchall()]
@@ -361,20 +358,18 @@ def fetch_database_records(standard, version, selection, table_type):
361
 
362
  def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat):
363
  try:
364
- # [핡심 μˆ˜μ •] μ™Όμͺ½(Base)κ³Ό 였λ₯Έμͺ½(Comp)의 νƒ€μž…μ„ 각각 λ…λ¦½μ μœΌλ‘œ νŒλ‹¨
365
  def get_type_by_cat(cat):
366
- if not cat: return "Main"
367
- is_main = cat == "ALL" or (cat[0].isdigit() and "." in cat)
368
- return "Main" if is_main else cat
369
 
370
  type_b = get_type_by_cat(base_cat)
371
  type_c = get_type_by_cat(comp_cat)
372
 
373
- # 각각의 κΈ°μ€€ μ—΄(Anchor) 정보λ₯Ό κ°€μ Έμ˜΄
374
  anchor_col_b, _ = get_table_config(type_b)
375
  anchor_col_c, _ = get_table_config(type_c)
376
 
377
- # μ‹œκ°μ  쀑볡 제거 (폭포수 방식)
378
  def apply_visual_merge(df, cols):
379
  if not df.empty and len(cols) > 1:
380
  is_dup = pd.Series([True] * len(df), index=df.index)
@@ -386,7 +381,6 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
386
  df.loc[is_dup, col] = "&nbsp;"
387
  return df
388
 
389
- # Code/Description κ²°ν•© 둜직
390
  def combine_code_desc(df):
391
  cols = list(df.columns)
392
  new_cols = []
@@ -418,7 +412,7 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
418
  df = combine_code_desc(df)
419
  return apply_visual_merge(df, df.columns)
420
 
421
- # 2. 비ꡐ 쑰회 (μ„œλ‘œ λ‹€λ₯Έ νƒ€μž… 지원)
422
  if all([base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat]):
423
  df_base, real_anchors_b = fetch_database_records(base_std, base_ver, base_cat, type_b)
424
  df_comp, real_anchors_c = fetch_database_records(comp_std, comp_ver, comp_cat, type_c)
@@ -426,11 +420,17 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
426
  if "Error" in df_base.columns: return df_base
427
  if "Error" in df_comp.columns: return df_comp
428
 
429
- # 각자의 액컀 리슀트λ₯Ό μ‚¬μš©ν•˜μ—¬ merge_key 생성
 
 
 
 
 
 
 
430
  df_base['merge_key'] = df_base[real_anchors_b].apply(lambda row: '-'.join(row.values.astype(str)), axis=1).str.replace(" ", "")
431
  df_comp['merge_key'] = df_comp[real_anchors_c].apply(lambda row: '-'.join(row.values.astype(str)), axis=1).str.replace(" ", "")
432
 
433
- # λ§€ν•‘ λΈŒλ¦Ώμ§€ λ‘œλ“œ
434
  conn_map = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
435
  q = "SELECT Base_section, Comp_section FROM Mapping_table WHERE TRIM(Base_std)=? AND TRIM(Base_ver)=? AND TRIM(Comp_std)=? AND TRIM(Comp_ver)=?"
436
  df_fw = pd.read_sql(q, conn_map, params=[base_std.strip(), base_ver.strip(), comp_std.strip(), comp_ver.strip()])
@@ -448,12 +448,10 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
448
  else:
449
  df_mapping = pd.DataFrame(columns=['Base_section', 'Comp_section'])
450
 
451
- # μ•”μ‹œμ  λ§€ν•‘ (ν‚€κ°€ μ™„μ „νžˆ λ˜‘κ°™μ€ 경우)
452
  implicit = pd.DataFrame({'Base_section': list(set(df_base['merge_key']) & set(df_comp['merge_key'])),
453
  'Comp_section': list(set(df_base['merge_key']) & set(df_comp['merge_key']))})
454
  bridge = pd.concat([df_mapping, implicit], ignore_index=True).drop_duplicates()
455
 
456
- # 컬럼λͺ… 정리 및 병합
457
  rename_b = {c: f"{c}_{base_ver}" for c in df_base.columns if c != 'merge_key'}
458
  df_base = df_base.rename(columns=rename_b)
459
  rename_c = {c: f"{c}_{comp_ver}" for c in df_comp.columns if c != 'merge_key'}
@@ -462,13 +460,17 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
462
  df_base['base_idx'], df_comp['comp_idx'] = range(len(df_base)), range(len(df_comp))
463
  merged = pd.merge(bridge, df_base, left_on='Base_section', right_on='merge_key', how='outer')
464
  merged = pd.merge(merged, df_comp, left_on='Comp_section', right_on='merge_key', how='outer')
 
 
 
465
  merged = merged.sort_values(['base_idx', 'comp_idx'])
466
 
467
- # κ²°κ³Ό ν–‰ 쑰립 및 Diff
468
  result_rows = []
469
  for _, row in merged.iterrows():
470
  row_dict = {}
471
- has_b, has_c = not pd.isna(row.get('base_idx')), not pd.isna(row.get('comp_idx'))
 
 
472
  for c in rename_b.values(): row_dict[c] = str(row[c]) if has_b and not pd.isna(row[c]) else ""
473
  for c in rename_c.values(): row_dict[c] = str(row[c]) if has_c and not pd.isna(row[c]) else ""
474
 
@@ -481,16 +483,24 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
481
  result_rows.append(row_dict)
482
 
483
  final_df = combine_code_desc(pd.DataFrame(result_rows))
484
- return apply_visual_merge(final_df, final_df.columns)
 
 
 
 
 
 
 
485
 
486
  return pd.DataFrame({"Info": ["쑰건을 μ„ νƒν•˜μ„Έμš”."]})
487
  except Exception as e:
 
488
  traceback.print_exc()
489
- return pd.DataFrame({"Error": [str(e)]})
490
 
491
 
492
  # ==========================================
493
- # 4. UI Layout & Event Binding (ν™”λ©΄ ꡬ성 및 이벀트 μ—°κ²°)
494
  # ==========================================
495
  with gr.Blocks() as demo:
496
  gr.Markdown("# πŸ“œ Regulation Viewer")
@@ -505,7 +515,6 @@ with gr.Blocks() as demo:
505
 
506
  with gr.Accordion("πŸ”„ 비ꡐ λ²•κ·œ", open=False):
507
  with gr.Column():
508
- # μ΄ˆκΈ°μ—λŠ” 선택지λ₯Ό λΉ„μ›Œλ‘  (κΈ°μ€€ λ²•κ·œ 선택 μ „κΉŒμ§€ οΏ½οΏ½μ €νžˆ ν†΅μ œ)
509
  comp_standard = gr.Dropdown(label="Standard", choices=[])
510
  comp_version = gr.Dropdown(label="Version", choices=[])
511
  comp_category = gr.Dropdown(label="Category", choices=[])
@@ -514,93 +523,35 @@ with gr.Blocks() as demo:
514
  search_btn = gr.Button("πŸ” 쑰회", variant="primary")
515
  output_df = gr.Dataframe(wrap=True, interactive=False, datatype="html", max_height=800)
516
 
517
-
518
  # --------------------------------------------------------
519
- # πŸ”— 이벀트 바인딩 (데이터 흐름도)
520
  # --------------------------------------------------------
521
-
522
- # [0] μ•± 초기 λ‘œλ“œ
523
  demo.load(fn=load_initial_standards, inputs=None, outputs=base_standard)
524
 
525
- # --------------------------------------------------------
526
- # [1] κΈ°μ€€ λ²•κ·œ 흐름 (Base Flow)
527
- # --------------------------------------------------------
528
- # β‘  κΈ°μ€€ Standard 선택 -> κΈ°μ€€ Version λͺ©λ‘ κ°±μ‹ 
529
- base_standard.change(
530
- fn=update_version_dropdown,
531
- inputs=[base_standard],
532
- outputs=[base_version]
533
- )
534
- # β‘‘ κΈ°μ€€ Version 선택 -> κΈ°μ€€ Category λͺ©λ‘ κ°±μ‹  (DB 전체 λͺ©λ‘)
535
- base_version.change(
536
- fn=update_base_category_dropdown,
537
- inputs=[base_standard, base_version],
538
- outputs=[base_category]
539
- )
540
 
541
- # --------------------------------------------------------
542
- # [2] 비ꡐ λ²•κ·œ ν†΅μ œ 흐름 (Base -> Comp Flow)
543
- # --------------------------------------------------------
544
- # β‘’ κΈ°μ€€ Version이 κ²°μ •λ˜λ©΄ -> ν—ˆμš©λœ '비ꡐ Standard' λͺ©λ‘ κ°±μ‹ 
545
- base_version.change(
546
- fn=update_comp_standard_dropdown,
547
- inputs=[base_standard, base_version],
548
- outputs=[comp_standard]
549
- )
550
- # β‘£ 비ꡐ Standard 선택 -> ν—ˆμš©λœ '비ꡐ Version' λͺ©λ‘ κ°±μ‹ 
551
- comp_standard.change(
552
- fn=update_comp_version_dropdown,
553
- inputs=[base_standard, base_version, comp_standard],
554
- outputs=[comp_version]
555
- )
556
 
557
- # --------------------------------------------------------
558
- # [3] μ΅œμ’… μΉ΄ν…Œκ³ λ¦¬ ν†΅μ œ (Registry 필터링)
559
- # --------------------------------------------------------
560
- # β‘€ κΈ°μ€€ Categoryκ°€ λ°”λ€Œκ±°λ‚˜, 비ꡐ Version이 λ°”λ€Œλ©΄ -> ν—ˆμš©λœ '비ꡐ Category' λͺ©λ‘ κ°±μ‹ 
561
  comp_change_triggers = [base_standard, base_version, base_category, comp_standard, comp_version]
562
-
563
- base_category.change(
564
- fn=update_comp_category_dropdown,
565
- inputs=comp_change_triggers,
566
- outputs=[comp_category]
567
- )
568
- comp_version.change(
569
- fn=update_comp_category_dropdown,
570
- inputs=comp_change_triggers,
571
- outputs=[comp_category]
572
- )
573
 
574
- # --------------------------------------------------------
575
- # [4] λ²„νŠΌ λ™μž‘ (Action)
576
- # --------------------------------------------------------
577
  search_btn.click(
578
  fn=execute_unified_search,
579
  inputs=[base_standard, base_version, base_category, comp_standard, comp_version, comp_category],
580
  outputs=[output_df]
581
  )
582
 
583
- base_reset_btn.click(
584
- fn=reset_base_selections,
585
- inputs=None,
586
- outputs=[base_standard, base_version, base_category]
587
- )
588
-
589
- comp_reset_btn.click(
590
- fn=reset_comp_selections,
591
- inputs=None,
592
- outputs=[comp_standard, comp_version, comp_category]
593
- )
594
 
595
 
596
  # ==========================================
597
  # 5. Application Styling (CSS)
598
  # ==========================================
599
  css = """
600
- /* ----------------------------------------------------
601
- [1] κΈ°λ³Έ μ„€μ • (7μ—΄ 이상 닀쀑 μ—΄ κΈ°μ€€)
602
- - 열이 λ§Žμ„ 경우 100%λ₯Ό λ„˜μ–΄μ„œ μžμ—°μŠ€λŸ½κ²Œ μŠ€ν¬λ‘€λ˜λ„λ‘ μ„€μ •
603
- ---------------------------------------------------- */
604
  table {
605
  table-layout: auto !important;
606
  width: max-content !important;
@@ -608,13 +559,9 @@ table {
608
  }
609
 
610
  th, td {
611
- min-width: 150px; /* 열이 λ§Žμ•„λ„ κΈ€μžκ°€ κΉ¨μ§€μ§€ μ•Šκ²Œ μ΅œμ†Œ λ„ˆλΉ„ λ°©μ–΄ */
612
  }
613
 
614
- /* ----------------------------------------------------
615
- [2] 2, 4, 5, 6μ—΄ 쑰건뢀 μ„€μ • (μ§€μ •ν•˜μ‹  λΉ„μœ¨ μ™„λ²½ 볡ꡬ)
616
- - ν•΄λ‹Ή μ—΄ 개수일 λ•Œλ§Œ κ³ μ • λ ˆμ΄μ•„μ›ƒ(fixed) 적용
617
- ---------------------------------------------------- */
618
  table:has(th:nth-last-child(2):first-child),
619
  table:has(th:nth-last-child(4):first-child),
620
  table:has(th:nth-last-child(5):first-child),
@@ -623,24 +570,20 @@ table:has(th:nth-last-child(6):first-child) {
623
  width: 100% !important;
624
  }
625
 
626
- /* 2컬럼 */
627
  table th:nth-last-child(2):first-child, table td:nth-last-child(2):first-child { width: 15% !important; min-width: 0 !important; }
628
  table th:nth-last-child(1), table td:nth-last-child(1) { width: 85% !important; min-width: 0 !important; }
629
 
630
- /* 4컬럼 */
631
  table th:nth-child(1):nth-last-child(4), table td:nth-child(1):nth-last-child(4) { width: 10% !important; min-width: 0 !important; }
632
  table th:nth-child(2):nth-last-child(3), table td:nth-child(2):nth-last-child(3) { width: 40% !important; min-width: 0 !important; }
633
  table th:nth-child(3):nth-last-child(2), table td:nth-child(3):nth-last-child(2) { width: 10% !important; min-width: 0 !important; }
634
  table th:nth-child(4):nth-last-child(1), table td:nth-child(4):nth-last-child(1) { width: 40% !important; min-width: 0 !important; }
635
 
636
- /* 5컬럼 */
637
  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; }
638
  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; }
639
  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; }
640
  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; }
641
  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; }
642
 
643
- /* 6컬럼 */
644
  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; }
645
  table th:nth-child(2):nth-last-child(5), table td:nth-child(2):nth-last-child(5) { width: 15% !important; min-width: 0 !important; }
646
  table th:nth-child(3):nth-last-child(4), table td:nth-child(3):nth-last-child(4) { width: 27% !important; min-width: 0 !important; }
@@ -648,9 +591,6 @@ table th:nth-child(4):nth-last-child(3), table td:nth-child(4):nth-last-child(3)
648
  table th:nth-child(5):nth-last-child(2), table td:nth-child(5):nth-last-child(2) { width: 15% !important; min-width: 0 !important; }
649
  table th:nth-child(6):nth-last-child(1), table td:nth-child(6):nth-last-child(1) { width: 27% !important; min-width: 0 !important; }
650
 
651
- /* ----------------------------------------------------
652
- [3] λ°μ΄ν„°ν”„λ ˆμž„ 및 헀더 κΈ°λ³Έ 슀크둀/ν…μŠ€νŠΈ μ„€μ •
653
- ---------------------------------------------------- */
654
  thead th {
655
  font-size: 18px !important;
656
  position: sticky;
@@ -661,7 +601,7 @@ thead th {
661
  .dataframe {
662
  max-height: none !important;
663
  overflow-y: visible !important;
664
- overflow-x: auto !important; /* 쒌우 μŠ€ν¬λ‘€λ°” ν—ˆμš© */
665
  display: block;
666
  }
667
  .dataframe > div {
@@ -671,7 +611,7 @@ thead th {
671
  td {
672
  font-size: 18px !important;
673
  white-space: pre-wrap !important;
674
- word-break: keep-all !important; /* μ•ŒνŒŒλ²³μ΄ ν•˜λ‚˜μ”© μ°’μ–΄μ§€λŠ” ν˜„μƒ λ°©μ§€ */
675
  line-height: 1.6;
676
  padding: 10px;
677
  vertical-align: top !important;
 
82
  conn = sqlite3.connect(db_path)
83
  tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
84
 
85
+ # [패치] μ‹œμŠ€ν…œ/μ„€μ • ν…Œμ΄λΈ” μ™„λ²½ μ œμ™Έ
86
+ valid_tables = [t for t in tables if t.lower() not in ['mapping_registry', 'mapping_table', 'table_config', 'sqlite_sequence']]
87
+
88
  main_table = f"{standard}_{version}"
89
+ if main_table not in valid_tables:
90
+ main_table = valid_tables[0] if valid_tables else None
 
91
 
 
92
  pattern = re.compile(f"^{standard}[_\\s-]*{version}[_\\s-]*", re.IGNORECASE)
93
+ for t in valid_tables:
94
+ if t == main_table: continue
95
  short_name = pattern.sub("", t).strip(" _")
96
  if short_name and short_name not in choices:
97
  choices.append(short_name)
98
  elif t not in choices:
99
  choices.append(t)
100
 
 
101
  if main_table:
102
  cols = pd.read_sql(f"PRAGMA table_info([{main_table}])", conn)['name'].tolist()
103
  lower_cols = [c.lower() for c in cols]
 
109
  for _, row in df.iterrows():
110
  ch = str(row[ch_col]).strip()
111
  ca = str(row[ca_col]).strip()
 
112
  if ch and ca and ch.lower() not in ['none', 'nan'] and ca.lower() not in ['none', 'nan']:
113
  choices.append(f"{ch}.{ca}")
114
  conn.close()
 
119
  return gr.update(choices=choices, value=None, interactive=True)
120
 
121
  def update_comp_standard_dropdown(base_std, base_ver):
 
122
  if not base_std or not base_ver:
123
  return gr.Dropdown(choices=[], value=None, interactive=False)
124
 
125
  try:
126
  conn = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
 
127
  query = """
128
  SELECT DISTINCT Comp_std FROM Mapping_registry WHERE Base_std=? AND Base_ver=?
129
  UNION
 
133
  conn.close()
134
 
135
  mapped_stds = sorted(df['Comp_std'].dropna().unique().tolist()) if not df.empty else []
 
 
136
  return gr.update(choices=mapped_stds, value=None, interactive=bool(mapped_stds))
137
  except Exception:
138
  return gr.update(choices=[], value=None, interactive=False)
139
 
140
  def update_comp_version_dropdown(base_std, base_ver, comp_std):
 
141
  if not all([base_std, base_ver, comp_std]):
142
  return gr.update(choices=[], value=None, interactive=False)
143
 
 
160
  if not all([base_std, base_ver, base_cat, comp_std, comp_ver]):
161
  return gr.update(choices=[], value=None, interactive=False)
162
 
163
+ # [패치] κ΅­λ¬Έ μ‘°ν•­("제1μž₯.총칙") 등도 μ™„λ²½νžˆ Main으둜 νŒλ³„
164
+ base_type = "Main" if not base_cat or base_cat == "ALL" or "." in base_cat else base_cat
165
 
166
  try:
167
  conn = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
 
168
  query = "SELECT DISTINCT Comp_Type FROM Mapping_registry WHERE Base_std=? AND Base_ver=? AND Base_Type=? AND Comp_std=? AND Comp_ver=?"
169
  df = pd.read_sql(query, conn, params=[base_std, base_ver, base_type, comp_std, comp_ver])
170
  conn.close()
 
174
  return gr.update(choices=[], value=None)
175
 
176
  final_choices = []
 
 
177
  if "Main" in allowed_types:
178
  final_choices.append("ALL")
179
  comp_db_path = os.path.join(UPLOAD_DIR, f"{comp_std}_{comp_ver}.db")
 
181
  if os.path.exists(comp_db_path):
182
  c_conn = sqlite3.connect(comp_db_path)
183
  c_tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", c_conn)['name'].tolist()
184
+ c_valid_tables = [t for t in c_tables if t.lower() not in ['mapping_registry', 'mapping_table', 'table_config', 'sqlite_sequence']]
185
 
186
  c_main_table = f"{comp_std}_{comp_ver}"
187
+ if c_main_table not in c_valid_tables:
188
+ c_main_table = c_valid_tables[0] if c_valid_tables else None
189
 
190
  if c_main_table:
191
  cols = pd.read_sql(f"PRAGMA table_info([{c_main_table}])", c_conn)['name'].tolist()
 
202
  final_choices.append(f"{ch}.{ca}")
203
  c_conn.close()
204
 
 
205
  for t in allowed_types:
206
+ if t != "Main": final_choices.append(t)
 
207
 
208
  return gr.update(choices=final_choices, value=final_choices[0] if final_choices else None, interactive=True)
209
 
 
283
  anchor_cols_config = [x.strip() for x in anchor_col_config.split(',')]
284
 
285
  tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
286
+
287
+ # [패치] SQLite μ‹œμŠ€ν…œ ν…Œμ΄λΈ” λ¬΄μ‹œ 둜직 적용
288
+ valid_tables = [t for t in tables if t.lower() not in ['mapping_registry', 'mapping_table', 'table_config', 'sqlite_sequence']]
289
+
290
  main_table = f"{standard}_{version}"
291
+ if main_table not in valid_tables:
292
+ main_table = valid_tables[0] if valid_tables else None
293
 
294
  target_table = None
295
  pattern = re.compile(f"^{standard}[_\\s-]*{version}[_\\s-]*", re.IGNORECASE)
296
 
297
+ for t in valid_tables:
298
  if t == selection or pattern.sub("", t).strip(" _") == selection:
299
  target_table = t
300
  break
 
302
  if target_table and target_table != main_table:
303
  df = pd.read_sql(f"SELECT * FROM [{target_table}]", conn)
304
  else:
305
+ if not main_table:
306
+ return pd.DataFrame({"Error": ["λ°μ΄ν„°λ² μ΄μŠ€ λ‚΄μ—μ„œ 메인 ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€."]}), []
307
+
308
  cursor = conn.cursor()
309
  cursor.execute(f"PRAGMA table_info([{main_table}])")
310
  cols = [c[1] for c in cursor.fetchall()]
 
358
 
359
  def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat):
360
  try:
361
+ # [패치] κ΅­λ¬Έ μ‘°ν•­ 식별 κ°•ν™”
362
  def get_type_by_cat(cat):
363
+ if not cat or cat == "ALL": return "Main"
364
+ if "." in cat: return "Main"
365
+ return cat
366
 
367
  type_b = get_type_by_cat(base_cat)
368
  type_c = get_type_by_cat(comp_cat)
369
 
 
370
  anchor_col_b, _ = get_table_config(type_b)
371
  anchor_col_c, _ = get_table_config(type_c)
372
 
 
373
  def apply_visual_merge(df, cols):
374
  if not df.empty and len(cols) > 1:
375
  is_dup = pd.Series([True] * len(df), index=df.index)
 
381
  df.loc[is_dup, col] = "&nbsp;"
382
  return df
383
 
 
384
  def combine_code_desc(df):
385
  cols = list(df.columns)
386
  new_cols = []
 
412
  df = combine_code_desc(df)
413
  return apply_visual_merge(df, df.columns)
414
 
415
+ # 2. 비ꡐ 쑰회
416
  if all([base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat]):
417
  df_base, real_anchors_b = fetch_database_records(base_std, base_ver, base_cat, type_b)
418
  df_comp, real_anchors_c = fetch_database_records(comp_std, comp_ver, comp_cat, type_c)
 
420
  if "Error" in df_base.columns: return df_base
421
  if "Error" in df_comp.columns: return df_comp
422
 
423
+ # [패치] Anchor κ²°μΈ‘ μ‹œ UI μΉœν™”μ  μ—λŸ¬ λ©”μ‹œμ§€ 볡원
424
+ for ra in real_anchors_b:
425
+ if ra not in df_base.columns:
426
+ return pd.DataFrame({"Error": [f"κΈ°μ€€ μ—΄(Anchor) '{ra}'이(κ°€) κΈ°μ€€ 데이터에 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. Table_Configλ₯Ό ν™•μΈν•˜μ„Έμš”."]})
427
+ for ra in real_anchors_c:
428
+ if ra not in df_comp.columns:
429
+ return pd.DataFrame({"Error": [f"비ꡐ μ—΄(Anchor) '{ra}'이(κ°€) 비ꡐ 데이터에 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. Table_Configλ₯Ό ν™•μΈν•˜μ„Έμš”."]})
430
+
431
  df_base['merge_key'] = df_base[real_anchors_b].apply(lambda row: '-'.join(row.values.astype(str)), axis=1).str.replace(" ", "")
432
  df_comp['merge_key'] = df_comp[real_anchors_c].apply(lambda row: '-'.join(row.values.astype(str)), axis=1).str.replace(" ", "")
433
 
 
434
  conn_map = sqlite3.connect(os.path.join(UPLOAD_DIR, "mapping.db"))
435
  q = "SELECT Base_section, Comp_section FROM Mapping_table WHERE TRIM(Base_std)=? AND TRIM(Base_ver)=? AND TRIM(Comp_std)=? AND TRIM(Comp_ver)=?"
436
  df_fw = pd.read_sql(q, conn_map, params=[base_std.strip(), base_ver.strip(), comp_std.strip(), comp_ver.strip()])
 
448
  else:
449
  df_mapping = pd.DataFrame(columns=['Base_section', 'Comp_section'])
450
 
 
451
  implicit = pd.DataFrame({'Base_section': list(set(df_base['merge_key']) & set(df_comp['merge_key'])),
452
  'Comp_section': list(set(df_base['merge_key']) & set(df_comp['merge_key']))})
453
  bridge = pd.concat([df_mapping, implicit], ignore_index=True).drop_duplicates()
454
 
 
455
  rename_b = {c: f"{c}_{base_ver}" for c in df_base.columns if c != 'merge_key'}
456
  df_base = df_base.rename(columns=rename_b)
457
  rename_c = {c: f"{c}_{comp_ver}" for c in df_comp.columns if c != 'merge_key'}
 
460
  df_base['base_idx'], df_comp['comp_idx'] = range(len(df_base)), range(len(df_comp))
461
  merged = pd.merge(bridge, df_base, left_on='Base_section', right_on='merge_key', how='outer')
462
  merged = pd.merge(merged, df_comp, left_on='Comp_section', right_on='merge_key', how='outer')
463
+
464
+ merged['base_idx'] = merged['base_idx'].fillna(float('inf'))
465
+ merged['comp_idx'] = merged['comp_idx'].fillna(float('inf'))
466
  merged = merged.sort_values(['base_idx', 'comp_idx'])
467
 
 
468
  result_rows = []
469
  for _, row in merged.iterrows():
470
  row_dict = {}
471
+ has_b = not pd.isna(row.get('base_idx')) and row.get('base_idx') != float('inf')
472
+ has_c = not pd.isna(row.get('comp_idx')) and row.get('comp_idx') != float('inf')
473
+
474
  for c in rename_b.values(): row_dict[c] = str(row[c]) if has_b and not pd.isna(row[c]) else ""
475
  for c in rename_c.values(): row_dict[c] = str(row[c]) if has_c and not pd.isna(row[c]) else ""
476
 
 
483
  result_rows.append(row_dict)
484
 
485
  final_df = combine_code_desc(pd.DataFrame(result_rows))
486
+
487
+ b_cols_final = [c for c in final_df.columns if c.endswith(f"_{base_ver}")]
488
+ c_cols_final = [c for c in final_df.columns if c.endswith(f"_{comp_ver}")]
489
+
490
+ final_df = apply_visual_merge(final_df, b_cols_final)
491
+ final_df = apply_visual_merge(final_df, c_cols_final)
492
+
493
+ return final_df
494
 
495
  return pd.DataFrame({"Info": ["쑰건을 μ„ νƒν•˜μ„Έμš”."]})
496
  except Exception as e:
497
+ import traceback
498
  traceback.print_exc()
499
+ return pd.DataFrame({"Error": [f"μ‹œμŠ€ν…œ 였λ₯˜ λ°œμƒ: {str(e)}"]})
500
 
501
 
502
  # ==========================================
503
+ # 4. UI Layout & Event Binding
504
  # ==========================================
505
  with gr.Blocks() as demo:
506
  gr.Markdown("# πŸ“œ Regulation Viewer")
 
515
 
516
  with gr.Accordion("πŸ”„ 비ꡐ λ²•κ·œ", open=False):
517
  with gr.Column():
 
518
  comp_standard = gr.Dropdown(label="Standard", choices=[])
519
  comp_version = gr.Dropdown(label="Version", choices=[])
520
  comp_category = gr.Dropdown(label="Category", choices=[])
 
523
  search_btn = gr.Button("πŸ” 쑰회", variant="primary")
524
  output_df = gr.Dataframe(wrap=True, interactive=False, datatype="html", max_height=800)
525
 
 
526
  # --------------------------------------------------------
527
+ # πŸ”— 이벀트 바인딩
528
  # --------------------------------------------------------
 
 
529
  demo.load(fn=load_initial_standards, inputs=None, outputs=base_standard)
530
 
531
+ base_standard.change(fn=update_version_dropdown, inputs=[base_standard], outputs=[base_version])
532
+ base_version.change(fn=update_base_category_dropdown, inputs=[base_standard, base_version], outputs=[base_category])
 
 
 
 
 
 
 
 
 
 
 
 
 
533
 
534
+ base_version.change(fn=update_comp_standard_dropdown, inputs=[base_standard, base_version], outputs=[comp_standard])
535
+ comp_standard.change(fn=update_comp_version_dropdown, inputs=[base_standard, base_version, comp_standard], outputs=[comp_version])
 
 
 
 
 
 
 
 
 
 
 
 
 
536
 
 
 
 
 
537
  comp_change_triggers = [base_standard, base_version, base_category, comp_standard, comp_version]
538
+ base_category.change(fn=update_comp_category_dropdown, inputs=comp_change_triggers, outputs=[comp_category])
539
+ comp_version.change(fn=update_comp_category_dropdown, inputs=comp_change_triggers, outputs=[comp_category])
 
 
 
 
 
 
 
 
 
540
 
 
 
 
541
  search_btn.click(
542
  fn=execute_unified_search,
543
  inputs=[base_standard, base_version, base_category, comp_standard, comp_version, comp_category],
544
  outputs=[output_df]
545
  )
546
 
547
+ base_reset_btn.click(fn=reset_base_selections, inputs=None, outputs=[base_standard, base_version, base_category])
548
+ comp_reset_btn.click(fn=reset_comp_selections, inputs=None, outputs=[comp_standard, comp_version, comp_category])
 
 
 
 
 
 
 
 
 
549
 
550
 
551
  # ==========================================
552
  # 5. Application Styling (CSS)
553
  # ==========================================
554
  css = """
 
 
 
 
555
  table {
556
  table-layout: auto !important;
557
  width: max-content !important;
 
559
  }
560
 
561
  th, td {
562
+ min-width: 150px;
563
  }
564
 
 
 
 
 
565
  table:has(th:nth-last-child(2):first-child),
566
  table:has(th:nth-last-child(4):first-child),
567
  table:has(th:nth-last-child(5):first-child),
 
570
  width: 100% !important;
571
  }
572
 
 
573
  table th:nth-last-child(2):first-child, table td:nth-last-child(2):first-child { width: 15% !important; min-width: 0 !important; }
574
  table th:nth-last-child(1), table td:nth-last-child(1) { width: 85% !important; min-width: 0 !important; }
575
 
 
576
  table th:nth-child(1):nth-last-child(4), table td:nth-child(1):nth-last-child(4) { width: 10% !important; min-width: 0 !important; }
577
  table th:nth-child(2):nth-last-child(3), table td:nth-child(2):nth-last-child(3) { width: 40% !important; min-width: 0 !important; }
578
  table th:nth-child(3):nth-last-child(2), table td:nth-child(3):nth-last-child(2) { width: 10% !important; min-width: 0 !important; }
579
  table th:nth-child(4):nth-last-child(1), table td:nth-child(4):nth-last-child(1) { width: 40% !important; min-width: 0 !important; }
580
 
 
581
  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; }
582
  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; }
583
  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; }
584
  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; }
585
  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; }
586
 
 
587
  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; }
588
  table th:nth-child(2):nth-last-child(5), table td:nth-child(2):nth-last-child(5) { width: 15% !important; min-width: 0 !important; }
589
  table th:nth-child(3):nth-last-child(4), table td:nth-child(3):nth-last-child(4) { width: 27% !important; min-width: 0 !important; }
 
591
  table th:nth-child(5):nth-last-child(2), table td:nth-child(5):nth-last-child(2) { width: 15% !important; min-width: 0 !important; }
592
  table th:nth-child(6):nth-last-child(1), table td:nth-child(6):nth-last-child(1) { width: 27% !important; min-width: 0 !important; }
593
 
 
 
 
594
  thead th {
595
  font-size: 18px !important;
596
  position: sticky;
 
601
  .dataframe {
602
  max-height: none !important;
603
  overflow-y: visible !important;
604
+ overflow-x: auto !important;
605
  display: block;
606
  }
607
  .dataframe > div {
 
611
  td {
612
  font-size: 18px !important;
613
  white-space: pre-wrap !important;
614
+ word-break: keep-all !important;
615
  line-height: 1.6;
616
  padding: 10px;
617
  vertical-align: top !important;