| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- #!/usr/bin/env python3
- """回填 crm_product / crm_customer 的 name_pinyin_abbr(仅空字段)。
- 用法:
- cd backend
- venv/bin/python scripts/backfill_name_pinyin_abbr.py --dry-run
- venv/bin/python scripts/backfill_name_pinyin_abbr.py
- venv/bin/python scripts/backfill_name_pinyin_abbr.py --env-file .env.production
- """
- from __future__ import annotations
- import argparse
- import os
- import sys
- from pathlib import Path
- from typing import Optional
- BACKEND_ROOT = Path(__file__).resolve().parents[1]
- if str(BACKEND_ROOT) not in sys.path:
- sys.path.insert(0, str(BACKEND_ROOT))
- def load_settings_env(env_file: Optional[str]) -> None:
- if env_file:
- os.environ["ENV_FILE"] = env_file
- from dotenv import load_dotenv
- candidates = []
- if env_file:
- p = Path(env_file)
- candidates.append(p if p.is_absolute() else BACKEND_ROOT / p)
- candidates.extend(
- [
- BACKEND_ROOT / ".env.local",
- BACKEND_ROOT / ".env",
- BACKEND_ROOT / ".env.production",
- ]
- )
- for path in candidates:
- if path.is_file():
- load_dotenv(path, override=True)
- break
- def _empty_pinyin_filter(model):
- from sqlalchemy import or_
- return or_(model.name_pinyin_abbr.is_(None), model.name_pinyin_abbr == "")
- def backfill_model(db, model, name_attr: str, *, dry_run: bool, batch_size: int) -> int:
- from app.utils.pinyin_abbr import build_name_pinyin_abbr
- q = db.query(model).filter(model.del_flag == 0).filter(_empty_pinyin_filter(model))
- total = q.count()
- print(f"{model.__tablename__}: empty={total}")
- if total == 0:
- return 0
- updated = 0
- dry_printed = 0
- while True:
- rows = q.limit(batch_size).all()
- if not rows:
- break
- for row in rows:
- try:
- name = getattr(row, name_attr) or ""
- abbr = build_name_pinyin_abbr(name)
- if not abbr:
- continue
- if dry_run:
- if dry_printed < 20:
- print(f" would set {row.id}: {abbr}")
- dry_printed += 1
- updated += 1
- else:
- row.name_pinyin_abbr = abbr
- updated += 1
- except Exception as exc: # noqa: BLE001 — batch continue
- print(f" skip {getattr(row, 'id', '?')}: {exc}")
- if dry_run:
- if updated > dry_printed:
- print(f" ... and {updated - dry_printed} more")
- break
- db.commit()
- # filled rows leave the empty filter; no offset needed
- print(f"{model.__tablename__}: updated={updated}")
- return updated
- def main() -> int:
- parser = argparse.ArgumentParser(description="Backfill CRM name_pinyin_abbr (empty only)")
- parser.add_argument("--dry-run", action="store_true", help="只统计/预览,不写库")
- parser.add_argument("--env-file", default=None, help="指定 dotenv 文件")
- parser.add_argument("--batch-size", type=int, default=200)
- args = parser.parse_args()
- load_settings_env(args.env_file)
- from app.core.database import SessionLocal
- from app.models.crm_models import CrmCustomer, CrmProduct
- db = SessionLocal()
- try:
- backfill_model(
- db, CrmProduct, "product_name", dry_run=args.dry_run, batch_size=args.batch_size
- )
- backfill_model(
- db, CrmCustomer, "customer_name", dry_run=args.dry_run, batch_size=args.batch_size
- )
- finally:
- db.close()
- return 0
- if __name__ == "__main__":
- raise SystemExit(main())
|