From 8d7774b9f50503863bec903c8c21fdc5be4a916c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B5=85=E5=BF=B5?= Date: Wed, 5 Aug 2026 18:45:55 +0800 Subject: [PATCH] feat(scripts): backfill Customer.add_time from raw_json Co-Authored-By: Claude --- .../scripts/backfill_customer_add_time.py | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 yunke-server/scripts/backfill_customer_add_time.py diff --git a/yunke-server/scripts/backfill_customer_add_time.py b/yunke-server/scripts/backfill_customer_add_time.py new file mode 100644 index 0000000..0041f79 --- /dev/null +++ b/yunke-server/scripts/backfill_customer_add_time.py @@ -0,0 +1,48 @@ +"""Backfill Customer.add_time from raw_json.addTime (no re-scrape). + +Iterates existing customer rows, parses raw_json.addTime (str or epoch) → +add_time ms-epoch, updates in batches. Idempotent: skips rows where add_time +is already set. WeChat rows have addTime; WhatsApp rows do not (createTime is +owner-staff, not customer-add) — those stay NULL. +""" +import asyncio +import time + +from sqlalchemy import select, update +from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine + +from app.db import models # noqa: F401 +from app.db.models import Customer +from app.scraper.wechat import _parse_add_time + +BATCH = 200 + + +async def main(db_url: str = "sqlite+aiosqlite:///./smoke.db") -> None: + engine = create_async_engine(db_url) + sm = async_sessionmaker(engine, expire_on_commit=False) + done = 0 + async with sm() as s: + rows = ( + await s.execute(select(Customer.friend_id, Customer.raw_json, Customer.add_time)) + ).all() + for fid, raw, existing in rows: + if existing or not raw: + continue + val = _parse_add_time(raw.get("addTime")) + if val is None: + continue + await s.execute( + update(Customer).where(Customer.friend_id == fid).values(add_time=val) + ) + done += 1 + if done % BATCH == 0: + await s.commit() + print(f"[{time.strftime('%H:%M:%S')}] {done} updated...") + await s.commit() + print(f"DONE — {done} customers got add_time") + await engine.dispose() + + +if __name__ == "__main__": + asyncio.run(main())