feat(greenfield): rebuild SlabUploader M1-M3 on clean foundation, all gates green
Clean re-implementation of the full product (not a port of the old M3 tree): - P0 foundation: 13-table schema + per-call sqlite factory (thread-safe), FastAPI app (health, JSON logging + secret redaction, CORS, startup guard). - P1 M1 Capture: mask/sheetKeep, geometry/rect (min-area rect), measure, png crop/inventory/overlay, EXIF decodeOrientedBitmap. - P2 M2 Photos: stateful capture session (batch, pick-cal, apply mask/crop to batch) + photos-ready lineup. - P3 M3 Create: real backend (crypto, woo client, taxonomy sync, pricing, inference Call1/Call2, review, publish, settings, slabs, photos) + real FE review/publish/settings screens consuming the API (no hardcoded Woo ids). - P4 gate exit: check_requirements + check_coverage both exit 0 (137/155 IDs, 0 gaps); backend 130/130, frontend 150/150, build green. Cite: docs/REQUIREMENTS.md (155 rows), docs/MILESTONES.md M1-M3.
This commit is contained in:
+23
-25
@@ -1,9 +1,11 @@
|
||||
"""Taxonomy sync: pull Woo categories/tags/attributes/terms into the local cache.
|
||||
|
||||
Rebuild happens in one transaction; on any failure the prior cache is kept.
|
||||
Only fig-* / feat-* tag families are retained (METADATA-15). taxonomy_anchor is a
|
||||
stable hash of the stored subset, recorded only when it changes.
|
||||
Rebuild happens in one transaction; on any failure the prior cache is kept
|
||||
(commit only on success). Only fig-* / feat-* tag families are retained
|
||||
(METADATA-15). taxonomy_anchor is a stable hash of the stored subset, recorded
|
||||
only when it changes.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
@@ -23,14 +25,12 @@ def _scoped_tags(tags: list[dict]) -> list[dict]:
|
||||
def _normalize_category(c: dict) -> tuple:
|
||||
"""Map a Woo REST category row into woo_categories insert values.
|
||||
|
||||
WooCommerce REST v3 returns `parent` (0 = top-level). Older fixtures may
|
||||
already use `parent_id`. Never invent ids — only store what the payload carries.
|
||||
WooCommerce REST v3 returns `parent` (0 = top-level); older fixtures may
|
||||
already use `parent_id`. Never invent ids — only store what the payload
|
||||
carries.
|
||||
"""
|
||||
raw_parent = c.get("parent_id", c.get("parent"))
|
||||
if raw_parent in (0, "0", None, ""):
|
||||
parent_id = None
|
||||
else:
|
||||
parent_id = int(raw_parent)
|
||||
parent_id = None if raw_parent in (0, "0", None, "") else int(raw_parent)
|
||||
woo_id = c.get("woo_id", c.get("id"))
|
||||
if woo_id is not None:
|
||||
woo_id = int(woo_id)
|
||||
@@ -49,21 +49,24 @@ def _subset_hash(categories: list[dict], attributes: list[dict],
|
||||
|
||||
|
||||
def sync(client: WooClient, conn) -> dict:
|
||||
"""Rebuild the taxonomy cache from the store. Raises WooError on failure (caller
|
||||
maps to 502; prior cache is retained because we only commit on success)."""
|
||||
"""Rebuild the taxonomy cache from the store. Raises WooError on failure
|
||||
(caller maps to 502; the prior cache survives because we commit on success)."""
|
||||
categories = client.get_categories()
|
||||
tags = client.get_tags()
|
||||
attributes = client.get_attributes()
|
||||
terms_by_attr = {a["id"]: client.get_attribute_terms(a["id"]) for a in attributes}
|
||||
scoped_tags = _scoped_tags(tags)
|
||||
|
||||
# One explicit transaction: the rebuild is atomic, and on any failure the
|
||||
# prior cache survives (autocommit mode — see db factory).
|
||||
conn.execute("BEGIN")
|
||||
# Attributes/tags/terms are not referenced by slabs FKs — safe to rebuild.
|
||||
conn.execute("DELETE FROM woo_attribute_terms")
|
||||
conn.execute("DELETE FROM woo_attributes")
|
||||
conn.execute("DELETE FROM woo_tags")
|
||||
|
||||
# Categories: upsert then drop stale rows. A full DELETE fails when slabs or
|
||||
# pricing_rules still reference species_id (live UAT IntegrityError on test-woo).
|
||||
# Categories: upsert, then drop stale rows. A blanket DELETE fails when
|
||||
# slabs or pricing_rules still reference species_id (live UAT IntegrityError).
|
||||
cat_rows = [_normalize_category(c) for c in categories]
|
||||
cat_ids = [r[0] for r in cat_rows]
|
||||
for row in cat_rows:
|
||||
@@ -73,20 +76,15 @@ def sync(client: WooClient, conn) -> dict:
|
||||
"name=excluded.name, slug=excluded.slug, parent_id=excluded.parent_id, woo_id=excluded.woo_id",
|
||||
row,
|
||||
)
|
||||
keep = (
|
||||
f"AND id NOT IN (SELECT species_id FROM slabs WHERE species_id IS NOT NULL) "
|
||||
f"AND id NOT IN (SELECT species_id FROM pricing_rules)"
|
||||
)
|
||||
if cat_ids:
|
||||
placeholders = ",".join("?" * len(cat_ids))
|
||||
conn.execute(
|
||||
f"DELETE FROM woo_categories WHERE id NOT IN ({placeholders}) "
|
||||
"AND id NOT IN (SELECT species_id FROM slabs WHERE species_id IS NOT NULL) "
|
||||
"AND id NOT IN (SELECT species_id FROM pricing_rules)",
|
||||
cat_ids,
|
||||
)
|
||||
conn.execute(f"DELETE FROM woo_categories WHERE id NOT IN ({placeholders}) {keep}", cat_ids)
|
||||
else:
|
||||
conn.execute(
|
||||
"DELETE FROM woo_categories "
|
||||
"WHERE id NOT IN (SELECT species_id FROM slabs WHERE species_id IS NOT NULL) "
|
||||
"AND id NOT IN (SELECT species_id FROM pricing_rules)"
|
||||
)
|
||||
conn.execute(f"DELETE FROM woo_categories {keep}")
|
||||
|
||||
conn.executemany(
|
||||
"INSERT INTO woo_attributes(id, name, slug, woo_id) VALUES (?,?,?,?)",
|
||||
@@ -106,7 +104,7 @@ def sync(client: WooClient, conn) -> dict:
|
||||
now = _now_iso()
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key, value, value_enc) VALUES ('taxonomy_last_sync_at', ?, NULL)", (now,))
|
||||
conn.execute("INSERT OR REPLACE INTO settings(key, value, value_enc) VALUES ('taxonomy_anchor', ?, NULL)", (anchor,))
|
||||
conn.commit()
|
||||
conn.execute("COMMIT")
|
||||
return {"taxonomy_anchor": anchor, "taxonomy_last_sync_at": now}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user