Rather than relying on reactive 65s retries, each semaphore slot is held
for at least MinBatchIntervalSeconds (20s). With 2 concurrent slots that
limits throughput to ~3 batches/min × ~2k tokens = ~6k output TPM,
safely under the 8k/min limit.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Haiku has generous rate limits so parallelism is safe again. Retry
logic catches any 429s. Progress estimate updated to ~8s per wave.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Testing Haiku 4.5 for catalog price analysis — structured JSON output
with explicit rules is well within its capabilities. Revert to Sonnet
if result quality is insufficient.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Tier 1 Anthropic accounts are capped at 8,000 output tokens/minute on
Sonnet. 3 concurrent batches burst well past that, causing 429s.
- MaxConcurrentBatches: 3 → 1 (sequential prevents burst)
- Add retry: on rate_limit_error, wait 65s then retry up to 3 times
so the per-minute window resets before the next attempt
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Item name + category path give Claude sufficient context for surface area
estimation. Descriptions add input tokens without meaningfully improving
verdict quality.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Skip $0-priced items (placeholders/category headers) in RunAiPriceCheck
- Build full category path (e.g. "Cerakote > Firearms") via BuildCategoryPath
so Claude receives coating-type context — Cerakote pricing differs significantly
from standard powder coat
- Update AI system prompt to instruct Claude to use the category path when
determining process type, equipment, cure times, and market rates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
500-item catalog was making 50 sequential API calls, causing progressive rate-limit
throttling (explains "super slow towards the end") and ~$3 in credits.
- BatchSize: 10 → 25 (word limits are in place; 25 items × ~80 tokens ≈ 2000
output tokens, well within MaxTokens=8192 — the original truncation cause)
- Run up to 3 batches concurrently via SemaphoreSlim(3) — independent API calls
with no shared state, so no growing context issue
- For a 500-item catalog: 50 sequential calls → 20 calls in ~7 parallel waves,
roughly 4× faster and 60% cheaper
- Dropped unused `costs` param from AnalyzeBatchAsync (system prompt has all costs)
- JS progress timing updated to reflect parallel waves
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Still seeing stubs despite MaxTokens=8192 — smaller batches and explicit
word limits in the prompt eliminate any remaining truncation risk.
- BatchSize: 15 → 10 (~1200 output tokens per batch vs. potential 3000+)
- Prompt: added 20-word cap on assumptions, 25-word cap on reasoning
- Prompt: strengthened "nothing before or after the '['" instruction
- Error log: now includes item IDs and first 300 chars of raw response
so the next failure tells us exactly what Claude returned
- JS timing: updated batch divisor from 25 → 10 to match actual batch size
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause: MaxTokens=4096 was too low — 25 items at ~250 tokens each hit the
limit mid-array (logged error showed Path: $[17]).
- MaxTokens: 4096 → 8192
- BatchSize: 25 → 15 items (keeps each response well under the limit)
- StripJsonFences → ExtractJsonArray: now also handles prose before/after the
JSON array, and recovers truncated responses by finding the last complete
object and closing the array — so partial batches return whatever Claude
finished rather than nothing
- GET action: added try-catch around ResultsJson deserialization so a bad DB
row shows a friendly "re-run" warning instead of a raw error page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude reviews every active catalog item against the shop's own operating costs
and returns a per-item verdict (below-cost / thin-margin / high / ok) with a
suggested price range, cost floor, and assumptions.
- New entity: CatalogPriceCheckReport (JSON blob, archived per company)
- New service: IAiCatalogPriceCheckService / AiCatalogPriceCheckService
batches items 25 at a time to stay within model context limits
- Two new controller actions: GET AiPriceCheck (view report) + POST RunAiPriceCheck
- AiPriceCheck view: summary cards (counts by verdict), color-coded item cards
with Edit Price link, assumptions detail, and loading spinner on submit
- AI Price Check button added to catalog Index header
- Migration AddCatalogPriceCheckReport applied
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- New AI Quick Quote floating button: staff type a verbal description to
get an instant price estimate for phone/walk-in customers; detected
color names are fuzzy-matched against inventory for stock status;
saves draft quote under a Walk-In / Phone customer with one click
- Inline customer change on Quote Details and Job Details: always-visible
native select with inline confirmation banner (no TomSelect dependency);
ChangeCustomer AJAX endpoints on QuotesController and JobsController
- Quote Edit page: customer dropdown is now editable (lock removed)
- Fix AutoMapper missing CatalogCategory -> UpdateCategoryDto mapping
that caused a crash on the catalog category Edit page
- Help docs and AI knowledge base updated for all three features
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>