QIDNLF commited on
Commit
ea04e61
Β·
verified Β·
1 Parent(s): b14ff6c

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -133
app.py CHANGED
@@ -6,7 +6,9 @@ import re
6
  import base64
7
  import difflib
8
 
 
9
  # 1️⃣ μ €μž₯μ†Œ μ„€μ •
 
10
  UPLOAD_DIR = "uploaded_dbs"
11
  if not os.path.exists(UPLOAD_DIR):
12
  os.makedirs(UPLOAD_DIR)
@@ -14,15 +16,15 @@ if not os.path.exists(UPLOAD_DIR):
14
  db_registry = []
15
 
16
  # --------------------------
17
- # μœ ν‹Έ ν•¨μˆ˜
18
  # --------------------------
19
  def blob_to_base64_html(blob_data):
20
  if blob_data is None or pd.isna(blob_data):
21
  return ""
22
  try:
23
  if isinstance(blob_data, (bytes, bytearray)):
24
- encoded_string = base64.b64encode(blob_data).decode('utf-8')
25
- return f'<img src="data:image/png;base64,{encoded_string}" width="200" />'
26
  return str(blob_data)
27
  except:
28
  return str(blob_data)
@@ -30,39 +32,38 @@ def blob_to_base64_html(blob_data):
30
  def natural_sort_key(s):
31
  if s is None:
32
  return []
33
- return [int(text) if text.isdigit() else text.lower()
34
- for text in re.split(r'(\d+)', str(s))]
35
 
36
  def refresh_registry_data():
37
  global db_registry
38
  db_registry = []
39
- db_files = [f for f in os.listdir(UPLOAD_DIR) if f.endswith(".db")]
40
 
41
- for filename in db_files:
42
- name_only = filename.replace(".db", "")
43
- parts = name_only.split("_")
44
- if len(parts) >= 2:
45
- db_registry.append({
46
- "path": os.path.join(UPLOAD_DIR, filename),
47
- "standard": parts[0],
48
- "version": parts[1]
49
- })
 
50
 
51
- return sorted(list(set([db["standard"] for db in db_registry])))
52
 
53
  # --------------------------
54
  # λ“œλ‘­λ‹€μš΄
55
  # --------------------------
56
  def on_load():
57
- standards = refresh_registry_data()
58
- return gr.Dropdown(choices=standards)
59
 
60
  def update_version_dd(standard):
61
  if not standard:
62
  return gr.Dropdown(choices=[])
63
- versions = sorted(list(set([
64
- db["version"] for db in db_registry if db["standard"] == standard
65
- ])))
66
  return gr.Dropdown(choices=versions)
67
 
68
  def update_category_dd(standard, version):
@@ -72,29 +73,25 @@ def update_category_dd(standard, version):
72
  choices = ["ALL"]
73
 
74
  try:
75
- target_db = next(db for db in db_registry if db["standard"] == standard and db["version"] == version)
76
- conn = sqlite3.connect(target_db["path"])
77
 
78
  tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
79
  main_t = f"{standard}_{version}"
80
-
81
  if main_t not in tables:
82
  main_t = tables[0]
83
 
84
- cols_info = pd.read_sql(f"PRAGMA table_info([{main_t}])", conn)
85
- cols = cols_info['name'].tolist()
86
- lower_cols = [c.lower() for c in cols]
87
 
88
- if 'chapter' in lower_cols and 'category' in lower_cols:
89
- ch = cols[lower_cols.index('chapter')]
90
- ca = cols[lower_cols.index('category')]
91
 
92
  df = pd.read_sql(f"SELECT DISTINCT [{ch}], [{ca}] FROM [{main_t}]", conn)
93
 
94
- for _, row in df.iterrows():
95
- c = str(row[ch]).strip()
96
- cat = str(row[ca]).strip()
97
- choices.append(f"{c}.{cat}")
98
 
99
  conn.close()
100
  except:
@@ -103,7 +100,7 @@ def update_category_dd(standard, version):
103
  return gr.Dropdown(choices=choices)
104
 
105
  # --------------------------
106
- # μ΄ˆκΈ°ν™” ν•¨μˆ˜
107
  # --------------------------
108
  def reset_base():
109
  return None, None, None
@@ -112,51 +109,43 @@ def reset_comp():
112
  return None, None, None
113
 
114
  # --------------------------
115
- # πŸ”₯ diff ν•¨μˆ˜
116
  # --------------------------
117
- def highlight_diff(base_text, comp_text):
118
- if pd.isna(base_text): base_text = ""
119
- if pd.isna(comp_text): comp_text = ""
120
-
121
- base_words = str(base_text).split()
122
- comp_words = str(comp_text).split()
123
 
124
- d = list(difflib.ndiff(base_words, comp_words))
 
125
 
126
- base_result = []
127
- comp_result = []
128
 
 
129
  i = 0
 
130
  while i < len(d):
131
  code = d[i][0]
132
  word = d[i][2:]
133
 
134
  if code == ' ':
135
- base_result.append(word)
136
- comp_result.append(word)
137
-
138
- elif code == '-' and i + 1 < len(d) and d[i + 1][0] == '+':
139
- old_word = word
140
- new_word = d[i + 1][2:]
141
-
142
- base_result.append(f"<span style='color:#ff4d4f; font-weight:600'>{old_word}</span>")
143
- comp_result.append(f"<span style='color:#ff4d4f; font-weight:600'>{new_word}</span>")
144
 
 
 
 
 
145
  i += 1
146
 
147
  elif code == '-':
148
- base_result.append(
149
- f"<span style='color:#ff4d4f; font-weight:600'>{word}</span>"
150
- )
151
 
152
  elif code == '+':
153
- comp_result.append(
154
- f"<span style='color:#2ecc71; font-weight:600'>{word}</span>"
155
- )
156
 
157
  i += 1
158
 
159
- return " ".join(base_result), " ".join(comp_result)
160
 
161
  # --------------------------
162
  # 데이터 쑰회
@@ -164,51 +153,21 @@ def highlight_diff(base_text, comp_text):
164
  def display_data(standard, version, selection):
165
 
166
  if not all([standard, version, selection]):
167
- return pd.DataFrame({"Info": ["선택을 μ™„λ£Œν•˜μ„Έμš”"]})
168
 
169
  try:
170
- target_db = next(db for db in db_registry if db["standard"] == standard and db["version"] == version)
171
- conn = sqlite3.connect(target_db["path"])
172
 
173
  tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
174
  main_t = f"{standard}_{version}"
175
-
176
  if main_t not in tables:
177
  main_t = tables[0]
178
 
179
- cols_info = pd.read_sql(f"PRAGMA table_info([{main_t}])", conn)
180
- cols = cols_info['name'].tolist()
181
- lower_cols = [c.lower().strip() for c in cols]
182
-
183
- real_ch, real_cat = None, None
184
-
185
- for c, lc in zip(cols, lower_cols):
186
- if lc == "chapter":
187
- real_ch = c
188
- elif lc == "category":
189
- real_cat = c
190
-
191
- if selection == "ALL":
192
- df = pd.read_sql(f"SELECT * FROM [{main_t}]", conn)
193
-
194
- elif "." in selection and real_ch and real_cat:
195
- ch, ca = selection.split(".", 1)
196
- df = pd.read_sql(
197
- f"""
198
- SELECT * FROM [{main_t}]
199
- WHERE TRIM(CAST([{real_ch}] AS TEXT)) = ?
200
- AND TRIM(CAST([{real_cat}] AS TEXT)) = ?
201
- """,
202
- conn,
203
- params=[ch.strip(), ca.strip()]
204
- )
205
-
206
- else:
207
- df = pd.read_sql(f"SELECT * FROM [{main_t}]", conn)
208
-
209
  conn.close()
210
 
211
- if df is None or df.empty:
212
  return pd.DataFrame({"Info": ["데이터 μ—†μŒ"]})
213
 
214
  df.columns = [c.lower().strip() for c in df.columns]
@@ -225,48 +184,67 @@ def display_data(standard, version, selection):
225
  return pd.DataFrame({"Error": [str(e)]})
226
 
227
  # --------------------------
228
- # 톡합 쑰회
229
  # --------------------------
230
  def unified_search(bs, bv, bc, cs, cv, cc):
231
 
 
232
  if bs and bv and bc and not (cs and cv and cc):
233
- return display_data(bs, bv, bc)
 
 
 
 
 
 
 
234
 
 
235
  if bs and bv and bc and cs and cv and cc:
236
 
237
  df_base = display_data(bs, bv, bc)
238
  df_comp = display_data(cs, cv, cc)
239
 
240
- if df_base is None or df_comp is None:
241
- return pd.DataFrame({"Error": ["데이터 μ—†μŒ"]})
242
-
243
  if 'section' not in df_base.columns or 'section' not in df_comp.columns:
244
- return pd.DataFrame({"Error": ["section 컬럼 μ—†μŒ"]})
245
 
246
- merged = pd.merge(df_base, df_comp, on="section", how="outer")
 
 
 
247
 
248
- merged = merged.rename(columns={
249
- "description_x": f"Description_{bv}",
250
- "description_y": f"Description_{cv}"
251
  })
252
 
 
 
253
  base_col = f"Description_{bv}"
254
  comp_col = f"Description_{cv}"
255
 
256
- # πŸ”₯ diff 적용
 
 
 
 
 
 
 
 
257
  for idx, row in merged.iterrows():
258
  b, c = highlight_diff(row.get(base_col, ""), row.get(comp_col, ""))
259
  merged.at[idx, base_col] = b
260
  merged.at[idx, comp_col] = c
261
 
262
  merged = merged.sort_values(
263
- by="section",
264
  key=lambda col: col.map(natural_sort_key)
265
  )
266
 
267
  return merged
268
 
269
- return pd.DataFrame({"Info": ["선택을 μ™„λ£Œν•˜μ„Έμš”"]})
270
 
271
  # --------------------------
272
  # UI
@@ -291,9 +269,6 @@ with gr.Blocks() as demo:
291
  comp_reset_btn = gr.Button("β†Ί μ΄ˆκΈ°ν™”")
292
 
293
  search_btn = gr.Button("πŸ” 쑰회")
294
-
295
- gr.Markdown("### πŸ“Š 비ꡐ κ²°κ³Ό")
296
-
297
  output_df = gr.Dataframe(wrap=True, interactive=False, datatype="html")
298
 
299
  demo.load(on_load, None, base_standard)
@@ -315,45 +290,39 @@ with gr.Blocks() as demo:
315
  base_reset_btn.click(reset_base, None, [base_standard, base_version, base_category])
316
  comp_reset_btn.click(reset_comp, None, [comp_standard, comp_version, comp_category])
317
 
 
318
  # μ‹€ν–‰
 
319
  if __name__ == "__main__":
320
  demo.launch(
321
  theme=gr.themes.Soft(),
322
  share=True,
323
  css="""
324
- @import url('https://cdn.jsdelivr.net/gh/orioncactus/pretendard/dist/web/static/pretendard.css');
325
-
326
- .gradio-container {
327
- max-width: 100% !important;
328
- font-family: 'Pretendard', sans-serif;
329
- }
330
-
331
- /* ν…Œμ΄λΈ” κ³ μ • */
332
  table {
333
  table-layout: fixed !important;
334
  width: 100% !important;
335
  }
336
 
337
- /* section κ³ μ • */
338
  th:nth-child(1), td:nth-child(1) {
339
- width: 120px !important;
340
- min-width: 120px !important;
341
- max-width: 120px !important;
 
342
  }
343
 
344
- /* desc 2개 μ •ν™•νžˆ 반반 */
345
- th:nth-child(2), td:nth-child(2),
346
- th:nth-child(3), td:nth-child(3) {
347
- width: 50% !important;
348
  }
349
 
350
- /* 헀더 κ³ μ • */
351
  thead th {
352
- position: sticky !important;
353
  top: 0;
354
- background: white !important;
355
- z-index: 2;
356
- border-bottom: 2px solid #ddd;
357
  }
358
 
359
  /* 슀크둀 */
 
6
  import base64
7
  import difflib
8
 
9
+ # --------------------------
10
  # 1️⃣ μ €μž₯μ†Œ μ„€μ •
11
+ # --------------------------
12
  UPLOAD_DIR = "uploaded_dbs"
13
  if not os.path.exists(UPLOAD_DIR):
14
  os.makedirs(UPLOAD_DIR)
 
16
  db_registry = []
17
 
18
  # --------------------------
19
+ # μœ ν‹Έ
20
  # --------------------------
21
  def blob_to_base64_html(blob_data):
22
  if blob_data is None or pd.isna(blob_data):
23
  return ""
24
  try:
25
  if isinstance(blob_data, (bytes, bytearray)):
26
+ encoded = base64.b64encode(blob_data).decode('utf-8')
27
+ return f'<img src="data:image/png;base64,{encoded}" width="200" />'
28
  return str(blob_data)
29
  except:
30
  return str(blob_data)
 
32
  def natural_sort_key(s):
33
  if s is None:
34
  return []
35
+ return [int(t) if t.isdigit() else t.lower()
36
+ for t in re.split(r'(\d+)', str(s))]
37
 
38
  def refresh_registry_data():
39
  global db_registry
40
  db_registry = []
 
41
 
42
+ for f in os.listdir(UPLOAD_DIR):
43
+ if f.endswith(".db"):
44
+ name = f.replace(".db", "")
45
+ parts = name.split("_")
46
+ if len(parts) >= 2:
47
+ db_registry.append({
48
+ "path": os.path.join(UPLOAD_DIR, f),
49
+ "standard": parts[0],
50
+ "version": parts[1]
51
+ })
52
 
53
+ return sorted(list(set([d["standard"] for d in db_registry])))
54
 
55
  # --------------------------
56
  # λ“œλ‘­λ‹€μš΄
57
  # --------------------------
58
  def on_load():
59
+ return gr.Dropdown(choices=refresh_registry_data())
 
60
 
61
  def update_version_dd(standard):
62
  if not standard:
63
  return gr.Dropdown(choices=[])
64
+ versions = sorted(set(
65
+ d["version"] for d in db_registry if d["standard"] == standard
66
+ ))
67
  return gr.Dropdown(choices=versions)
68
 
69
  def update_category_dd(standard, version):
 
73
  choices = ["ALL"]
74
 
75
  try:
76
+ target = next(d for d in db_registry if d["standard"] == standard and d["version"] == version)
77
+ conn = sqlite3.connect(target["path"])
78
 
79
  tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
80
  main_t = f"{standard}_{version}"
 
81
  if main_t not in tables:
82
  main_t = tables[0]
83
 
84
+ cols = pd.read_sql(f"PRAGMA table_info([{main_t}])", conn)['name'].tolist()
85
+ lower = [c.lower() for c in cols]
 
86
 
87
+ if 'chapter' in lower and 'category' in lower:
88
+ ch = cols[lower.index('chapter')]
89
+ ca = cols[lower.index('category')]
90
 
91
  df = pd.read_sql(f"SELECT DISTINCT [{ch}], [{ca}] FROM [{main_t}]", conn)
92
 
93
+ for _, r in df.iterrows():
94
+ choices.append(f"{str(r[ch]).strip()}.{str(r[ca]).strip()}")
 
 
95
 
96
  conn.close()
97
  except:
 
100
  return gr.Dropdown(choices=choices)
101
 
102
  # --------------------------
103
+ # μ΄ˆκΈ°ν™”
104
  # --------------------------
105
  def reset_base():
106
  return None, None, None
 
109
  return None, None, None
110
 
111
  # --------------------------
112
+ # diff
113
  # --------------------------
114
+ def highlight_diff(base, comp):
115
+ base = "" if pd.isna(base) else str(base)
116
+ comp = "" if pd.isna(comp) else str(comp)
 
 
 
117
 
118
+ b_words = base.split()
119
+ c_words = comp.split()
120
 
121
+ d = list(difflib.ndiff(b_words, c_words))
 
122
 
123
+ b_res, c_res = [], []
124
  i = 0
125
+
126
  while i < len(d):
127
  code = d[i][0]
128
  word = d[i][2:]
129
 
130
  if code == ' ':
131
+ b_res.append(word)
132
+ c_res.append(word)
 
 
 
 
 
 
 
133
 
134
+ elif code == '-' and i+1 < len(d) and d[i+1][0] == '+':
135
+ new_word = d[i+1][2:]
136
+ b_res.append(f"<span style='color:#ff4d4f;font-weight:600'>{word}</span>")
137
+ c_res.append(f"<span style='color:#ff4d4f;font-weight:600'>{new_word}</span>")
138
  i += 1
139
 
140
  elif code == '-':
141
+ b_res.append(f"<span style='color:#ff4d4f;font-weight:600'>{word}</span>")
 
 
142
 
143
  elif code == '+':
144
+ c_res.append(f"<span style='color:#2ecc71;font-weight:600'>{word}</span>")
 
 
145
 
146
  i += 1
147
 
148
+ return " ".join(b_res), " ".join(c_res)
149
 
150
  # --------------------------
151
  # 데이터 쑰회
 
153
  def display_data(standard, version, selection):
154
 
155
  if not all([standard, version, selection]):
156
+ return pd.DataFrame({"Info": ["선택 ν•„μš”"]})
157
 
158
  try:
159
+ target = next(d for d in db_registry if d["standard"] == standard and d["version"] == version)
160
+ conn = sqlite3.connect(target["path"])
161
 
162
  tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
163
  main_t = f"{standard}_{version}"
 
164
  if main_t not in tables:
165
  main_t = tables[0]
166
 
167
+ df = pd.read_sql(f"SELECT * FROM [{main_t}]", conn)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
  conn.close()
169
 
170
+ if df.empty:
171
  return pd.DataFrame({"Info": ["데이터 μ—†μŒ"]})
172
 
173
  df.columns = [c.lower().strip() for c in df.columns]
 
184
  return pd.DataFrame({"Error": [str(e)]})
185
 
186
  # --------------------------
187
+ # πŸ”₯ 톡합 쑰회 (핡심 μˆ˜μ •)
188
  # --------------------------
189
  def unified_search(bs, bv, bc, cs, cv, cc):
190
 
191
+ # βœ… 단일 쑰회
192
  if bs and bv and bc and not (cs and cv and cc):
193
+ df = display_data(bs, bv, bc)
194
+
195
+ df = df.rename(columns={
196
+ "section": "Section",
197
+ "description": "Description"
198
+ })
199
+
200
+ return df
201
 
202
+ # βœ… 비ꡐ 쑰회
203
  if bs and bv and bc and cs and cv and cc:
204
 
205
  df_base = display_data(bs, bv, bc)
206
  df_comp = display_data(cs, cv, cc)
207
 
 
 
 
208
  if 'section' not in df_base.columns or 'section' not in df_comp.columns:
209
+ return pd.DataFrame({"Error": ["section μ—†μŒ"]})
210
 
211
+ df_base = df_base.rename(columns={
212
+ "section": f"Section_{bv}",
213
+ "description": f"Description_{bv}"
214
+ })
215
 
216
+ df_comp = df_comp.rename(columns={
217
+ "section": f"Section_{cv}",
218
+ "description": f"Description_{cv}"
219
  })
220
 
221
+ base_sec = f"Section_{bv}"
222
+ comp_sec = f"Section_{cv}"
223
  base_col = f"Description_{bv}"
224
  comp_col = f"Description_{cv}"
225
 
226
+ merged = pd.merge(
227
+ df_base,
228
+ df_comp,
229
+ left_on=base_sec,
230
+ right_on=comp_sec,
231
+ how="outer"
232
+ )
233
+
234
+ # diff 적용
235
  for idx, row in merged.iterrows():
236
  b, c = highlight_diff(row.get(base_col, ""), row.get(comp_col, ""))
237
  merged.at[idx, base_col] = b
238
  merged.at[idx, comp_col] = c
239
 
240
  merged = merged.sort_values(
241
+ by=base_sec,
242
  key=lambda col: col.map(natural_sort_key)
243
  )
244
 
245
  return merged
246
 
247
+ return pd.DataFrame({"Info": ["선택 ν•„μš”"]})
248
 
249
  # --------------------------
250
  # UI
 
269
  comp_reset_btn = gr.Button("β†Ί μ΄ˆκΈ°ν™”")
270
 
271
  search_btn = gr.Button("πŸ” 쑰회")
 
 
 
272
  output_df = gr.Dataframe(wrap=True, interactive=False, datatype="html")
273
 
274
  demo.load(on_load, None, base_standard)
 
290
  base_reset_btn.click(reset_base, None, [base_standard, base_version, base_category])
291
  comp_reset_btn.click(reset_comp, None, [comp_standard, comp_version, comp_category])
292
 
293
+ # --------------------------
294
  # μ‹€ν–‰
295
+ # --------------------------
296
  if __name__ == "__main__":
297
  demo.launch(
298
  theme=gr.themes.Soft(),
299
  share=True,
300
  css="""
301
+ /* ν…Œμ΄λΈ” */
 
 
 
 
 
 
 
302
  table {
303
  table-layout: fixed !important;
304
  width: 100% !important;
305
  }
306
 
307
+ /* 단일 쑰회 */
308
  th:nth-child(1), td:nth-child(1) {
309
+ width: 20% !important;
310
+ }
311
+ th:nth-child(2), td:nth-child(2) {
312
+ width: 80% !important;
313
  }
314
 
315
+ /* 비ꡐ 쑰회 (4컬럼) */
316
+ th:nth-child(3), td:nth-child(3),
317
+ th:nth-child(4), td:nth-child(4) {
318
+ width: 40% !important;
319
  }
320
 
321
+ /* header */
322
  thead th {
323
+ position: sticky;
324
  top: 0;
325
+ background: white;
 
 
326
  }
327
 
328
  /* 슀크둀 */