"""Mock-data seeder (ADDITIVE + idempotent) for JeV testing and phase-6 calibration. docker compose exec bot python tools/seed_mock.py Rules: - Never deletes or modifies existing user data; existing rows (by name/key/id) are skipped, so re-runs are safe and the team's own test data is untouched. - Deterministic ids (uuid5) for every created entity → stable cross-references. - Covers: units, brands, attribute definitions + material EAV values, materials, brand/material links, material unit conversions, suppliers (+categories), supplier↔material edges, projects, warehouses, inventory + ledger, and three full quotes: offered / accepted multi-project / arrived (receipt + allocation + PURCHASE ledger row + decisions + events). - All numeric parameters use Decimal (asyncpg is strict about numeric columns). """ import asyncio import sys import uuid from datetime import date, datetime, timedelta, timezone from decimal import Decimal from pathlib import Path sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from sqlalchemy import text from app.db import SessionLocal NS = uuid.UUID("6d3ed6a0-9e11-4b2a-8f7e-000000000000") def uid(*parts: str) -> uuid.UUID: return uuid.uuid5(NS, "|".join(parts)) def D(v) -> Decimal: return Decimal(str(v)) STATS: dict[str, list[int]] = {} def _bump(kind: str, created: int, skipped: int) -> None: c, s = STATS.get(kind, [0, 0]) STATS[kind] = [c + created, s + skipped] UNITS_EXTRA = [ ("sh", "شاخه", "count"), ("m2", "متر مربع", "area"), ("pack", "بسته", "count"), ("roll", "رول", "count"), ("sheet", "ورق", "count"), ] BRANDS = [ "فولاد مبارکه", "ذوب آهن اصفهان", "سیمان تهران", "سیمان سپاهان", "پلیکا", "البرز", "سرامیک آسیا", ] ATTR_DEFS = [ ("spec_قطر", "قطر"), ("spec_سایز", "سایز"), ("spec_گرید", "گرید"), ("spec_ضخامت", "ضخامت"), ("spec_بسته‌بندی", "بسته‌بندی"), ] # name, canonical unit, supplier, brand|None, specs[(key,value)], conversions[(unit, factor)] MATERIALS = [ ("میلگرد آجدار", "kg", "نمایندگی فولاد مبارکه", "فولاد مبارکه", [("spec_سایز", "سایز ۱۴"), ("spec_گرید", "آجدار A3")], [("شاخه", 12), ("تن", 1000)]), ("تیرآهن", "kg", "نمایندگی فولاد مبارکه", "فولاد مبارکه", [("spec_سایز", "سایز ۱۸")], [("شاخه", 226)]), ("ورق سیاه", "kg", "نمایندگی فولاد مبارکه", "فولاد مبارکه", [("spec_ضخامت", "۲ میل")], [("ورق", "24.6")]), ("پروفیل قوطی", "kg", "نمایندگی فولاد مبارکه", "فولاد مبارکه", [("spec_سایز", "۴۰×۴۰"), ("spec_ضخامت", "۲ میل")], []), ("سیمان پرتلند", "kg", "بازرگانی مصطفوی", "سیمان تهران", [("spec_بسته‌بندی", "۵۰ کیلویی")], [("کیسه", 50), ("تن", 1000)]), ("گچ و خاک", "kg", "بازرگانی مصطفوی", None, [("spec_بسته‌بندی", "۳۰ کیلویی")], [("کیسه", 30)]), ("گچ ساختمانی", "kg", "بازرگانی مصطفوی", None, [], [("کیسه", 30)]), ("شن و ماسه", "kg", "بازرگانی مصطفوی", None, [], []), ("لوله پلیکا", "m", "پلیکا فروش البرز", "پلیکا", [("spec_قطر", "۶ اینچ")], [("شاخه", 6)]), ("لوله مانیسمان", "m", "تجهیزات آبی پارس", None, [("spec_قطر", "۲ اینچ")], []), ("سه راهی پلیکا", "piece", "پلیکا فروش البرز", "پلیکا", [("spec_قطر", "۶ اینچ")], []), ("شیر فلکه", "piece", "تجهیزات آبی پارس", None, [("spec_سایز", "۲ اینچ")], []), ("کاشی سرامیک", "m2", "سرامیک آسیا", "سرامیک آسیا", [("spec_سایز", "۶۰×۶۰")], []), ("رنگ روغنی", "kg", "شیمی صنعت سپاهان", None, [("spec_بسته‌بندی", "گالن ۳.۶ کیلویی")], []), ("الکترود جوشکاری", "kg", "ابزار سازان اصفهان", None, [("spec_سایز", "۳.۲")], []), ("نایلون شیرینک", "kg", "شیمی صنعت سپاهان", None, [], []), ] SUPPLIERS = [ ("بازرگانی مصطفوی", "construction-materials", "اصفهان، خیابان امام", "031-32223344", "حاج آقا مصطفوی"), ("نمایندگی فولاد مبارکه", "metals-steel-sections", "اصفهان، شهرک صنعتی", "031-33445566", "مهندس کریمی"), ("پلیکا فروش البرز", "mechanical-installations-pipes", "کرج، جاده مخصوص", "026-34567890", "آقای رضایی"), ("تجهیزات آبی پارس", "mechanical-installations-pipes", "تهران، بازار آهن", "021-55667788", "آقای شریفی"), ("سرامیک آسیا", "finishing-interior-decoration", "یزد، میبد", "035-32345678", "آقای دهقان"), ("ابزار سازان اصفهان", "construction-tools-equipment", "اصفهان، بازار", "031-32112233", "آقای نوری"), ("شیمی صنعت سپاهان", "construction-chemicals", "اصفهان، شهرک علمی", "031-33889900", "خانم احمدی"), ] PROJECTS = [ ("دفتر", "دفتر مرکزی شرکت"), ("منشانه", "پروژه منشانه"), ("باغ اسپارت", "پروژه باغ اسپارت"), ("پردیس کاشان", "پروژه پردیس کاشان"), ("بابک", "پروژه بابک"), ] async def _name_id(s, table: str, name: str): row = (await s.execute( text(f"SELECT id FROM {table} WHERE name = :n LIMIT 1"), {"n": name} )).scalar() return str(row) if row else None async def seed() -> None: now = datetime.now(timezone.utc) async with SessionLocal() as s: # ---------------- units ---------------- created = skipped = 0 for code, name_fa, dim in UNITS_EXTRA: r = await s.execute(text( "INSERT INTO units (code, name_fa, dimension, si_factor, is_base) " "VALUES (:c, :n, :d, 1, false) ON CONFLICT (code) DO NOTHING" ), {"c": code, "n": name_fa, "d": dim}) created += r.rowcount skipped += 1 - r.rowcount _bump("units", created, skipped) _unit_rows = (await s.execute(text("SELECT id, code, name_fa FROM units"))).all() unit_map = {r.code: r.id for r in _unit_rows} unit_fa = {r.name_fa: r.id for r in _unit_rows} def unit_of(ref) -> int: return unit_map.get(ref) or unit_fa[ref] # ---------------- brands ---------------- created = skipped = 0 brand_id: dict[str, str] = {} for b in BRANDS: existing = await _name_id(s, "brands", b) if existing: brand_id[b] = existing skipped += 1 continue bid = uid("brand", b) await s.execute(text( "INSERT INTO brands (id, name, country) VALUES (:id, :name, 'ایران') ON CONFLICT (id) DO NOTHING" ), {"id": str(bid), "name": b}) brand_id[b] = str(bid) created += 1 _bump("brands", created, skipped) # ---------------- attribute definitions ---------------- created = skipped = 0 attr_id: dict[str, str] = {} for key, label in ATTR_DEFS: row = (await s.execute( text("SELECT id FROM attribute_definitions WHERE attribute_key=:k"), {"k": key} )).scalar() if row: attr_id[key] = str(row) skipped += 1 continue aid = uid("attr", key) await s.execute(text( "INSERT INTO attribute_definitions (id, attribute_key, label, data_type, enum_values) " "VALUES (:id, :k, :l, 'string', NULL) ON CONFLICT (id) DO NOTHING" ), {"id": str(aid), "k": key, "l": label}) attr_id[key] = str(aid) created += 1 _bump("attribute_definitions", created, skipped) # ---------------- suppliers ---------------- created = skipped = 0 cat_map = {r.slug: str(r.id) for r in (await s.execute(text("SELECT id, slug FROM material_categories"))).all()} supplier_id: dict[str, str] = {} for name, slug, location, phone, contact in SUPPLIERS: existing = await _name_id(s, "suppliers", name) if existing: supplier_id[name] = existing skipped += 1 continue sid = uid("supplier", name) await s.execute(text( "INSERT INTO suppliers (id, name, category_id, location, phone, contact_person) " "VALUES (:id, :name, :cat, :loc, :phone, :contact) ON CONFLICT (id) DO NOTHING" ), {"id": str(sid), "name": name, "cat": cat_map.get(slug), "loc": location, "phone": phone, "contact": contact}) supplier_id[name] = str(sid) created += 1 _bump("suppliers", created, skipped) # ---------------- materials (+ links, EAV, conversions) ---------------- created = skipped = 0 material_id: dict[str, str] = {} brand_material_of: dict[str, str | None] = {} brand_of: dict[str, str | None] = {} for name, unit_code, sup_name, brand, specs, convs in MATERIALS: mid = await _name_id(s, "materials", name) if mid is None: mid = str(uid("material", name)) await s.execute(text( "INSERT INTO materials (id, name, canonical_unit_id, status) " "VALUES (:id, :name, :unit, 'active') ON CONFLICT (id) DO NOTHING" ), {"id": mid, "name": name, "unit": unit_map[unit_code]}) created += 1 else: skipped += 1 material_id[name] = mid for key, value in specs: await s.execute(text( "INSERT INTO material_attribute_values " "(id, material_id, attribute_definition_id, value_string) " "VALUES (:id, :m, :a, :v) " "ON CONFLICT (material_id, attribute_definition_id) DO NOTHING" ), {"id": str(uid("mav", name, key)), "m": mid, "a": attr_id[key], "v": value}) if brand: bmid = str(uid("bm", brand, name)) await s.execute(text( "INSERT INTO brand_materials (id, brand_id, material_id, is_active) " "VALUES (:id, :b, :m, true) ON CONFLICT (brand_id, material_id) DO NOTHING" ), {"id": bmid, "b": brand_id[brand], "m": mid}) brand_material_of[name] = bmid brand_of[name] = brand_id[brand] else: brand_material_of[name] = None brand_of[name] = None for conv_code, factor in convs: exists = (await s.execute(text( "SELECT 1 FROM material_units WHERE material_id=:m AND brand_material_id IS NULL " "AND unit_id=:u LIMIT 1" ), {"m": mid, "u": unit_of(conv_code)})).scalar() if exists: continue await s.execute(text( "INSERT INTO material_units (id, material_id, brand_material_id, unit_id, " "conversion_factor, is_default) VALUES (:id, :m, NULL, :u, :f, false)" ), {"id": str(uid("mu", name, conv_code)), "m": mid, "u": unit_of(conv_code), "f": D(factor)}) await s.execute(text( "INSERT INTO supplier_materials (id, supplier_id, material_id, is_active, " "last_quoted_price, last_quoted_at) VALUES (:id, :sup, :m, true, :price, :at) " "ON CONFLICT (supplier_id, material_id) DO NOTHING" ), {"id": str(uid("sm", sup_name, name)), "sup": supplier_id[sup_name], "m": mid, "price": D("100000"), "at": now}) _bump("materials", created, skipped) # ---------------- projects + warehouses ---------------- created = skipped = 0 project_id: dict[str, str] = {} for name, desc in PROJECTS: pid = await _name_id(s, "projects", name) if pid is None: pid = str(uid("project", name)) await s.execute(text( "INSERT INTO projects (id, name, description) VALUES (:id, :name, :desc) " "ON CONFLICT (id) DO NOTHING" ), {"id": pid, "name": name, "desc": desc}) created += 1 else: skipped += 1 project_id[name] = pid _bump("projects", created, skipped) created = skipped = 0 warehouse_id: dict[tuple[str, str], str] = {} for pname, pid in project_id.items(): for wname in ("انبار مرکزی", "انبار کارگاه"): wid = str(uid("wh", pname, wname)) exists = (await s.execute(text( "SELECT 1 FROM warehouses WHERE id=:id LIMIT 1"), {"id": wid})).scalar() if exists: skipped += 1 else: await s.execute(text( "INSERT INTO warehouses (id, project_id, name, description) " "VALUES (:id, :p, :n, :d)" ), {"id": wid, "p": pid, "n": wname, "d": f"{wname} پروژه {pname}"}) created += 1 warehouse_id[(pname, wname)] = wid _bump("warehouses", created, skipped) # ---------------- inventory + ledger ---------------- inv_plan = [ ("منشانه", "انبار مرکزی", "گچ و خاک", None, "200", "kg"), ("منشانه", "انبار کارگاه", "سیمان پرتلند", "سیمان تهران", "150", "kg"), ("بابک", "انبار مرکزی", "میلگرد آجدار", "فولاد مبارکه", "800", "kg"), ("پردیس کاشان", "انبار مرکزی", "لوله پلیکا", "پلیکا", "60", "m"), ] created = skipped = 0 for pname, wname, mname, bname, qty, unit_code in inv_plan: iid = str(uid("inv", pname, wname, mname)) exists = (await s.execute(text( "SELECT 1 FROM inventory WHERE id=:id LIMIT 1"), {"id": iid})).scalar() if exists: skipped += 1 continue await s.execute(text( "INSERT INTO inventory (id, warehouse_id, material_id, brand_id, quantity, unit_id, " "specs, specs_hash) VALUES (:id, :w, :m, :b, :q, :u, '{}'::jsonb, 'default')" ), {"id": iid, "w": warehouse_id[(pname, wname)], "m": material_id[mname], "b": brand_id.get(bname) if bname else None, "q": D(qty), "u": unit_map[unit_code]}) await s.execute(text( "INSERT INTO inventory_transactions (id, inventory_id, warehouse_id, transaction_type, " "quantity, performed_by, transaction_date, note) " "VALUES (:id, :inv, :w, 'PURCHASE', :q, 'کاربر آزمایشی', :at, 'موجودی اولیه (seed)')" ), {"id": str(uid("tx", iid, "purchase")), "inv": iid, "w": warehouse_id[(pname, wname)], "q": D(qty), "at": now - timedelta(days=20)}) created += 1 _bump("inventory", created, skipped) # ---------------- quotes ---------------- created = skipped = 0 admin_uid = (await s.execute(text( "SELECT id FROM users WHERE role='admin' ORDER BY created_at LIMIT 1" ))).scalar() admin_uid = str(admin_uid) if admin_uid else None async def insert_quote_block(qkey, supplier, projects, status, quote_date, expiry, accepted, arrived, is_official, notes, items, termsheets, decisions_map, events, receipts=None, legacy_project=None): qid = str(uid("quote", qkey)) exists = (await s.execute(text("SELECT 1 FROM quotes WHERE id=:id"), {"id": qid})).scalar() if exists: return False await s.execute(text( "INSERT INTO quotes (id, supplier_id, project_id, quote_date, expiry_date, " "accepted_date, arrived_date, status, is_official, notes) VALUES " "(:id, :sup, :proj, :qd, :ed, :ad, :ar, CAST(:st AS quote_status), :off, :notes)" ), {"id": qid, "sup": supplier_id[supplier], "proj": project_id.get(legacy_project) if legacy_project else None, "qd": quote_date, "ed": expiry, "ad": accepted, "ar": arrived, "st": status, "off": is_official, "notes": notes}) for p in projects: await s.execute(text( "INSERT INTO quote_projects (quote_id, project_id) VALUES (:q, :p) " "ON CONFLICT DO NOTHING"), {"q": qid, "p": project_id[p]}) ts_ids: dict[str, str] = {} for ts in termsheets: tid = str(uid("ts", qkey, ts["label"])) ts_ids[ts["label"]] = tid await s.execute(text( "INSERT INTO termsheets (id, quote_id, label, status, discount_type, discount_value) " "VALUES (:id, :q, :l, CAST(:st AS termsheet_status), CAST(:dt AS discount_type), :dv)" ), {"id": tid, "q": qid, "l": ts["label"], "st": ts["status"], "dt": ts.get("discount_type", "none"), "dv": D(ts.get("discount_value", 0))}) for i, (due, amt, note) in enumerate(ts["steps"], start=1): await s.execute(text( "INSERT INTO termsheet_schedule_steps (id, termsheet_id, step_no, due_date, " "amount, note) VALUES (:id, :ts, :no, :due, :amt, :note)" ), {"id": str(uid("step", qkey, ts["label"], str(i))), "ts": tid, "no": i, "due": due, "amt": D(amt), "note": note}) line_ids: dict[str, str] = {} its_ids: dict[tuple[str, str], str] = {} for it in items: lid = str(uid("li", qkey, it["material"])) line_ids[it["material"]] = lid await s.execute(text( "INSERT INTO line_items (id, quote_id, material_id, brand_material_id, unit_id, " "quantity, notes) VALUES (:id, :q, :m, :bm, :u, :qty, NULL)" ), {"id": lid, "q": qid, "m": material_id[it["material"]], "bm": brand_material_of.get(it["material"]), "u": unit_map[it["unit"]], "qty": D(it["qty"])}) for p in it.get("projects", projects): await s.execute(text( "INSERT INTO line_item_projects (line_item_id, project_id) VALUES (:li, :p) " "ON CONFLICT DO NOTHING"), {"li": lid, "p": project_id[p]}) if it.get("price") is not None: tlabel = it.get("termsheet", termsheets[0]["label"]) iid = str(uid("it", qkey, it["material"], tlabel)) its_ids[(it["material"], tlabel)] = iid await s.execute(text( "INSERT INTO item_termsheets (id, line_item_id, termsheet_id, unit_price, " "min_order_qty) VALUES (:id, :li, :ts, :price, NULL)" ), {"id": iid, "li": lid, "ts": ts_ids[tlabel], "price": D(it["price"])}) dec_ids: dict[str, str] = {} for mname, dec in decisions_map.items(): did = str(uid("dec", qkey, mname)) chosen_its = (its_ids.get((mname, dec.get("termsheet"))) if dec.get("status") == "chosen" else None) await s.execute(text( "INSERT INTO item_decisions (id, line_item_id, chosen_item_termsheet_id, status, " "project_id, adjusted_unit_price, decided_at) VALUES " "(:id, :li, :its, CAST(:st AS item_decision_status), :proj, :price, :at)" ), {"id": did, "li": line_ids[mname], "its": chosen_its, "st": dec["status"], "proj": project_id.get(dec.get("project")) if dec.get("project") else None, "price": D(dec["price"]) if dec.get("price") is not None else None, "at": accepted if dec.get("status") == "chosen" else None}) dec_ids[mname] = did for ev in events: at = {"offered": quote_date, "accepted": accepted, "arrived": arrived}.get(ev) or quote_date await s.execute(text( "INSERT INTO quote_events (id, quote_id, event_type, payload, occurred_at, actor) " "VALUES (:id, :q, CAST(:ev AS quote_event_type), '{}'::jsonb, :at, 'کاربر آزمایشی (seed)')" ), {"id": str(uid("ev", qkey, ev)), "q": qid, "ev": ev, "at": at}) for rc in receipts or []: rid = str(uid("rc", qkey, rc["material"])) await s.execute(text( "INSERT INTO item_receipts (id, item_decision_id, confirmed_by, received_at, " "received_qty, on_time, quality_note) VALUES (:id, :d, :by, :at, :qty, true, NULL)" ), {"id": rid, "d": dec_ids[rc["material"]], "by": admin_uid, "at": arrived, "qty": D(rc["qty"])}) for wname, alloc_qty in rc["allocations"]: aid = str(uid("alloc", qkey, rc["material"], wname)) wid = warehouse_id[(rc["project"], wname)] await s.execute(text( "INSERT INTO item_receipt_allocations (id, receipt_id, warehouse_id, quantity) " "VALUES (:id, :r, :w, :q)" ), {"id": aid, "r": rid, "w": wid, "q": D(alloc_qty)}) inv_row = (await s.execute(text( "SELECT id FROM inventory WHERE warehouse_id=:w AND material_id=:m " "AND COALESCE(brand_id, '00000000-0000-0000-0000-000000000000') = " "COALESCE(CAST(:b AS uuid), '00000000-0000-0000-0000-000000000000') " "AND specs_hash='default' LIMIT 1" ), {"w": wid, "m": material_id[rc["material"]], "b": brand_of.get(rc["material"])})).scalar() qty_canonical = D(alloc_qty) * D(rc.get("factor", 1)) if inv_row is None: inv_id = str(uid("inv", rc["project"], wname, rc["material"])) await s.execute(text( "INSERT INTO inventory (id, warehouse_id, material_id, brand_id, quantity, " "unit_id, specs, specs_hash) " "VALUES (:id, :w, :m, :b, :q, :u, '{}'::jsonb, 'default')" ), {"id": inv_id, "w": wid, "m": material_id[rc["material"]], "b": brand_of.get(rc["material"]), "q": qty_canonical, "u": unit_map[rc.get("canonical", "kg")]}) else: inv_id = str(inv_row) await s.execute(text( "UPDATE inventory SET quantity = quantity + :q WHERE id=:id" ), {"q": qty_canonical, "id": inv_id}) await s.execute(text( "INSERT INTO inventory_transactions (id, inventory_id, warehouse_id, " "receipt_allocation_id, transaction_type, quantity, performed_by, " "transaction_date, note) VALUES (:id, :inv, :w, :alloc, 'PURCHASE', :q, " "'کاربر آزمایشی', :at, :note)" ), {"id": str(uid("tx", qkey, rc["material"], wname)), "inv": inv_id, "w": wid, "alloc": aid, "q": qty_canonical, "at": arrived, "note": f"ورود از رسید خرید {rc['material']}"}) return True d_today = date(2026, 10, 12) ok1 = await insert_quote_block( "q1", "پلیکا فروش البرز", ["پردیس کاشان"], "offered", now - timedelta(days=2), d_today + timedelta(days=12), None, None, True, "[mock] استعلام لوله و اتصالات پردیس", items=[ {"material": "لوله پلیکا", "unit": "sh", "qty": "120", "price": "450000"}, {"material": "سه راهی پلیکا", "unit": "piece", "qty": "40", "price": "85000"}, ], termsheets=[{"label": "نقد امروز (۵٪ تخفیف)", "status": "offered", "discount_type": "percent", "discount_value": "5", "steps": [(d_today, "60000000", "تسویه نقدی یکباره")]}], decisions_map={"لوله پلیکا": {"status": "pending"}, "سه راهی پلیکا": {"status": "pending"}}, events=["offered"], legacy_project="پردیس کاشان", ) ok2 = await insert_quote_block( "q2", "بازرگانی مصطفوی", ["منشانه", "دفتر"], "accepted", now - timedelta(days=9), d_today + timedelta(days=5), now - timedelta(days=6), None, False, "[mock] خرید گچ و سیمان پروژه‌های منشانه و دفتر", items=[ {"material": "گچ و خاک", "unit": "bag", "qty": "100", "price": "85000", "termsheet": "سه قسط ماهانه", "projects": ["منشانه"]}, {"material": "سیمان پرتلند", "unit": "bag", "qty": "50", "price": "185000", "termsheet": "سه قسط ماهانه", "projects": ["دفتر"]}, ], termsheets=[{"label": "سه قسط ماهانه", "status": "accepted", "steps": [(d_today, "5875000", "قسط اول"), (d_today + timedelta(days=30), "5875000", "قسط دوم"), (d_today + timedelta(days=60), "5875000", "قسط سوم")]}], decisions_map={ "گچ و خاک": {"status": "chosen", "project": "منشانه", "termsheet": "سه قسط ماهانه", "price": "85000"}, "سیمان پرتلند": {"status": "chosen", "project": "دفتر", "termsheet": "سه قسط ماهانه", "price": "185000"}, }, events=["offered", "accepted"], legacy_project="منشانه", ) ok3 = await insert_quote_block( "q3", "نمایندگی فولاد مبارکه", ["بابک"], "arrived", now - timedelta(days=15), d_today - timedelta(days=1), now - timedelta(days=12), now - timedelta(days=3), True, "[mock] میلگرد پروژه بابک (رسید کامل)", items=[{"material": "میلگرد آجدار", "unit": "ton", "qty": "2", "price": "42000000"}], termsheets=[{"label": "نقد امروز", "status": "accepted", "steps": [(d_today, "84000000", "تسویه")]}], decisions_map={"میلگرد آجدار": {"status": "chosen", "project": "بابک", "termsheet": "نقد امروز", "price": "42000000"}}, events=["offered", "accepted", "arrived"], receipts=[{"material": "میلگرد آجدار", "qty": "2", "project": "بابک", "allocations": [("انبار مرکزی", "2")], "factor": 1000, "canonical": "kg"}], legacy_project="بابک", ) _bump("quotes", sum(1 for x in (ok1, ok2, ok3) if x), sum(1 for x in (ok1, ok2, ok3) if not x)) await s.commit() print("\n=== seed summary (created / skipped) ===") for kind, (c, sk) in STATS.items(): print(f" {kind:22} created={c:3} skipped={sk:3}") print("done. Nothing was deleted or modified.") if __name__ == "__main__": asyncio.run(seed())