Commit Graph
15 Commits
Author SHA1 Message Date
root 76a505744f feat(embeddings): count-verified batching, bounded concurrency, batched Chroma queries
gemini-embedding-2-preview collapses a plain contents=[...] string list into
one concatenated input (single vector), so naive batching silently drops
vectors. Add two safe, configurable levers to EmbeddingService.embed_texts():

- EMBEDDING_BATCH_SIZE (>1): attempts a real multi-content request, VERIFIES
  the returned embedding count, and falls back to per-item embedding when the
  API collapses or errors. One vector per input, in input order, always.
- EMBEDDING_MAX_CONCURRENCY (>1): bounded ThreadPoolExecutor across items or
  chunks; results re-collected in input order.

Zero-vector degradation is preserved throughout: a failed input yields a zero
vector at its own position, never a dropped or misaligned entry. Defaults
(1/1) reproduce the previous strictly-sequential behavior exactly.

Batched ChromaDB querying:
- New query_similar_by_embeddings(): ONE collection.query() for N vectors,
  results aligned to inputs; zero vectors are excluded (cosine vs zero vector
  is meaningless) and come back empty; falls back to per-vector queries if the
  batched call fails, keeping items independent.
- query_similar_by_embedding() now delegates to it, keeping the nested result
  shape so existing callers are unaffected.
- moodboard _match_library_per_item() uses the batched query: N Chroma calls
  per brief -> 1, and failed embeddings can no longer surface garbage matches.

Focused offline tests in backend/tests/test_embedding_optimization.py
(16 tests: ordering, collapse fallback, zero-vector slots, concurrency,
alignment, backward-compat shape, moodboard adapter).
2026-08-04 13:40:04 +05:30
Hermesandroot 4f159d7916 perf(moodboard): bounded concurrent page analysis with configurable pacing + robust retries
Replace the sequential per-page loop (one Gemini vision call at a time with a
fixed 5s sleep) with a configurable semaphore pipeline in the new
services/moodboard_page_runner.py:

- MOODBOARD_PAGE_CONCURRENCY caps parallel page analyses (clamped [1,8]);
  MOODBOARD_PAGE_DELAY paces page launches (0 disables; 5.0 keeps the
  historical request rate); MOODBOARD_PAGE_RETRY_DELAYS sets the backoff
  schedule; MOODBOARD_PAGE_ATTEMPT_TIMEOUT bounds each attempt.
- Results stay in page order regardless of completion order; progress
  (current_page_status / processed / page_results) persists after every page.
- Retries now cover 429 + transient 5xx + transport errors (was 429-only),
  honour a Retry-After hint, and give up immediately once the schedule is
  exhausted (was: sleep after last attempt + one unprotected extra call) -
  the per-page fallback then produces a neutral result as before.
- Image-batch retries reuse the same helper (same drop-on-failure contract).

Smoke checks: backend/scripts/smoke_moodboard_concurrency.py (stdlib-only,
pytest-collectable) - 11/11 pass.
2026-08-04 13:40:04 +05:30
Ajay SableandClaude Opus 4.8 a51bf2b3f4 fix(moodboard): accurate library matching — DB-grounded fabric availability + real threshold + embed_texts batch bug
The "Fabric direction" cards showed in-house garment matches for fabrics
SAPL doesn't stock. Calibrating against the live gemini-embedding-2 index
proved cosine similarity can't judge availability (a suede query scores
0.69 > cotton poplin 0.66; nonsense 0.51), so availability must be a DB
fact.

- _fabric_status: vocabulary-driven, matches STRUCTURED fabric fields only
  (composition/weave/texture/pattern), never free-text description — kills
  the blend/soft/coloured false-positives; replaces loose check_fabric_library
  in the moodboard grounding.
- _match_library_per_item: gap-gate — a sourcing-gap fabric surfaces zero
  in-house garments.
- embed_texts: gemini-embedding-2 returns ONE embedding for contents=[N],
  silently collapsing N per-item queries to 1; embed per-text instead
  (restores silhouette/print matching that was dropped by a zip).
- _enrich_matches: calibrated similarity floor (moodboard_match_min_similarity
  0.58, env MOODBOARD_MATCH_MIN_SIM) drops nonsense-tier hits.
- Frontend: subtle library-status hint on fabric cards (in / similar / not in
  library) so an empty gap card doesn't read as broken.

Validated live vs real DB: tweed/suede/terry→gap (no in-house), cotton
poplin/twill→have, satin/organza+viscose→similar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 11:23:47 +05:30
Ajay SableandClaude Opus 4.8 4c9486dca2 feat(ai+scraper): OpenCode Go multi-model layer + MOA Deep Research, gender-aware grounded trend scraper, calm bento report
AI / OpenCode Go:
- New services/llm/ shared multi-model layer (opencode_client, moa_orchestrator,
  oai_format, routing) — additive alongside Gemini; all Gemini paths untouched.
- MOA "Deep Research" agent (services/hermes/agent_moa) + POST /api/hermes/ask-with-moa:
  3 parallel reference models + aggregator with fallback chain + transient retry.
- Frontend "Deep" toggle in Ask KAARIGAR (client.ts, AskHermes) rendering model_trace.
- Per-feature routing (services/llm/routing): scraper reasoning → minimax-m3,
  Gemini as automatic first-turn fallback.

Trend scraper:
- Reasoning loop routed to minimax (scraper_agent) with grounding guardrails.
- Grounded in SAPL's own fabric + garment libraries (check_fabric_library /
  check_garment_library) — flags real sourcing gaps, before + after scraping.
- Gender-aware BRAND_URLS (women/men/kids) — agent identifies gender+category
  from the brief; scrape_all_brands sweeps every brand carrying that gender.
- URL fixes (Mango GB, Pull&Bear, Next, UO, Free People, Stradivarius) + worker
  hardening (Inditex CDNs + self-heal, currentSrc/srcset, salvage-on-close, retry).
- Pinterest crash root-caused to a Camoufox/Playwright driver bug; patched via
  services/playwright_patch (idempotent, re-applied on startup in main.py).

Report UI:
- Markdown renderer (tables/lists/headers, compact variant) + BentoReport: a calm,
  visual brief for creatives (palette band, signal confidence dots, "what to make"
  cards, verdict) with grounding/feasibility/gaps behind a "Full analysis" toggle
  (feasibility scorecard + gaps-vs-strengths chips).

tsc -b + backend py_compile clean; smoke tests verified (opencode 10/10, glue 9/9).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-25 21:37:59 +05:30
Ajay SableandClaude Opus 4.8 6817016761 feat(feed): SAPL Home event-feed — curated cross-OS studio feed + KAARIGAR daily brief + share-to-studio
Replaces the static Library landing with a familiar (Facebook/LinkedIn-style)
home feed that surfaces genuinely interesting activity across the whole OS,
curated so routine uploads don't flood it.

Feed (services/feed_service.py + routers/feed.py):
- Derived-on-read events: user posts, TNA-approved moments, boards, moodboard
  briefs, trend reports, big library/fabric drops, weekly WFX new-order digest,
  review milestones. Interestingness scoring suppresses routine uploads unless
  engaged (reactions/comments rescue them).
- Interactions: react / comment / save + user posts (new tables feed_reactions,
  feed_comments, feed_saves, feed_posts — auto-create on boot).
- Cursor pagination + "Load more".

KAARIGAR Daily Brief (services/feed_digest.py, GET /api/feed/brief):
- Persona-aware "what matters today" from Stitch/TNA — designer/planner/merchant/
  quality/fabric/intern see role-relevant items; admin sees the full studio view.

Share-to-studio (models/feed_share.py, POST/GET /api/feed/share):
- Boards / moodboard briefs / trend reports are private by default; owner
  publishes to the feed. Shared items get read-through access (collections/
  moodboard/scraper detail GETs). "Share to studio" button on all three pages.

Frontend: pages/Home.tsx (3-col profile / feed / SAPL-Today+activity),
ShareToStudioButton, client methods; / rewired to the feed, Library -> /library
(old /?facet deep-links redirect). Verified: smoke_test_feed 31/31 +
playwright_check_feed 13/13; tsc -b + build + py_compile clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Dehx6goqDV2TiQ92dFFWGP
2026-07-24 10:59:18 +05:30
Ajay SableandClaude Opus 4.8 f27b1f6b78 feat(tna): env-scoped Stitch owner-username alias (saplind-Deji→saplind-Das on UAT)
The UAT tenant seeds Deji Das as saplind-Das, but prod's template master (portal2)
carries saplind-Deji (live-verified 2026-07-14). Remap owner usernames at the push
boundary, active only for a UAT integration host; prod keeps the original usernames.
Unblocks the in-app push (all 9 Category templates now POST clean to UAT).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-14 12:49:29 +05:30
Ajay SableandClaude Opus 4.8 3b0230969c feat(tna): reconcile Stitch TNA write-back integration + Template-Readiness
Foundation for the TNA authoring→approve→push flow. Reconciles our dormant
scaffold to Stitch's delivered contract (write-back proven live 8 Jul):

- config: split integration host/key (stitch_integration_host/stitch_key),
  correct write path /api/integration/tna-template/
- stitch_tna_api: 3 real endpoints (task-master, task-category-master,
  tna-template POST), STITCH-KEY-only auth
- tna_export: real Create schema — category-by-name, username owners,
  dependency-relationship type + depends_on chain, topological ordering,
  validate_payload enforcing the server rules (exactly 4 critical /
  ≥1 sewing / ≥1 shipment) discovered via live POST
- stitch_master: cached task dictionary + template reconciliation
  (naming-variant detection) + master-aware validation gate
- routers/tna: /tna/stitch/{task-dictionary,reconciliation,validate/{id}}
- frontend: Template-Readiness page (/tna/stitch-sync) + "Stitch" tab
- scripts/dryrun_stitch_template.py: build+validate payload (no POST)
- docs/plans: full end-to-end TNA platform plan + STATUS update

Verified on branch: py_compile + tsc -b + build clean; read-only dry-run
against live UAT Stitch builds + validates as expected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 11:45:01 +05:30
Ajay SableandClaude Opus 4.8 ff63ee5991 fix(auth): bake public Google client id as config default so prod SSO shows
The compose environment override didn't reliably reach the backend container on
Dokploy (env-only redeploy didn't rebuild/recreate). Setting the PUBLIC OAuth
client id as the config.py default guarantees /auth/config returns it — the code
change forces a backend rebuild, and the value is compiled in. Env still overrides.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 11:05:18 +05:30
Ajay SableandClaude Opus 4.8 651d0c9bdf feat(auth): auto-provision company Google sign-ins with a limited default role
First-time @saplind.com sign-in via Google now creates the user
automatically with google_default_role (default "Viewer" — read-only on
the design surfaces), instead of rejecting unknown emails. Domain +
email_verified are still enforced; existing users are never modified, so
anyone already assigned a higher role (e.g. Admin) keeps it. Admins raise
access afterwards in /admin. Set google_default_role="" to revert to
invite-only. Also stores google_client_secret in config (unused by the
ID-token flow) for completeness.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 15:52:39 +05:30
Ajay SableandClaude Opus 4.8 335ba2b820 feat(stitch): scaffold parametrised TNA API client (dormant, flag-gated)
Stitch committed (Manish Arora, 2026-06-29) to release a TNA API set on/before
2026-07-07: GET TNA Task / TNA Task Category / TNA Assign Users, and POST TNA
Template Creation/Update (the write path we previously lacked).

Add a thin client for those four endpoints, behind the new
stitch_tna_api_enabled flag (OFF by default — no runtime change until live):
  - reuses the existing STITCH-KEY integration auth + stitch_api_host
  - endpoint paths are config-overridable (exact paths unpublished) so the real
    ones drop in from Stitch's spec without a code change
  - _extract_rows tolerates both list and paginated (data.results+next) envelopes
  - fetch_tna_tasks / fetch_tna_task_categories / fetch_tna_assign_users (GET),
    upsert_tna_template (POST), tna_api_status(), and a read-only probe()
  - guards raise StitchTnaApiError when disabled or unconfigured

Compiles/imports; flag-off guard + envelope parser unit-checked.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 17:51:18 +05:30
Ajay SableandClaude Opus 4.8 93edc7f66f feat(studio): Product Studio Pro as a native proxy (our UI, their backend)
Replaces the earlier iframe embed: per Ajay, it should be our SAPL frontend over
the vendor's backend, exactly like Concept Studio — not the vendor's branded page.

Live-probed the backend and confirmed the contract (and corrected the trial-quota
worry): /api/tenant-info shows isTrial:false (paid tenant, no quota); /api/edit-image
works anonymously with the image as a base64 data-URL -> requestId; poll
/api/edit-status until COMPLETED -> images[].

- backend/routers/product_studio.py: POST /api/product-studio/generate
  (gated moodboard:edit) base64s the upload, submits, polls to completion,
  downloads the result, persists to data/concepts (served /concepts/).
  config.product_studio_base; registered in main.py.
- frontend/pages/ProductStudio.tsx: native SAPL page (upload, prompt + seed chips,
  aspect ratio, generate, result, download). client.ts productStudioGenerate().

Smokes: end-to-end backend via the real vendor with a synthetic image 4/4 PASS
(851 KB PNG persisted); native-UI render 5/5 PASS (no iframe). tsc -b + build +
py_compile clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:22:52 +05:30
Ajay SableandClaude Opus 4.8 678e0764a5 feat(studio): Concept Studio — Swatch→Garment via AIBrandSolutions proxy
New "Studio" tab (/concept): drop a garment shape + a fabric swatch, describe
the item, and generate a concept preview. Proxies SAPL's commissioned
AIBrandSolutions Swatch→Garment backend server-side (no vendor creds needed),
persists the result to data/concepts/, and serves it same-origin behind our
login/RBAC.

- backend/routers/concept.py: POST /api/concept/swatch-to-garment (moodboard:edit)
- config: swatch_to_garment_base
- main.py: mount /concepts (fresh-checkout-safe mkdir) + register router
- frontend: ConceptStudio page, "Studio" nav tab, swatchToGarment()/conceptImageUrl()
- docs: 24-Jun design-library meeting notes + STATUS entry

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 18:31:23 +05:30
Ajay SableandClaude Opus 4.8 a71fc6d911 feat(tna): hide Stitch demo orders from the TNA views
Add a hide_demo_orders flag (default on) and route the order-list endpoints
through a single _orders_q() helper that excludes "*-Demo" styles, so the
TNA-8/10/11-Demo orders from Stitch don't reach pilot users. Reversible via
config; detail lookups by style_number are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:29:58 +05:30
Ajay SableandClaude Opus 4.8 7fa236854e feat(auth): Google Workspace SSO (company sign-in)
Add "Continue with Google" to the login page, gated to the company domain
(saplind.com) and invite-first (the user must already exist). Verifies the
Google ID token server-side and issues our existing app token — RBAC unchanged.

Dormant until IT provides GOOGLE_CLIENT_ID: GET /api/auth/config reports
whether SSO is enabled so the button only renders when configured; password
login is unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:20:21 +05:30
shridhar.sabat c5a11f6adc Initial commit: SAPL backend + frontend + docs 2026-06-21 18:37:36 +05:30