fix(capture): persist the full AI taxonomy for capture-born garments — Design DNA is computed then dropped #689

Closed
opened 2026-09-18 10:38:21 +00:00 by shridhar.sabat · 7 comments
shridhar.sabat commented 2026-09-18 10:38:21 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Overview

Shridhar (UAT): "why all garments title shows 'Unclassified'?" — the big italic line on the garment detail sheet is the Design DNA, and every capture-born garment is missing it.

Root cause

The capture pipeline computes the full AI taxonomy and then throws most of it away when it persists the garment.

services/capture_job_processor.py::_fields_from_autosave_tags (the background/job capture path) builds the garment's tags from an explicit THREE-field list:

for k in ("silhouette", "neckline", "sleeve"):
    v = _pick_tag(tag_result, k)
    if v:
        tags[k] = v

plus colour (palette) and the category scalar that garment_capture_save_service.save_one_group injects (tags_body.setdefault("category", category)). The interactive path (components/BulkGarmentUpload.tsx) has the same allow-list twice: emptyFields() seeds tags: { silhouette: '', neckline: '', sleeve: '' } and aiFields() prefills only those keys, so buildFieldsPayload() can only ever send those three.

The model's design_dna, group, fabric_family, weave_knit_structure, coloration_method, value_addition, construction_details, length, closure, hem, fabric_texture, sub_type are all extracted (the prompt asks for every one of them — prompts/tagging_prompt.py:29) and then dropped on the floor. Only the description string keeps a trace ("Shirt · Charcoal · Regular silhouette · crinkled_crushed fabric").

Contrast with the single-upload path (routers/garments.py:251-288), which persists every auto_save field as a tag (and every needs_review field as a tag + review-queue row). That is why upload/PDF-ingest garments have DNA (gpt5_vision / pixtral_pdf rows) and capture-born garments do not.

Evidence

garments with a design_dna tag
UAT 1 of 20 — every draft M0093–M0105 has none
dev 61 of 128 (upload/PDF paths)

UAT M0104's complete tag set is category=mens (injected scalar), silhouette, neckline, sleeve, colour — the exact 3+1+1 shape the two allow-lists produce.

Consequence: the sheet's Design DNA headline prints the fallback word "Unclassified" (the sentinel SimilarIntentModal.tsx:8 treats as "no DNA"), and the reviewer's AI-tags form opens with 13 empty fields the model had already answered.

Fix

Persist the whole taxonomy the tagger returns, on BOTH capture paths, honestly attributed:

  1. _fields_from_autosave_tags: build tags from every taxonomy field present in the tagging result (auto_save preferred, then needs_review — the same preference _pick_tag already applies), skipping category (scalar + injected) and handling colour structurally as today.
  2. save_one_group: accept an optional tags_meta map (field -> {confidence, source}) so an AI-derived value is stored with the model's real confidence and source="ai" instead of being mislabelled source="human", confidence=1.0. Fields without meta keep today's behaviour.
  3. BulkGarmentUpload.tsx: widen emptyFields().tags / aiFields() to the taxonomy and send tags_meta — a value still equal to the AI reading is ai + its confidence; a value the reviewer typed over is human.

No extra model calls: the tagging result already exists at both save points.

Acceptance criteria

  1. A capture-born garment (both paths) carries the AI's design_dna (and the rest of the taxonomy) as a tag.
  2. Its detail sheet shows the real DNA instead of "Unclassified" (needs a non-null DNA), and the reviewer's AI-tags form is prefilled with the model's values.
  3. Persisted AI values carry the model's confidence and source="ai"; human-overridden values stay source="human", confidence=1.0.
  4. category is still persisted once (scalar + injected tag), never duplicated or clobbered by the AI's garment-type reading.
  5. The description string is byte-identical to today's output.
  6. Existing upload/PDF paths unchanged; UAT freeze config untouched.

Regression tests

  • Backend: _fields_from_autosave_tags returns the full taxonomy for a fixture tagging result (and the description unchanged); save_one_group writes source/confidence from tags_meta and keeps human/1.0 for fields without it.
  • Live: run a real capture (preview → save) on dev, assert the created garment's all_tags include design_dna with source="ai" and a confidence, then remove the probe garment.

Constraints

  • No new model calls and no provider change — persist what is already computed.
  • Dev is the reference branch; the report came from UAT.
# Overview Shridhar (UAT): *"why all garments title shows 'Unclassified'?"* — the big italic line on the garment detail sheet is the **Design DNA**, and every capture-born garment is missing it. ## Root cause The capture pipeline **computes the full AI taxonomy and then throws most of it away** when it persists the garment. `services/capture_job_processor.py::_fields_from_autosave_tags` (the background/job capture path) builds the garment's tags from an explicit THREE-field list: ```python for k in ("silhouette", "neckline", "sleeve"): v = _pick_tag(tag_result, k) if v: tags[k] = v ``` plus `colour` (palette) and the `category` scalar that `garment_capture_save_service.save_one_group` injects (`tags_body.setdefault("category", category)`). The interactive path (`components/BulkGarmentUpload.tsx`) has the same allow-list twice: `emptyFields()` seeds `tags: { silhouette: '', neckline: '', sleeve: '' }` and `aiFields()` prefills only those keys, so `buildFieldsPayload()` can only ever send those three. The model's `design_dna`, `group`, `fabric_family`, `weave_knit_structure`, `coloration_method`, `value_addition`, `construction_details`, `length`, `closure`, `hem`, `fabric_texture`, `sub_type` are all extracted (the prompt asks for every one of them — `prompts/tagging_prompt.py:29`) and then dropped on the floor. Only the `description` string keeps a trace ("Shirt · Charcoal · Regular silhouette · crinkled_crushed fabric"). Contrast with the single-upload path (`routers/garments.py:251-288`), which persists **every** `auto_save` field as a tag (and every `needs_review` field as a tag + review-queue row). That is why upload/PDF-ingest garments have DNA (`gpt5_vision` / `pixtral_pdf` rows) and capture-born garments do not. ## Evidence | | garments with a `design_dna` tag | |---|---| | UAT | **1 of 20** — every draft M0093–M0105 has none | | dev | 61 of 128 (upload/PDF paths) | UAT M0104's complete tag set is `category=mens` (injected scalar), `silhouette`, `neckline`, `sleeve`, `colour` — the exact 3+1+1 shape the two allow-lists produce. Consequence: the sheet's Design DNA headline prints the fallback word **"Unclassified"** (the sentinel `SimilarIntentModal.tsx:8` treats as "no DNA"), and the reviewer's AI-tags form opens with 13 empty fields the model had already answered. ## Fix Persist the whole taxonomy the tagger returns, on BOTH capture paths, **honestly attributed**: 1. `_fields_from_autosave_tags`: build `tags` from every taxonomy field present in the tagging result (auto_save preferred, then needs_review — the same preference `_pick_tag` already applies), skipping `category` (scalar + injected) and handling `colour` structurally as today. 2. `save_one_group`: accept an optional `tags_meta` map (`field -> {confidence, source}`) so an AI-derived value is stored with the model's real confidence and `source="ai"` instead of being mislabelled `source="human"`, `confidence=1.0`. Fields without meta keep today's behaviour. 3. `BulkGarmentUpload.tsx`: widen `emptyFields().tags` / `aiFields()` to the taxonomy and send `tags_meta` — a value still equal to the AI reading is `ai` + its confidence; a value the reviewer typed over is `human`. No extra model calls: the tagging result already exists at both save points. ## Acceptance criteria 1. A capture-born garment (both paths) carries the AI's `design_dna` (and the rest of the taxonomy) as a tag. 2. Its detail sheet shows the real DNA instead of "Unclassified" (needs a non-null DNA), and the reviewer's AI-tags form is prefilled with the model's values. 3. Persisted AI values carry the model's confidence and `source="ai"`; human-overridden values stay `source="human"`, `confidence=1.0`. 4. `category` is still persisted once (scalar + injected tag), never duplicated or clobbered by the AI's garment-type reading. 5. The `description` string is byte-identical to today's output. 6. Existing upload/PDF paths unchanged; UAT freeze config untouched. ## Regression tests - Backend: `_fields_from_autosave_tags` returns the full taxonomy for a fixture tagging result (and the description unchanged); `save_one_group` writes `source`/`confidence` from `tags_meta` and keeps `human`/1.0 for fields without it. - Live: run a real capture (preview → save) on dev, assert the created garment's `all_tags` include `design_dna` with `source="ai"` and a confidence, then remove the probe garment. ## Constraints - No new model calls and no provider change — persist what is already computed. - Dev is the reference branch; the report came from UAT.
shridhar.sabat commented 2026-09-18 11:03:24 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Merged to dev (6dc0d4f3) and verified live with a REAL capture. Awaiting the UAT promotion + Shridhar's phone test.

Deployed artifact (not CI-green alone)

  • checkout /etc/dokploy/compose/sapl-sapl-dev/code HEAD = 6dc0d4f361bc38cc2dd79dec15a8c77ef61e3fa1
  • backend AND worker rebuilt: grep -c CAPTURE_TAXONOMY_FIELDS /app/services/capture_job_processor.py → 2 in sapl-sapl-dev-backend-1 and in sapl-sapl-dev-worker-1 (a frontend-only deploy reuses the cached backend image, so this was checked explicitly)
  • frontend bundle badge dev-6dc0d4f3; the new client payload is live (grep -l tags_meta → assets/Upload-*.js)

Live end-to-end: a real auto_save capture job on dev
POST /api/garments-capture/jobs (2 real shots, auto_save: true, capture_mode: camera) → job 4d5b3613-c105-4440-87ec-131cab06ae89 → worker classified, tagged, grouped and saved draft M0046.

tags persisted: 16  (before this fix: 5 = category + silhouette + neckline + sleeve + colour)
  design_dna           -> 'classic_minimal'      source=ai  conf=0.92  label=high
  fabric_family        -> 'Knit'                 source=ai  conf=0.98  label=high
  hem                  -> 'straight'             source=ai  conf=0.92  label=high
  closure              -> 'pullover'             source=ai  conf=0.85  label=high
  construction_details -> '["Side slit", "Unlined"]'  source=ai  conf=0.85  (JSON — the single-upload storage shape)
  group / sub_type / coloration_method / weave_knit_structure / fabric_texture / value_addition  -> present, source=ai
  category             -> 'mens'  source=human  conf=1.0     (the injected facet — unchanged behaviour)
description: "Polo · Dusty Blue · Regular silhouette · knit_jersey fabric"   (unchanged shape)

The whole fields payload carried 14 tags_meta entries, i.e. the values are attributed to the model instead of masquerading as human 100% verifications.

Rendered result — the detail sheet of that freshly captured draft now reads Classic Minimal as its headline instead of the fallback word "Unclassified" (read from the live DOM, mobile 390×844 and desktop 1440×900):

  • /root/.hermes/scripts/sapl-garment-detail-parity-screenshots/dna/mobile-sheet-headline.png
  • /root/.hermes/scripts/sapl-garment-detail-parity-screenshots/dna/desktop-sheet-headline.png

Data left as found: the probe garment was hard-deleted after the assertions — DELETE /api/garments/{id}?hard=true → 200, then GET → 404 and garments/garment_tags both count 0 for that id. The probe CaptureJob row remains in capture_jobs (dev test artifact, no UI surface once terminal).

Tests

tests/test_capture_jobs_autosave.py   branch: 1 failed, 22 passed
                                      pristine origin/dev baseline: 1 failed, 18 passed
  same single failure by name: test_autosave_clean_group_saves_normal_garment
  (the documented staleness vs the #569 never-auto-save policy)
  → +4 new tests, no regression
tests/test_capture_jobs.py test_garment_capture_bulk.py test_garments_capture.py
tests/test_garment_capture_service.py test_review_save_hardening.py   88 passed

Second commit (refs #684), self-audit of the shipped parity fix: the sheet's Category row preferred the mens/womens facet column over the taxonomy tag, so a record whose AI-tags Category was corrected to "Dress" would have rendered "Mens" while the edit form said "Dress". The tag now wins; the harness now also asserts VALUE equality (it only asserted row existence — the gap that let this through).

**Merged to `dev` (`6dc0d4f3`) and verified live with a REAL capture.** Awaiting the UAT promotion + Shridhar's phone test. **Deployed artifact (not CI-green alone)** - checkout `/etc/dokploy/compose/sapl-sapl-dev/code` HEAD = `6dc0d4f361bc38cc2dd79dec15a8c77ef61e3fa1` - **backend AND worker rebuilt**: `grep -c CAPTURE_TAXONOMY_FIELDS /app/services/capture_job_processor.py` → `2` in `sapl-sapl-dev-backend-1` and in `sapl-sapl-dev-worker-1` (a frontend-only deploy reuses the cached backend image, so this was checked explicitly) - frontend bundle badge `dev-6dc0d4f3`; the new client payload is live (`grep -l tags_meta` → `assets/Upload-*.js`) **Live end-to-end: a real auto_save capture job on dev** `POST /api/garments-capture/jobs` (2 real shots, `auto_save: true`, `capture_mode: camera`) → job `4d5b3613-c105-4440-87ec-131cab06ae89` → worker classified, tagged, grouped and saved draft **M0046**. ``` tags persisted: 16 (before this fix: 5 = category + silhouette + neckline + sleeve + colour) design_dna -> 'classic_minimal' source=ai conf=0.92 label=high fabric_family -> 'Knit' source=ai conf=0.98 label=high hem -> 'straight' source=ai conf=0.92 label=high closure -> 'pullover' source=ai conf=0.85 label=high construction_details -> '["Side slit", "Unlined"]' source=ai conf=0.85 (JSON — the single-upload storage shape) group / sub_type / coloration_method / weave_knit_structure / fabric_texture / value_addition -> present, source=ai category -> 'mens' source=human conf=1.0 (the injected facet — unchanged behaviour) description: "Polo · Dusty Blue · Regular silhouette · knit_jersey fabric" (unchanged shape) ``` The whole fields payload carried 14 `tags_meta` entries, i.e. the values are attributed to the model instead of masquerading as human 100% verifications. **Rendered result** — the detail sheet of that freshly captured draft now reads **`Classic Minimal`** as its headline instead of the fallback word "Unclassified" (read from the live DOM, mobile 390×844 and desktop 1440×900): - `/root/.hermes/scripts/sapl-garment-detail-parity-screenshots/dna/mobile-sheet-headline.png` - `/root/.hermes/scripts/sapl-garment-detail-parity-screenshots/dna/desktop-sheet-headline.png` **Data left as found:** the probe garment was hard-deleted after the assertions — `DELETE /api/garments/{id}?hard=true` → 200, then `GET` → 404 and `garments`/`garment_tags` both count `0` for that id. The probe CaptureJob row remains in `capture_jobs` (dev test artifact, no UI surface once terminal). **Tests** ``` tests/test_capture_jobs_autosave.py branch: 1 failed, 22 passed pristine origin/dev baseline: 1 failed, 18 passed same single failure by name: test_autosave_clean_group_saves_normal_garment (the documented staleness vs the #569 never-auto-save policy) → +4 new tests, no regression tests/test_capture_jobs.py test_garment_capture_bulk.py test_garments_capture.py tests/test_garment_capture_service.py test_review_save_hardening.py 88 passed ``` **Second commit (refs #684), self-audit of the shipped parity fix:** the sheet's `Category` row preferred the mens/womens facet column over the taxonomy tag, so a record whose AI-tags Category was corrected to "Dress" would have rendered "Mens" while the edit form said "Dress". The tag now wins; the harness now also asserts VALUE equality (it only asserted row existence — the gap that let this through).
shridhar.sabat commented 2026-09-18 11:24:48 +00:00 (Migrated from gitea.ai-automation-hub.tech)

UAT promotion deployed and verified live (4e0163dd, badge uat-4e0163d). Plus the Designer-headline check Shridhar asked for mid-flight.

Deployed artifact (UAT)

  • checkout /etc/dokploy/compose/sapl-sapl-uat/code HEAD = 4e0163dda4c38a144ae6dc3d20851e26c52fb6de
  • deploy-uat + Smoke test UAT PWA green; backend AND worker rebuilt: grep -c CAPTURE_TAXONOMY_FIELDS /app/services/capture_job_processor.py → 2 in both containers, _tag_attribution present in the save service
  • frontend badge uat-4e0163d, and the new client payload is in the bundle (grep -l tags_meta …/assets/Upload-*.js)

Live UAT proof — INTERACTIVE path (preview → save), chosen because the auto_save JOB path web-pushes the owner's device at terminal and I did not want to buzz Shridhar's phone. Real shots pulled from the UAT image store, payload built exactly as the capture sheet builds it (tag text + tags_meta + structured palette):

preview ok: 2 groups, 15/14 tags sent, 14/13 tags_meta entries
save: ok | M0001 + M0002 created, 0 failed

M0001 — 16 tags (was 5 before this fix):
  design_dna            src=ai  conf=0.88  :: classic_minimal
  fabric_family         src=ai  conf=0.90  :: Knit
  weave_knit_structure  src=ai  conf=0.85  :: Waffle
  closure               src=ai  conf=0.95  :: button_down_front
  hem / length / neckline / silhouette / sleeve / value_addition / group  -> src=ai + confidences
  construction_details  src=ai  conf=0.90  :: Button-through, Side slit, No pocket
  category              src=human conf=1.0 :: mens      (the injected facet — unchanged)
  >> headline renders: classic_minimal
M0002 — 15 tags, design_dna src=ai conf=0.88 :: classic_minimal (Woven / Seersucker / crinkled_crushed …)

Rendered in the live UAT UI: the sheet headline reads "Classic Minimal" on mobile 390×844 and desktop 1440×900 (DOM-read, not eyeballed) — the record that would previously have said "Unclassified".
Screenshots: /root/.hermes/scripts/sapl-garment-detail-parity-screenshots/uat-dna/.

Data left as found: both probe garments hard-deleted after the assertions — DELETE ?hard=true → 200, GET → 404, and garments/garment_tags both count 0 for those ids.

Designer acceptance (Shridhar: "make sure designers can edit headline") — the headline IS the design_dna tag, and the Designer role already carries library: manage, so no code change was needed; I proved it end-to-end on dev with REAL Designer credentials (qa.designer@saplind.com, role Designer, scope: own):

PASS  a Designer sees the Edit action on the detail sheet
PASS  DESIGN DNA field is editable in the form (not disabled/read-only)
PASS  the saved headline shows the Designer's value  — headline="Qa-Probe-…"
PASS  the DNA tag is stored as a human edit          — source=human
PASS  original DNA restored (record left as found)
PASS  no JS errors

Screenshots: /root/.hermes/scripts/sapl-designer-dna-screenshots/ (before / editing / after).

Two data-shape nuances found while verifying (worth their own follow-up, NOT regressions):

  1. construction_details storage differs by path: the job path now writes JSON (upload-path parity) while the interactive sheet sends the readable text it renders in the form — both are displayed, but one field has two shapes across capture paths.
  2. The sheet's "Construction details" row reads the garment COLUMN, and _GARMENT_FIELD_KEYS does not include construction_details, so capture-born garments (column NULL) hide that row even though the tag now carries a value — the same parity class as #684, small.
**UAT promotion deployed and verified live (`4e0163dd`, badge `uat-4e0163d`).** Plus the Designer-headline check Shridhar asked for mid-flight. **Deployed artifact (UAT)** - checkout `/etc/dokploy/compose/sapl-sapl-uat/code` HEAD = `4e0163dda4c38a144ae6dc3d20851e26c52fb6de` - `deploy-uat` + `Smoke test UAT PWA` green; backend AND worker rebuilt: `grep -c CAPTURE_TAXONOMY_FIELDS /app/services/capture_job_processor.py` → `2` in both containers, `_tag_attribution` present in the save service - frontend badge `uat-4e0163d`, and the new client payload is in the bundle (`grep -l tags_meta …/assets/Upload-*.js`) **Live UAT proof — INTERACTIVE path (preview → save), chosen because the auto_save JOB path web-pushes the owner's device at terminal and I did not want to buzz Shridhar's phone.** Real shots pulled from the UAT image store, payload built exactly as the capture sheet builds it (tag text + `tags_meta` + structured palette): ``` preview ok: 2 groups, 15/14 tags sent, 14/13 tags_meta entries save: ok | M0001 + M0002 created, 0 failed M0001 — 16 tags (was 5 before this fix): design_dna src=ai conf=0.88 :: classic_minimal fabric_family src=ai conf=0.90 :: Knit weave_knit_structure src=ai conf=0.85 :: Waffle closure src=ai conf=0.95 :: button_down_front hem / length / neckline / silhouette / sleeve / value_addition / group -> src=ai + confidences construction_details src=ai conf=0.90 :: Button-through, Side slit, No pocket category src=human conf=1.0 :: mens (the injected facet — unchanged) >> headline renders: classic_minimal M0002 — 15 tags, design_dna src=ai conf=0.88 :: classic_minimal (Woven / Seersucker / crinkled_crushed …) ``` Rendered in the live UAT UI: the sheet headline reads **"Classic Minimal"** on mobile 390×844 and desktop 1440×900 (DOM-read, not eyeballed) — the record that would previously have said "Unclassified". Screenshots: `/root/.hermes/scripts/sapl-garment-detail-parity-screenshots/uat-dna/`. **Data left as found:** both probe garments hard-deleted after the assertions — `DELETE ?hard=true` → 200, `GET` → 404, and `garments`/`garment_tags` both count `0` for those ids. **Designer acceptance (Shridhar: "make sure designers can edit headline")** — the headline IS the `design_dna` tag, and the Designer role already carries `library: manage`, so no code change was needed; I proved it end-to-end on **dev** with REAL Designer credentials (`qa.designer@saplind.com`, role Designer, `scope: own`): ``` PASS a Designer sees the Edit action on the detail sheet PASS DESIGN DNA field is editable in the form (not disabled/read-only) PASS the saved headline shows the Designer's value — headline="Qa-Probe-…" PASS the DNA tag is stored as a human edit — source=human PASS original DNA restored (record left as found) PASS no JS errors ``` Screenshots: `/root/.hermes/scripts/sapl-designer-dna-screenshots/` (before / editing / after). **Two data-shape nuances found while verifying (worth their own follow-up, NOT regressions):** 1. `construction_details` storage differs by path: the job path now writes JSON (upload-path parity) while the interactive sheet sends the readable text it renders in the form — both are displayed, but one field has two shapes across capture paths. 2. The sheet's "Construction details" row reads the garment COLUMN, and `_GARMENT_FIELD_KEYS` does not include `construction_details`, so capture-born garments (column NULL) hide that row even though the tag now carries a value — the same parity class as #684, small.
shridhar.sabat commented 2026-09-18 11:25:08 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Correction to nuance 2 above (checked the column, not assumed): capture-born garments do not leave construction_details NULL — it holds an empty JSON list [] (M0084/M0104/M0105 all show []). The sheet's Construction-details row therefore hides because the joined value is EMPTY, not because the column is NULL. Same net effect and the same small parity gap (the tag carries a value the sheet's column-based row does not read), stated accurately.

**Correction to nuance 2 above (checked the column, not assumed):** capture-born garments do not leave `construction_details` NULL — it holds an empty JSON list `[]` (`M0084/M0104/M0105` all show `[]`). The sheet's Construction-details row therefore hides because the joined value is EMPTY, not because the column is NULL. Same net effect and the same small parity gap (the tag carries a value the sheet's column-based row does not read), stated accurately.
shridhar.sabat commented 2026-09-18 12:02:35 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Bug-testing round complete — one NEW latent bug found (not from #689), plus the round's live verification.

NEW bug: style-number reservation can livelock a whole series (verified, reproduced live)

plm_service.reserve_next_style_number creates the counter row at count=1 for any (segment, category) key that has no row yet, and the capture save's uniqueness check counts soft-deleted rows. So a fresh series key whose first candidate is already taken can never save, and because the failed save rolls the whole transaction back — counter row included — every retry repeats the same collision:

POST /api/garments-capture/save  segment="mens" (bogus value; the sheet only sends new_dev|shopping)
  -> reserve: series_key "mens:mens" has no counter row -> count=1 -> M0001
  -> M0001 exists (soft-deleted since 2026-09-08)
  -> 409 'Style number M0001 already exists.'   <-- and the counter row is rolled back with it
  -> next attempt: M0001 again. Forever.

Reproduced on dev with two groups (both failed identically). Impact today is LATENT: the segments the UI actually sends have healthy counters (new_dev:mens 47 on dev, 106 on UAT), so capture saves work — but any new segment value, a restored/reset style_counters, or a tier whose counter lags its existing styles bricks that series with an error the user cannot act on. Suggested fix (small, plm_service.py): after computing the candidate, advance past any style number that already exists (regardless of is_deleted) before returning it. Not shipped — it is production style numbering, so it wants an explicit decision.

Round-2 fixes verified LIVE on deployed dev (c4deaa27)

Live interactive capture on dev (real shots, payload built exactly as the capture sheet builds it) → M0048: 17 tags, which exercises every fix:

sub_type             src=ai conf=0.94  :: short-sleeve      <- was dropped client-side entirely
colour               src=ai conf=0.96  :: [{"colour_name":"Grey",...}]  <- was human/1.0
silhouette           src=ai conf=0.92  :: Relaxed            <- plain text, NOT '["Relaxed"]'
construction_details src=ai conf=0.93  :: Button-through, No pocket, Side slit
design_dna           src=ai conf=0.90  :: classic_minimal    -> headline "Classic Minimal"
category             src=human conf=1.0 :: mens             <- injected facet, meta ignored

save: ok, both groups saved (M0048/M0049), probes hard-deleted afterwards (GET → 404, garments → 0 rows).

Housekeeping on the shared tiers

  • Deleted my own leftover probe rows: dev M0048/M0049; and on UAT the bogus mens:mens counter row my hand-built probe created (new_dev:* counters untouched: 106 / 6).
  • Left untouched (not mine to delete): dev garment M0047 (non-draft, created 17:06:56 IST today, design_dna=romantic_feminine, colour tagged human). It is not from any of my probes — flagging it so dev activity is accounted for.
  • I did not touch the soft-deleted M0001 on dev (2026-09-08): it is the trigger of the livelock above but it is legitimate history.
**Bug-testing round complete — one NEW latent bug found (not from #689), plus the round's live verification.** ### NEW bug: style-number reservation can livelock a whole series (verified, reproduced live) `plm_service.reserve_next_style_number` creates the counter row at `count=1` for any `(segment, category)` key that has no row yet, and the capture save's uniqueness check counts **soft-deleted** rows. So a fresh series key whose first candidate is already taken can never save, and because the failed save rolls the whole transaction back — counter row included — **every retry repeats the same collision**: ``` POST /api/garments-capture/save segment="mens" (bogus value; the sheet only sends new_dev|shopping) -> reserve: series_key "mens:mens" has no counter row -> count=1 -> M0001 -> M0001 exists (soft-deleted since 2026-09-08) -> 409 'Style number M0001 already exists.' <-- and the counter row is rolled back with it -> next attempt: M0001 again. Forever. ``` Reproduced on dev with two groups (both failed identically). Impact today is LATENT: the segments the UI actually sends have healthy counters (`new_dev:mens` 47 on dev, 106 on UAT), so capture saves work — but any new segment value, a restored/reset `style_counters`, or a tier whose counter lags its existing styles bricks that series with an error the user cannot act on. Suggested fix (small, `plm_service.py`): after computing the candidate, advance past any style number that already exists (regardless of `is_deleted`) before returning it. Not shipped — it is production style numbering, so it wants an explicit decision. ### Round-2 fixes verified LIVE on deployed dev (`c4deaa27`) Live interactive capture on dev (real shots, payload built exactly as the capture sheet builds it) → **M0048: 17 tags**, which exercises every fix: ``` sub_type src=ai conf=0.94 :: short-sleeve <- was dropped client-side entirely colour src=ai conf=0.96 :: [{"colour_name":"Grey",...}] <- was human/1.0 silhouette src=ai conf=0.92 :: Relaxed <- plain text, NOT '["Relaxed"]' construction_details src=ai conf=0.93 :: Button-through, No pocket, Side slit design_dna src=ai conf=0.90 :: classic_minimal -> headline "Classic Minimal" category src=human conf=1.0 :: mens <- injected facet, meta ignored ``` `save: ok`, both groups saved (M0048/M0049), probes hard-deleted afterwards (`GET` → 404, `garments` → 0 rows). ### Housekeeping on the shared tiers - Deleted my own leftover probe rows: dev M0048/M0049; and on **UAT** the bogus `mens:mens` counter row my hand-built probe created (`new_dev:*` counters untouched: 106 / 6). - **Left untouched (not mine to delete):** dev garment **M0047** (non-draft, created 17:06:56 IST today, `design_dna=romantic_feminine`, `colour` tagged `human`). It is not from any of my probes — flagging it so dev activity is accounted for. - I did **not** touch the soft-deleted `M0001` on dev (2026-09-08): it is the trigger of the livelock above but it is legitimate history.
shridhar.sabat commented 2026-09-18 12:28:25 +00:00 (Migrated from gitea.ai-automation-hub.tech)

UAT promotion of the review rounds is live and verified: see sapl/sapl#700 (latest comment) for the deployed-artifact checks and the live capture dump (uat-c91e660, M0107/M0108 then hard-deleted).

Remaining open questions for Shridhar: (1) close #683/#684/#689 after his phone test, (2) the two deferred product decisions — five fields landing tag-only while the facets/analytics read the garment column, and the read-only sheet no longer showing the mens/womens facet once a category tag exists.

UAT promotion of the review rounds is live and verified: see sapl/sapl#700 (latest comment) for the deployed-artifact checks and the live capture dump (`uat-c91e660`, M0107/M0108 then hard-deleted). Remaining open questions for Shridhar: (1) close #683/#684/#689 after his phone test, (2) the two deferred product decisions — five fields landing tag-only while the facets/analytics read the garment column, and the read-only sheet no longer showing the mens/womens facet once a category tag exists.
shridhar.sabat commented 2026-09-18 12:55:54 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Promoted to PROD via the standard chain (sapl/sapl#705, merge 7444ed64) and verified on the deployed artifact — full evidence in the #705 comment (backend/worker markers, bundle badge, deployed-function probes, read-only parity check on prod M0004, and prod's pre-fix data state).

Remaining: closing #683/#684/#689 after Shridhar's phone test on UAT.

Promoted to PROD via the standard chain (sapl/sapl#705, merge `7444ed64`) and verified on the deployed artifact — full evidence in the #705 comment (backend/worker markers, bundle badge, deployed-function probes, read-only parity check on prod M0004, and prod's pre-fix data state). Remaining: closing #683/#684/#689 after Shridhar's phone test on UAT.
shridhar.sabat commented 2026-09-24 07:07:02 +00:00 (Migrated from gitea.ai-automation-hub.tech)

Follow-up filed: #902 — the presentation + concept half of this issue, with live evidence.

Your #689 fix is merged and verified (capture-born garments now carry the AI's full taxonomy), but the word
still shows on every other surface, because it is a frontend fallback, not missing data:

  • frontend/src/lib/library.ts:76 invents dna = 'Unclassified' when a record has no design_dna tag, and
    four surfaces render that invented value as the garment's title (Library card, detail <h2>,
    delete-confirm copy, "Find pieces like" header).
  • Measured live on dev today: Library → Concepts = 48 of 48 cards titled Unclassified; Made = 2 of 48
    (HUNT-P001 PDF-ingest, SA CD01 QA draft); the detail <h2> too.
  • Population without DNA: dev 64/116 · demo 63 · uat 20 (pre-#689 capture-born) · prod 2 — its entire library.
  • Root cause (b): AI concepts are born with zero tags — save_concept writes no GarmentTag rows at all,
    and the moodboard path sends decode_id while the board's real design_dna is dropped.

#902 fixes the sentinel-as-title contract, relabels the facet bucket No design story, and attaches the
board's design_dna to moodboard-born concepts (source="moodboard_brief"). This issue's own ACs stay as
they are — do not close #689 on the strength of #902.

**Follow-up filed: #902** — the *presentation + concept* half of this issue, with live evidence. Your #689 fix is merged and verified (capture-born garments now carry the AI's full taxonomy), but the word still shows on **every other** surface, because it is a frontend fallback, not missing data: - `frontend/src/lib/library.ts:76` invents `dna = 'Unclassified'` when a record has no `design_dna` tag, and four surfaces render that invented value as the garment's **title** (Library card, detail `<h2>`, delete-confirm copy, "Find pieces like" header). - Measured live on dev today: **Library → Concepts = 48 of 48 cards titled `Unclassified`**; Made = 2 of 48 (`HUNT-P001` PDF-ingest, `SA CD01` QA draft); the detail `<h2>` too. - Population without DNA: dev 64/116 · demo 63 · uat 20 (pre-#689 capture-born) · **prod 2 — its entire library**. - Root cause (b): AI concepts are born with **zero** tags — `save_concept` writes no `GarmentTag` rows at all, and the moodboard path sends `decode_id` while the board's real `design_dna` is dropped. #902 fixes the sentinel-as-title contract, relabels the facet bucket `No design story`, and attaches the board's `design_dna` to moodboard-born concepts (`source="moodboard_brief"`). This issue's own ACs stay as they are — do not close #689 on the strength of #902.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sapl/sapl#689