Update app.py
Browse files
app.py
CHANGED
|
@@ -234,17 +234,19 @@ def get_table_config(table_type):
|
|
| 234 |
|
| 235 |
def fetch_database_records(standard, version, selection, table_type):
|
| 236 |
if not all([standard, version, selection]):
|
| 237 |
-
return pd.DataFrame({"Info": ["์ ํ ํ์"]}),
|
| 238 |
|
| 239 |
try:
|
| 240 |
db_path = os.path.join(UPLOAD_DIR, f"{standard}_{version}.db")
|
| 241 |
if not os.path.exists(db_path):
|
| 242 |
-
return pd.DataFrame({"Error": [f"ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {db_path}"]}),
|
| 243 |
|
| 244 |
conn = sqlite3.connect(db_path)
|
| 245 |
conn.text_factory = decode_sqlite_text
|
| 246 |
|
| 247 |
anchor_col_config, display_setting = get_table_config(table_type)
|
|
|
|
|
|
|
| 248 |
|
| 249 |
tables = pd.read_sql("SELECT name FROM sqlite_master WHERE type='table';", conn)['name'].tolist()
|
| 250 |
main_table = f"{standard}_{version}"
|
|
@@ -255,11 +257,7 @@ def fetch_database_records(standard, version, selection, table_type):
|
|
| 255 |
pattern = re.compile(f"^{standard}[_\\s-]*{version}[_\\s-]*", re.IGNORECASE)
|
| 256 |
|
| 257 |
for t in tables:
|
| 258 |
-
if t == selection:
|
| 259 |
-
target_table = t
|
| 260 |
-
break
|
| 261 |
-
short_name = pattern.sub("", t).strip(" _")
|
| 262 |
-
if short_name == selection:
|
| 263 |
target_table = t
|
| 264 |
break
|
| 265 |
|
|
@@ -289,15 +287,20 @@ def fetch_database_records(standard, version, selection, table_type):
|
|
| 289 |
|
| 290 |
df = df.drop(columns=[c for c in df.columns if c.lower() == "version"], errors="ignore")
|
| 291 |
|
| 292 |
-
# ๋์๋ฌธ์ ๋ฌด์ํ๊ณ ์ค์ ์ต์ปค ์ปฌ๋ผ๋ช
์ฐพ๊ธฐ
|
| 293 |
-
|
|
|
|
|
|
|
|
|
|
| 294 |
|
| 295 |
if display_setting:
|
| 296 |
cols_to_show = [c.strip() for c in display_setting.split(',')]
|
| 297 |
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)]
|
| 298 |
|
| 299 |
-
|
| 300 |
-
|
|
|
|
|
|
|
| 301 |
|
| 302 |
if actual_cols_to_show:
|
| 303 |
df = df[actual_cols_to_show]
|
|
@@ -306,12 +309,12 @@ def fetch_database_records(standard, version, selection, table_type):
|
|
| 306 |
df[col] = df[col].apply(convert_blob_to_html_img)
|
| 307 |
|
| 308 |
conn.close()
|
| 309 |
-
return df,
|
| 310 |
|
| 311 |
except Exception as e:
|
| 312 |
import traceback
|
| 313 |
traceback.print_exc()
|
| 314 |
-
return pd.DataFrame({"Error": [str(e)]}),
|
| 315 |
|
| 316 |
|
| 317 |
def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat):
|
|
@@ -319,39 +322,46 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 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 |
# ----------------------------------------
|
| 323 |
-
# 1. ๋จ์ผ ์กฐํ
|
| 324 |
# ----------------------------------------
|
| 325 |
if base_std and base_ver and base_cat and (not comp_std or not comp_ver or not comp_cat):
|
| 326 |
df, _ = fetch_database_records(base_std, base_ver, base_cat, table_type)
|
| 327 |
if "Error" in df.columns or "Info" in df.columns: return df
|
| 328 |
-
|
| 329 |
-
if not df.empty and len(df.columns) > 1:
|
| 330 |
-
is_duplicate = pd.Series([True] * len(df), index=df.index)
|
| 331 |
-
for col in df.columns[:-1]:
|
| 332 |
-
current_col_str = df[col].astype(str).str.strip()
|
| 333 |
-
current_match = (current_col_str == current_col_str.shift(1)) & (~current_col_str.isin(["", "nan", "None", " "]))
|
| 334 |
-
is_duplicate = is_duplicate & current_match
|
| 335 |
-
df.loc[is_duplicate, col] = " "
|
| 336 |
-
return df
|
| 337 |
|
| 338 |
# ----------------------------------------
|
| 339 |
-
# 2. ๋น๊ต ์กฐํ
|
| 340 |
# ----------------------------------------
|
| 341 |
if all([base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat]):
|
| 342 |
-
df_base,
|
| 343 |
-
df_comp,
|
| 344 |
|
| 345 |
if "Error" in df_base.columns: return df_base
|
| 346 |
if "Error" in df_comp.columns: return df_comp
|
| 347 |
-
if real_anchor_b not in df_base.columns or real_anchor_c not in df_comp.columns:
|
| 348 |
-
return pd.DataFrame({"Error": ["์ค์ ๋ ๊ธฐ์ค ์ด(Anchor)์ด ๋ฐ์ดํฐ์ ์กด์ฌํ์ง ์์ต๋๋ค."]})
|
| 349 |
|
| 350 |
-
|
| 351 |
-
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
|
| 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()])
|
|
@@ -365,15 +375,11 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 365 |
if not df_mapping.empty:
|
| 366 |
df_mapping['Base_section'] = df_mapping['Base_section'].astype(str).str.replace('\n', ',').str.replace('\r', '')
|
| 367 |
df_mapping['Comp_section'] = df_mapping['Comp_section'].astype(str).str.replace('\n', ',').str.replace('\r', '')
|
| 368 |
-
|
| 369 |
df_mapping['Base_section'] = df_mapping['Base_section'].str.split(',')
|
| 370 |
df_mapping['Comp_section'] = df_mapping['Comp_section'].str.split(',')
|
| 371 |
-
|
| 372 |
df_mapping = df_mapping.explode('Base_section').explode('Comp_section')
|
| 373 |
-
|
| 374 |
df_mapping['Base_section'] = df_mapping['Base_section'].astype(str).str.strip().str.replace(" ", "")
|
| 375 |
df_mapping['Comp_section'] = df_mapping['Comp_section'].astype(str).str.strip().str.replace(" ", "")
|
| 376 |
-
|
| 377 |
df_mapping = df_mapping[(df_mapping['Base_section'] != '') & (df_mapping['Base_section'] != 'nan')]
|
| 378 |
df_mapping = df_mapping[(df_mapping['Comp_section'] != '') & (df_mapping['Comp_section'] != 'nan')]
|
| 379 |
df_mapping = df_mapping.drop_duplicates()
|
|
@@ -390,7 +396,6 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 390 |
df_implicit = pd.DataFrame({'Base_section': list(implicit_keys), 'Comp_section': list(implicit_keys)})
|
| 391 |
bridge = pd.concat([df_mapping, df_implicit], ignore_index=True)
|
| 392 |
|
| 393 |
-
# ๋ธ๋ฆฟ์ง ๋ณํฉ ์ ์ด๋ฆ ๋ณ๊ฒฝ (๋ฒ์ ๋ณ ์ ๋ฏธ์ฌ ์ถ๊ฐ)
|
| 394 |
rename_b = {c: f"{c}_{base_ver}" for c in df_base.columns if c != 'merge_key'}
|
| 395 |
df_base = df_base.rename(columns=rename_b)
|
| 396 |
|
|
@@ -400,7 +405,6 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 400 |
df_base['base_idx'] = range(len(df_base))
|
| 401 |
df_comp['comp_idx'] = range(len(df_comp))
|
| 402 |
|
| 403 |
-
# ๋ธ๋ฆฟ์ง๋ฅผ ์ด์ฉํด ์กฐ์ธ
|
| 404 |
merged = pd.merge(bridge, df_base, left_on='Base_section', right_on='merge_key', how='outer')
|
| 405 |
merged = pd.merge(merged, df_comp, left_on='Comp_section', right_on='merge_key', how='outer')
|
| 406 |
|
|
@@ -412,22 +416,17 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 412 |
result_rows = []
|
| 413 |
b_cols_renamed = list(rename_b.values())
|
| 414 |
c_cols_renamed = list(rename_c.values())
|
| 415 |
-
|
| 416 |
-
b_anchor_renamed = f"{real_anchor_b}_{base_ver}"
|
| 417 |
-
c_anchor_renamed = f"{real_anchor_c}_{comp_ver}"
|
| 418 |
|
| 419 |
for _, row in merged.iterrows():
|
| 420 |
row_dict = {}
|
| 421 |
-
has_b = not pd.isna(row.get(
|
| 422 |
-
has_c = not pd.isna(row.get(
|
| 423 |
|
| 424 |
for c in b_cols_renamed:
|
| 425 |
row_dict[c] = str(row.get(c)) if has_b and not pd.isna(row.get(c)) else ""
|
| 426 |
-
|
| 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 |
-
# Main ํ
์ด๋ธ์ธ ๊ฒฝ์ฐ์๋ง Description์ ํํด Diff ์ ์ฉ
|
| 431 |
if table_type == "Main" and has_b and has_c:
|
| 432 |
desc_b_col = next((c for c in b_cols_renamed if 'description' in c.lower()), None)
|
| 433 |
desc_c_col = next((c for c in c_cols_renamed if 'description' in c.lower()), None)
|
|
@@ -435,19 +434,15 @@ def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, com
|
|
| 435 |
if desc_b_col and desc_c_col:
|
| 436 |
b_val, c_val = row_dict[desc_b_col], row_dict[desc_c_col]
|
| 437 |
if b_val and c_val and "<img" not in b_val and "<img" not in c_val and b_val != c_val:
|
| 438 |
-
|
| 439 |
-
row_dict[desc_b_col] = b_diff
|
| 440 |
-
row_dict[desc_c_col] = c_diff
|
| 441 |
|
| 442 |
result_rows.append(row_dict)
|
| 443 |
|
| 444 |
final_df = pd.DataFrame(result_rows)
|
| 445 |
|
| 446 |
-
#
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
for c in b_cols_renamed:
|
| 450 |
-
final_df.loc[duplicate_mask, c] = ""
|
| 451 |
|
| 452 |
return final_df
|
| 453 |
|
|
|
|
| 234 |
|
| 235 |
def fetch_database_records(standard, version, selection, table_type):
|
| 236 |
if not all([standard, version, selection]):
|
| 237 |
+
return pd.DataFrame({"Info": ["์ ํ ํ์"]}), []
|
| 238 |
|
| 239 |
try:
|
| 240 |
db_path = os.path.join(UPLOAD_DIR, f"{standard}_{version}.db")
|
| 241 |
if not os.path.exists(db_path):
|
| 242 |
+
return pd.DataFrame({"Error": [f"ํ์ผ์ ์ฐพ์ ์ ์์ต๋๋ค: {db_path}"]}), []
|
| 243 |
|
| 244 |
conn = sqlite3.connect(db_path)
|
| 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()
|
| 252 |
main_table = f"{standard}_{version}"
|
|
|
|
| 257 |
pattern = re.compile(f"^{standard}[_\\s-]*{version}[_\\s-]*", re.IGNORECASE)
|
| 258 |
|
| 259 |
for t in tables:
|
| 260 |
+
if t == selection or pattern.sub("", t).strip(" _") == selection:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 261 |
target_table = t
|
| 262 |
break
|
| 263 |
|
|
|
|
| 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)
|
| 294 |
+
real_anchors.append(real_ac)
|
| 295 |
|
| 296 |
if display_setting:
|
| 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)
|
| 304 |
|
| 305 |
if actual_cols_to_show:
|
| 306 |
df = df[actual_cols_to_show]
|
|
|
|
| 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
|
| 316 |
traceback.print_exc()
|
| 317 |
+
return pd.DataFrame({"Error": [str(e)]}), []
|
| 318 |
|
| 319 |
|
| 320 |
def execute_unified_search(base_std, base_ver, base_cat, comp_std, comp_ver, comp_cat):
|
|
|
|
| 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)
|
| 329 |
+
for col in cols:
|
| 330 |
+
if col in df.columns:
|
| 331 |
+
curr = df[col].astype(str).str.strip()
|
| 332 |
+
match = (curr == curr.shift(1)) & (~curr.isin(["", "nan", "None"]))
|
| 333 |
+
is_dup = is_dup & match
|
| 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)
|
| 351 |
|
| 352 |
if "Error" in df_base.columns: return df_base
|
| 353 |
if "Error" in df_comp.columns: return df_comp
|
|
|
|
|
|
|
| 354 |
|
| 355 |
+
for ra in real_anchors_b:
|
| 356 |
+
if ra not in df_base.columns: return pd.DataFrame({"Error": [f"๊ธฐ์ค ์ด '{ra}'์ด ๋ฐ์ดํฐ์ ์์ต๋๋ค."]})
|
| 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()])
|
|
|
|
| 375 |
if not df_mapping.empty:
|
| 376 |
df_mapping['Base_section'] = df_mapping['Base_section'].astype(str).str.replace('\n', ',').str.replace('\r', '')
|
| 377 |
df_mapping['Comp_section'] = df_mapping['Comp_section'].astype(str).str.replace('\n', ',').str.replace('\r', '')
|
|
|
|
| 378 |
df_mapping['Base_section'] = df_mapping['Base_section'].str.split(',')
|
| 379 |
df_mapping['Comp_section'] = df_mapping['Comp_section'].str.split(',')
|
|
|
|
| 380 |
df_mapping = df_mapping.explode('Base_section').explode('Comp_section')
|
|
|
|
| 381 |
df_mapping['Base_section'] = df_mapping['Base_section'].astype(str).str.strip().str.replace(" ", "")
|
| 382 |
df_mapping['Comp_section'] = df_mapping['Comp_section'].astype(str).str.strip().str.replace(" ", "")
|
|
|
|
| 383 |
df_mapping = df_mapping[(df_mapping['Base_section'] != '') & (df_mapping['Base_section'] != 'nan')]
|
| 384 |
df_mapping = df_mapping[(df_mapping['Comp_section'] != '') & (df_mapping['Comp_section'] != 'nan')]
|
| 385 |
df_mapping = df_mapping.drop_duplicates()
|
|
|
|
| 396 |
df_implicit = pd.DataFrame({'Base_section': list(implicit_keys), 'Comp_section': list(implicit_keys)})
|
| 397 |
bridge = pd.concat([df_mapping, df_implicit], ignore_index=True)
|
| 398 |
|
|
|
|
| 399 |
rename_b = {c: f"{c}_{base_ver}" for c in df_base.columns if c != 'merge_key'}
|
| 400 |
df_base = df_base.rename(columns=rename_b)
|
| 401 |
|
|
|
|
| 405 |
df_base['base_idx'] = range(len(df_base))
|
| 406 |
df_comp['comp_idx'] = range(len(df_comp))
|
| 407 |
|
|
|
|
| 408 |
merged = pd.merge(bridge, df_base, left_on='Base_section', right_on='merge_key', how='outer')
|
| 409 |
merged = pd.merge(merged, df_comp, left_on='Comp_section', right_on='merge_key', how='outer')
|
| 410 |
|
|
|
|
| 416 |
result_rows = []
|
| 417 |
b_cols_renamed = list(rename_b.values())
|
| 418 |
c_cols_renamed = list(rename_c.values())
|
|
|
|
|
|
|
|
|
|
| 419 |
|
| 420 |
for _, row in merged.iterrows():
|
| 421 |
row_dict = {}
|
| 422 |
+
has_b = not pd.isna(row.get('base_idx')) and row.get('base_idx') != float('inf')
|
| 423 |
+
has_c = not pd.isna(row.get('comp_idx')) and row.get('comp_idx') != float('inf')
|
| 424 |
|
| 425 |
for c in b_cols_renamed:
|
| 426 |
row_dict[c] = str(row.get(c)) if has_b and not pd.isna(row.get(c)) else ""
|
|
|
|
| 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 |
if table_type == "Main" and has_b and has_c:
|
| 431 |
desc_b_col = next((c for c in b_cols_renamed if 'description' in c.lower()), None)
|
| 432 |
desc_c_col = next((c for c in c_cols_renamed if 'description' in c.lower()), None)
|
|
|
|
| 434 |
if desc_b_col and desc_c_col:
|
| 435 |
b_val, c_val = row_dict[desc_b_col], row_dict[desc_c_col]
|
| 436 |
if b_val and c_val and "<img" not in b_val and "<img" not in c_val and b_val != c_val:
|
| 437 |
+
row_dict[desc_b_col], row_dict[desc_c_col] = generate_html_diff(b_val, c_val)
|
|
|
|
|
|
|
| 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 |
|
| 447 |
return final_df
|
| 448 |
|