| """ | |
| Seed synonyms for search query expansion. | |
| """ | |
| import argparse | |
| import csv | |
| import os | |
| import sys | |
| from datetime import datetime | |
| from pathlib import Path | |
| from typing import Iterable, List, Tuple | |
| import django | |
| ROOT_DIR = Path(__file__).resolve().parents[2] | |
| BACKEND_DIR = ROOT_DIR / "backend" | |
| DATA_DIR = ROOT_DIR / "tài nguyên" | |
| LOG_DIR = BACKEND_DIR / "logs" / "data_quality" | |
| HUE_PORTAL_DIR = BACKEND_DIR / "hue_portal" | |
| for path in (HUE_PORTAL_DIR, BACKEND_DIR, ROOT_DIR): | |
| if str(path) not in sys.path: | |
| sys.path.insert(0, str(path)) | |
| os.environ.setdefault("DJANGO_SETTINGS_MODULE", "hue_portal.hue_portal.settings") | |
| django.setup() | |
| from hue_portal.core.models import Synonym | |
| DEFAULT_SEEDS: List[Tuple[str, str]] = [ | |
| ("đèn đỏ", "vượt đèn đỏ"), | |
| ("vượt đèn", "vượt đèn đỏ"), | |
| ("nồng độ cồn", "rượu bia"), | |
| ("nồng độ cồn", "say xỉn"), | |
| ("nồng độ cồn", "uống rượu"), | |
| ("mũ bảo hiểm", "nón bảo hiểm"), | |
| ("mũ bảo hiểm", "mũ"), | |
| ("giấy phép lái xe", "bằng lái"), | |
| ("giấy phép lái xe", "GPLX"), | |
| ("giấy phép lái xe", "bằng"), | |
| ("đăng ký xe", "đăng ký"), | |
| ("đăng ký xe", "giấy đăng ký"), | |
| ("dừng đỗ", "đỗ xe"), | |
| ("dừng đỗ", "dừng xe"), | |
| ("dây an toàn", "thắt dây an toàn"), | |
| ("tốc độ", "vượt tốc độ"), | |
| ("tốc độ", "quá tốc độ"), | |
| ("sai làn", "sai đường"), | |
| ("sai làn", "đi sai làn"), | |
| ("điện thoại", "sử dụng điện thoại"), | |
| ("điện thoại", "gọi điện"), | |
| ("cư trú", "thủ tục cư trú"), | |
| ("cư trú", "đăng ký cư trú"), | |
| ("cư trú", "tạm trú"), | |
| ("cư trú", "thường trú"), | |
| ("ANTT", "an ninh trật tự"), | |
| ("ANTT", "an ninh"), | |
| ("PCCC", "phòng cháy chữa cháy"), | |
| ("PCCC", "cháy nổ"), | |
| ("thủ tục", "hành chính"), | |
| ("thủ tục", "TTHC"), | |
| ("công an", "CA"), | |
| ("công an", "cảnh sát"), | |
| ("tiếp dân", "tiếp công dân"), | |
| ("tiếp dân", "một cửa"), | |
| ("đơn vị", "cơ quan"), | |
| ("đơn vị", "phòng ban"), | |
| ] | |
| def load_from_csv(path: Path) -> List[Tuple[str, str]]: | |
| if not path.exists(): | |
| return [] | |
| pairs: List[Tuple[str, str]] = [] | |
| with path.open(encoding="utf-8") as handle: | |
| reader = csv.DictReader(handle) | |
| for row in reader: | |
| keyword = (row.get("keyword") or "").strip() | |
| alias = (row.get("alias") or "").strip() | |
| if keyword and alias: | |
| pairs.append((keyword, alias)) | |
| return pairs | |
| def seed_synonyms(pairs: Iterable[Tuple[str, str]], log_path: Path) -> None: | |
| created = 0 | |
| updated = 0 | |
| skipped = 0 | |
| with log_path.open("a", encoding="utf-8") as log_file: | |
| for keyword, alias in pairs: | |
| try: | |
| synonym, was_created = Synonym.objects.get_or_create( | |
| keyword=keyword, | |
| defaults={"alias": alias} | |
| ) | |
| if was_created: | |
| created += 1 | |
| log_file.write(f"{datetime.utcnow().isoformat()}Z CREATED {keyword} -> {alias}\n") | |
| else: | |
| if synonym.alias != alias: | |
| synonym.alias = alias | |
| synonym.save(update_fields=["alias"]) | |
| updated += 1 | |
| log_file.write(f"{datetime.utcnow().isoformat()}Z UPDATED {keyword} -> {alias}\n") | |
| else: | |
| skipped += 1 | |
| except Exception as exc: | |
| log_file.write(f"{datetime.utcnow().isoformat()}Z ERROR {keyword} -> {alias} :: {exc}\n") | |
| total = Synonym.objects.count() | |
| print(f"✅ Seeded {created} mới, cập nhật {updated}, bỏ qua {skipped}. Tổng: {total}") | |
| print(f"Log chi tiết: {log_path}") | |
| def parse_args(): | |
| parser = argparse.ArgumentParser(description="Seed synonyms cho chatbot") | |
| parser.add_argument("--source", type=Path, default=DATA_DIR / "synonyms.csv", help="Đường dẫn CSV synonyms") | |
| parser.add_argument("--include-default", action="store_true", help="Bao gồm seed mặc định trong script") | |
| return parser.parse_args() | |
| def main(): | |
| args = parse_args() | |
| LOG_DIR.mkdir(parents=True, exist_ok=True) | |
| log_path = LOG_DIR / f"synonyms_{datetime.utcnow().strftime('%Y%m%d%H%M%S')}.log" | |
| pairs: List[Tuple[str, str]] = [] | |
| csv_pairs = load_from_csv(args.source) | |
| if csv_pairs: | |
| pairs.extend(csv_pairs) | |
| if args.include_default or not pairs: | |
| pairs.extend(DEFAULT_SEEDS) | |
| seed_synonyms(pairs, log_path) | |
| if __name__ == "__main__": | |
| main() | |