SPB Git forge

spb/fabri-ka

Public

Agrégateur de produits québécois — www.fabri-ka.com

217commits 1branches 0releases
66.1 MBsize
maindefault branch
3 h agolast push
Python 38.4% HTML 30.3% TypeScript 17.2% CSS 11% JavaScript 3.2%
3.1 KB · 85 lines python
Raw Blame History
1#!/usr/bin/env python32"""Vague 2 — recatégorisation des produits existants avec les signaux `details`.34Le classifieur (schema.infer_category) tient maintenant compte des options5Shopify et des attributs WooCommerce (matériaux, formats…) stockés dans la6colonne `details` depuis la vague 1. La sync n'actualise la catégorie que7lorsque le hash de contenu change ; ce script applique la nouvelle logique8rétroactivement à tous les produits actifs.910Usage : python3 scripts/recategorize.py [--dry-run]11"""12import argparse13import json14import os15import sys1617ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))18sys.path.insert(0, ROOT)1920from fabrika import db as fdb  # noqa: E40221from fabrika.schema import infer_category, details_signal_text  # noqa: E402222324def main():25    ap = argparse.ArgumentParser()26    ap.add_argument("--dry-run", action="store_true")27    args = ap.parse_args()2829    con = fdb.connect()30    cur = con.execute("SELECT uid, category, product_type, tags, title, "31                      "substr(description,1,200) AS descr, details "32                      "FROM products WHERE active=1")33    updates, scanned = [], 034    while True:35        rows = cur.fetchmany(5000)36        if not rows:37            break38        for r in rows:39            scanned += 140            tags = ""41            try:42                tags = " ".join(json.loads(r["tags"] or "[]"))43            except Exception:44                pass45            details = {}46            try:47                details = json.loads(r["details"]) if r["details"] else {}48            except Exception:49                pass50            new = infer_category(r["product_type"] or "", tags, r["title"] or "",51                                 r["descr"] or "", details_signal_text(details))52            # jamais de rétrogradation vers « autre » : beaucoup de53            # catégories sont héritées de la boutique (enrich_products.py)54            if new != "autre" and new != (r["category"] or ""):55                updates.append((new, r["uid"]))56    print(f"[recat] {scanned} produits actifs analysés, {len(updates)} changements")57    if args.dry_run or not updates:58        from collections import Counter59        c = Counter(u[0] for u in updates)60        print("[recat] top nouvelles catégories:", dict(c.most_common(10)))61        con.close()62        return63    con.close()64    import sqlite3, time65    done = 066    for i in range(0, len(updates), 2000):67        batch = updates[i:i + 2000]68        for attempt in range(20):     # la sync massive peut tenir le verrou > 60 s69            try:70                wcon = fdb.connect()71                wcon.executemany("UPDATE products SET category=? WHERE uid=?", batch)72                wcon.commit(); wcon.close()73                done += len(batch)74                break75            except sqlite3.OperationalError as exc:76                print(f"  [recat] verrou ({exc}), attente… ({attempt+1})", flush=True)77                time.sleep(15)78        else:79            raise SystemExit("[recat] verrou persistant, abandon")80    print(f"[recat] {done} catégories mises à jour")818283if __name__ == "__main__":84    main()85