Spaces:
Sleeping
Sleeping
| import json | |
| import re | |
| import pandas as pd | |
| from match_docs import normalize_so_hieu | |
| def normalize_text(text: str) -> str: | |
| """Chuẩn hóa title để match ổn định hơn.""" | |
| text = str(text or "").strip().lower() | |
| text = re.sub(r"\s+", " ", text) | |
| text = re.sub(r"[\"'`´“”‘’]", "", text) | |
| text = re.sub(r"[.,;:!?(){}\[\]]", "", text) | |
| return text | |
| def strip_trailing_year(text: str) -> str: | |
| """Bỏ năm ở cuối title nếu có, ví dụ '... 2020' -> '...'.""" | |
| text = str(text or "").strip() | |
| return re.sub(r"\s+(?:19|20)\d{2}$", "", text).strip() | |
| def title_aliases(title: str, loai_van_ban: str | None = None) -> list[str]: | |
| """Sinh các biến thể title để match tốt hơn.""" | |
| aliases = [] | |
| raw = str(title or "").strip() | |
| if not raw: | |
| return aliases | |
| base = normalize_text(raw) | |
| core = normalize_text(strip_trailing_year(raw)) | |
| def add(value: str) -> None: | |
| value = normalize_text(value) | |
| if value and value not in aliases: | |
| aliases.append(value) | |
| add(base) | |
| add(core) | |
| # Bỏ các tiền tố loại văn bản để khớp cả tên rút gọn và tên đầy đủ. | |
| for variant in (base, core): | |
| if variant.startswith("luat "): | |
| add(variant[len("luat "):]) | |
| if variant.startswith("bo luat "): | |
| add(variant[len("bo luat "):]) | |
| # Thêm tiền tố theo loại nếu schema chỉ ghi phần tên rút gọn. | |
| loai_norm = normalize_text(loai_van_ban or "") | |
| if loai_norm == "luat": | |
| add(f"luat {core}") | |
| elif loai_norm == "bo luat": | |
| add(f"bo luat {core}") | |
| return aliases | |
| def build_lookup_tables(df: pd.DataFrame) -> tuple[dict[str, str], dict[str, str]]: | |
| """Tạo 2 bảng tra cứu: theo số hiệu và theo title.""" | |
| lookup_by_so_hieu: dict[str, str] = {} | |
| lookup_by_title: dict[str, str] = {} | |
| df = df.copy() | |
| df["id"] = df["id"].astype(str) | |
| for _, row in df.iterrows(): | |
| doc_id = str(row.get("id", "")).strip() | |
| if not doc_id: | |
| continue | |
| so_hieu_raw = row.get("so_ky_hieu", "") | |
| title_raw = row.get("title", "") | |
| loai_raw = row.get("loai_van_ban", "") | |
| if pd.notna(so_hieu_raw) and str(so_hieu_raw).strip().lower() != "nan": | |
| skh_clean = normalize_so_hieu(so_hieu_raw) | |
| if skh_clean: | |
| lookup_by_so_hieu[skh_clean] = doc_id | |
| if pd.notna(title_raw) and str(title_raw).strip().lower() != "nan": | |
| for alias in title_aliases(title_raw, loai_raw if pd.notna(loai_raw) else None): | |
| lookup_by_title[alias] = doc_id | |
| return lookup_by_so_hieu, lookup_by_title | |
| def extract_title_candidates(doc: dict) -> list[str]: | |
| """Lấy các biến thể title có thể dùng để match.""" | |
| candidates = [] | |
| loai_van_ban = doc.get("loai_van_ban") | |
| for key in ("title", "ten"): | |
| value = doc.get(key) | |
| if value and str(value).strip().lower() != "nan": | |
| candidates.extend(title_aliases(str(value).strip(), loai_van_ban)) | |
| return candidates | |
| def extract_so_hieu_candidates(doc: dict) -> list[str]: | |
| """Lấy các biến thể số hiệu có thể dùng để match.""" | |
| candidates = [] | |
| for key in ("so_ky_hieu", "so_hieu"): | |
| value = doc.get(key) | |
| if value and str(value).strip().lower() != "nan": | |
| candidates.append(str(value).strip()) | |
| return candidates | |
| def main() -> None: | |
| metadata_path = "data/metadata.parquet" | |
| schema_path = "chu_de_lao_dong_schema.json" | |
| output_path = "chu_de_lao_dong_schema_with_ids.json" | |
| print(f"Đang đọc metadata từ {metadata_path}...") | |
| df = pd.read_parquet(metadata_path) | |
| if "id" not in df.columns: | |
| raise KeyError("metadata.parquet không có cột `id`.") | |
| if "so_ky_hieu" not in df.columns: | |
| raise KeyError("metadata.parquet không có cột `so_ky_hieu`.") | |
| if "title" not in df.columns: | |
| raise KeyError("metadata.parquet không có cột `title`.") | |
| lookup_by_so_hieu, lookup_by_title = build_lookup_tables(df) | |
| print( | |
| f"Đã tạo lookup: {len(lookup_by_so_hieu)} so_ky_hieu, {len(lookup_by_title)} title." | |
| ) | |
| print(f"Đang đọc schema từ {schema_path}...") | |
| with open(schema_path, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| matched_by_so_hieu = 0 | |
| matched_by_title = 0 | |
| total_count = 0 | |
| for item in data: | |
| total_count += 1 | |
| doc_id = None | |
| matched_method = None | |
| for so_hieu_raw in extract_so_hieu_candidates(item): | |
| so_hieu_clean = normalize_so_hieu(so_hieu_raw) | |
| doc_id = lookup_by_so_hieu.get(so_hieu_clean) | |
| if doc_id: | |
| matched_method = "so_ky_hieu" | |
| matched_by_so_hieu += 1 | |
| break | |
| if not doc_id: | |
| for title_raw in extract_title_candidates(item): | |
| title_clean = normalize_text(title_raw) | |
| doc_id = lookup_by_title.get(title_clean) | |
| if doc_id: | |
| matched_method = "title" | |
| matched_by_title += 1 | |
| break | |
| if doc_id: | |
| item["doc_id"] = doc_id | |
| item["match_method"] = matched_method | |
| else: | |
| print( | |
| f"CẢNH BÁO: Không tìm thấy doc_id cho '{item.get('title')}' / '{item.get('so_ky_hieu')}'" | |
| ) | |
| print( | |
| f"Tổng kết: match {matched_by_so_hieu + matched_by_title}/{total_count} văn bản " | |
| f"(theo so_ky_hieu: {matched_by_so_hieu}, theo title: {matched_by_title})." | |
| ) | |
| with open(output_path, "w", encoding="utf-8") as f: | |
| json.dump(data, f, ensure_ascii=False, indent=2) | |
| print(f"Đã lưu kết quả ra file: {output_path}") | |
| if __name__ == "__main__": | |
| main() | |