Update app.py
Browse files
app.py
CHANGED
|
@@ -3,85 +3,102 @@ import sqlite3
|
|
| 3 |
import pandas as pd
|
| 4 |
import os
|
| 5 |
import re
|
| 6 |
-
import base64
|
| 7 |
|
| 8 |
# 1️⃣ 저장소 설정
|
| 9 |
UPLOAD_DIR = "uploaded_dbs"
|
| 10 |
if not os.path.exists(UPLOAD_DIR):
|
| 11 |
-
|
| 12 |
|
| 13 |
db_registry = []
|
| 14 |
|
| 15 |
-
# BLOB 데이터를 Base64 HTML 이미지 태그로 변환
|
| 16 |
def blob_to_base64_html(blob_data):
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
except:
|
| 27 |
-
return "[이미지 변환 에러]"
|
| 28 |
|
| 29 |
def natural_sort_key(s):
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
|
| 34 |
def refresh_registry_data():
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
|
| 50 |
# 2️⃣ UI 업데이트 함수
|
| 51 |
def on_load():
|
| 52 |
-
|
| 53 |
-
|
| 54 |
|
| 55 |
def update_version_dd(standard):
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
|
| 60 |
def update_category_dd(standard, version):
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
def display_data(standard, version, selection):
|
| 86 |
if not all([standard, version, selection]): return None
|
| 87 |
try:
|
|
@@ -90,67 +107,56 @@ def display_data(standard, version, selection):
|
|
| 90 |
all_tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
|
| 91 |
|
| 92 |
main_t = f"{standard}_{version}"
|
| 93 |
-
if main_t not in all_tables:
|
|
|
|
|
|
|
| 94 |
|
| 95 |
is_sub_table = False
|
| 96 |
if selection == "ALL":
|
| 97 |
-
df = pd.read_sql(f"SELECT * FROM [{main_t}]", conn)
|
| 98 |
elif "." in selection:
|
| 99 |
ch, ca = selection.split('.', 1)
|
| 100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
else:
|
| 102 |
is_sub_table = True
|
| 103 |
-
actual_table = next((t for t in all_tables if
|
| 104 |
-
df = pd.read_sql(f"SELECT * FROM [{actual_table}]", conn)
|
| 105 |
conn.close()
|
| 106 |
|
| 107 |
if not df.empty:
|
| 108 |
-
# 1. 먼저 BLOB(이미지) 데이터를 HTML로 변환
|
| 109 |
for col in df.columns:
|
| 110 |
df[col] = df[col].apply(blob_to_base64_html)
|
| 111 |
|
| 112 |
-
# 2. 정렬 로직 적용
|
| 113 |
-
sort_col = 'section' if 'section' in df.columns else df.columns[0]
|
| 114 |
-
df['sort_key'] = df[sort_col].apply(natural_sort_key)
|
| 115 |
-
df = df.sort_values(by='sort_key').drop(columns=['sort_key'])
|
| 116 |
-
|
| 117 |
-
# 3. 💡 [열 필터링 로직]
|
| 118 |
if not is_sub_table:
|
| 119 |
-
# 기본적으로 보여줄 열 정의
|
| 120 |
base_cols = [c for c in ['section', 'description'] if c in df.columns]
|
| 121 |
-
|
| 122 |
-
|
| 123 |
-
image_cols = [
|
| 124 |
-
c for c in df.columns
|
| 125 |
-
if c not in base_cols and df[c].astype(str).str.contains('<img', na=False).any()
|
| 126 |
-
]
|
| 127 |
-
|
| 128 |
-
# 최종적으로 section, description + 이미지 열만 표시
|
| 129 |
-
df = df[base_cols + image_cols]
|
| 130 |
-
else:
|
| 131 |
-
# 부속 테이블(Table A 등)은 모든 열을 보여주되, 정렬용 임시 열만 제거
|
| 132 |
-
df = df[[c for c in df.columns if not c.startswith('sort_')]]
|
| 133 |
|
| 134 |
return df
|
| 135 |
except Exception as e:
|
| 136 |
return pd.DataFrame({"Error": [f"조회 실패: {str(e)}"]})
|
| 137 |
-
|
| 138 |
# 4️⃣ UI 구성
|
| 139 |
-
with gr.Blocks() as demo:
|
| 140 |
-
|
| 141 |
|
| 142 |
-
|
| 143 |
-
|
| 144 |
-
|
| 145 |
-
|
| 146 |
|
| 147 |
-
|
| 148 |
-
output_df = gr.Dataframe(wrap=True, interactive=False, datatype="html")
|
| 149 |
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
|
| 155 |
if __name__ == "__main__":
|
| 156 |
-
|
|
|
|
| 3 |
import pandas as pd
|
| 4 |
import os
|
| 5 |
import re
|
| 6 |
+
import base64
|
| 7 |
|
| 8 |
# 1️⃣ 저장소 설정
|
| 9 |
UPLOAD_DIR = "uploaded_dbs"
|
| 10 |
if not os.path.exists(UPLOAD_DIR):
|
| 11 |
+
os.makedirs(UPLOAD_DIR)
|
| 12 |
|
| 13 |
db_registry = []
|
| 14 |
|
| 15 |
+
# BLOB 데이터를 Base64 HTML 이미지 태그로 변환
|
| 16 |
def blob_to_base64_html(blob_data):
|
| 17 |
+
if blob_data is None or pd.isna(blob_data):
|
| 18 |
+
return ""
|
| 19 |
+
try:
|
| 20 |
+
if isinstance(blob_data, (bytes, bytearray)):
|
| 21 |
+
encoded_string = base64.b64encode(blob_data).decode('utf-8')
|
| 22 |
+
return f'<img src="data:image/png;base64,{encoded_string}" width="200" height="auto" />'
|
| 23 |
+
return str(blob_data)
|
| 24 |
+
except:
|
| 25 |
+
return str(blob_data)
|
|
|
|
|
|
|
| 26 |
|
| 27 |
def natural_sort_key(s):
|
| 28 |
+
if s is None: return []
|
| 29 |
+
return [int(text) if text.isdigit() else text.lower()
|
| 30 |
+
for text in re.split(r'(\d+)', str(s))]
|
| 31 |
|
| 32 |
def refresh_registry_data():
|
| 33 |
+
global db_registry
|
| 34 |
+
db_registry = []
|
| 35 |
+
if not os.path.exists(UPLOAD_DIR): return []
|
| 36 |
+
db_files = [f for f in os.listdir(UPLOAD_DIR) if f.endswith(".db")]
|
| 37 |
+
for filename in db_files:
|
| 38 |
+
name_only = filename.replace(".db", "")
|
| 39 |
+
parts = name_only.split("_")
|
| 40 |
+
if len(parts) >= 2:
|
| 41 |
+
db_registry.append({
|
| 42 |
+
"path": os.path.join(UPLOAD_DIR, filename),
|
| 43 |
+
"standard": parts[0],
|
| 44 |
+
"version": parts[1]
|
| 45 |
+
})
|
| 46 |
+
return sorted(list(set([db["standard"] for db in db_registry])))
|
| 47 |
|
| 48 |
# 2️⃣ UI 업데이트 함수
|
| 49 |
def on_load():
|
| 50 |
+
standards = refresh_registry_data()
|
| 51 |
+
return gr.Dropdown(choices=standards, value=None)
|
| 52 |
|
| 53 |
def update_version_dd(standard):
|
| 54 |
+
if not standard: return gr.Dropdown(choices=[], value=None)
|
| 55 |
+
versions = sorted(list(set([db["version"] for db in db_registry if db["standard"] == standard])))
|
| 56 |
+
return gr.Dropdown(choices=versions, value=None)
|
| 57 |
|
| 58 |
def update_category_dd(standard, version):
|
| 59 |
+
if not standard or not version: return gr.Dropdown(choices=[], value=None)
|
| 60 |
+
choices = ["ALL"]
|
| 61 |
+
try:
|
| 62 |
+
target_db = next(db for db in db_registry if db["standard"] == standard and db["version"] == version)
|
| 63 |
+
conn = sqlite3.connect(target_db["path"])
|
| 64 |
+
all_tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
|
| 65 |
+
|
| 66 |
+
main_t = f"{standard}_{version}"
|
| 67 |
+
if main_t not in all_tables:
|
| 68 |
+
candidates = [t for t in all_tables if standard.lower() in t.lower() and version.lower() in t.lower()]
|
| 69 |
+
main_t = candidates[0] if candidates else all_tables[0]
|
| 70 |
+
|
| 71 |
+
cols_info = pd.read_sql(f"PRAGMA table_info([{main_t}])", conn)
|
| 72 |
+
orig_cols = cols_info['name'].tolist()
|
| 73 |
+
lower_cols = [c.lower() for c in orig_cols]
|
| 74 |
+
|
| 75 |
+
if 'chapter' in lower_cols and 'category' in lower_cols:
|
| 76 |
+
real_ch = orig_cols[lower_cols.index('chapter')]
|
| 77 |
+
real_cat = orig_cols[lower_cols.index('category')]
|
| 78 |
+
query = f"SELECT DISTINCT [{real_ch}], [{real_cat}] FROM [{main_t}] WHERE [{real_ch}] IS NOT NULL"
|
| 79 |
+
df_cat = pd.read_sql(query, conn)
|
| 80 |
+
|
| 81 |
+
cats = []
|
| 82 |
+
for _, row in df_cat.iterrows():
|
| 83 |
+
val_ch = str(row[real_ch]).strip()
|
| 84 |
+
val_cat = str(row[real_cat]).strip()
|
| 85 |
+
if val_ch:
|
| 86 |
+
cats.append(f"{val_ch}.{val_cat}")
|
| 87 |
+
|
| 88 |
+
cats.sort(key=natural_sort_key)
|
| 89 |
+
choices.extend(cats)
|
| 90 |
+
|
| 91 |
+
for t in all_tables:
|
| 92 |
+
if t == main_t: continue
|
| 93 |
+
clean_name = t.replace(f"{standard}_{version}_", "").replace(f"{standard}{version}_", "")
|
| 94 |
+
choices.append(clean_name)
|
| 95 |
+
|
| 96 |
+
conn.close()
|
| 97 |
+
except:
|
| 98 |
+
pass
|
| 99 |
+
return gr.Dropdown(choices=choices, value=None)
|
| 100 |
+
|
| 101 |
+
# 3️⃣ 데이터 조회 로직
|
| 102 |
def display_data(standard, version, selection):
|
| 103 |
if not all([standard, version, selection]): return None
|
| 104 |
try:
|
|
|
|
| 107 |
all_tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
|
| 108 |
|
| 109 |
main_t = f"{standard}_{version}"
|
| 110 |
+
if main_t not in all_tables:
|
| 111 |
+
candidates = [t for t in all_tables if standard.lower() in t.lower() and version.lower() in t.lower()]
|
| 112 |
+
main_t = candidates[0] if candidates else all_tables[0]
|
| 113 |
|
| 114 |
is_sub_table = False
|
| 115 |
if selection == "ALL":
|
| 116 |
+
df = pd.read_sql(f"SELECT * FROM [{main_t}] ORDER BY rowid", conn)
|
| 117 |
elif "." in selection:
|
| 118 |
ch, ca = selection.split('.', 1)
|
| 119 |
+
cols_info = pd.read_sql(f"PRAGMA table_info([{main_t}])", conn)
|
| 120 |
+
cols_lower = [c.lower() for c in cols_info['name'].tolist()]
|
| 121 |
+
real_ch = cols_info['name'].tolist()[cols_lower.index('chapter')]
|
| 122 |
+
real_cat = cols_info['name'].tolist()[cols_lower.index('category')]
|
| 123 |
+
|
| 124 |
+
df = pd.read_sql(f"SELECT * FROM [{main_t}] WHERE CAST([{real_ch}] AS TEXT)=? AND CAST([{real_cat}] AS TEXT)=? ORDER BY rowid",
|
| 125 |
+
conn, params=[ch, ca])
|
| 126 |
else:
|
| 127 |
is_sub_table = True
|
| 128 |
+
actual_table = next((t for t in all_tables if selection in t), selection)
|
| 129 |
+
df = pd.read_sql(f"SELECT * FROM [{actual_table}] ORDER BY rowid", conn)
|
| 130 |
conn.close()
|
| 131 |
|
| 132 |
if not df.empty:
|
|
|
|
| 133 |
for col in df.columns:
|
| 134 |
df[col] = df[col].apply(blob_to_base64_html)
|
| 135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 136 |
if not is_sub_table:
|
|
|
|
| 137 |
base_cols = [c for c in ['section', 'description'] if c in df.columns]
|
| 138 |
+
image_cols = [c for c in df.columns if c not in base_cols and '<img' in str(df[c].tolist())]
|
| 139 |
+
df = df[base_cols + [c for c in image_cols if c not in base_cols]]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 140 |
|
| 141 |
return df
|
| 142 |
except Exception as e:
|
| 143 |
return pd.DataFrame({"Error": [f"조회 실패: {str(e)}"]})
|
| 144 |
+
|
| 145 |
# 4️⃣ UI 구성
|
| 146 |
+
with gr.Blocks(theme=gr.themes.Soft()) as demo:
|
| 147 |
+
gr.Markdown("# 📜 Regulation Viewer")
|
| 148 |
|
| 149 |
+
with gr.Row():
|
| 150 |
+
standard_dd = gr.Dropdown(label="1. 법규 선택")
|
| 151 |
+
version_dd = gr.Dropdown(label="2. Version 선택")
|
| 152 |
+
category_dd = gr.Dropdown(label="3. Category / Table 선택")
|
| 153 |
|
| 154 |
+
output_df = gr.Dataframe(wrap=True, interactive=False, datatype="html")
|
|
|
|
| 155 |
|
| 156 |
+
demo.load(on_load, None, standard_dd)
|
| 157 |
+
standard_dd.change(update_version_dd, standard_dd, version_dd)
|
| 158 |
+
version_dd.change(update_category_dd, [standard_dd, version_dd], category_dd)
|
| 159 |
+
category_dd.change(display_data, [standard_dd, version_dd, category_dd], output_df)
|
| 160 |
|
| 161 |
if __name__ == "__main__":
|
| 162 |
+
demo.launch()
|