{
  "markdown": "# VE Estimator\n\nPremier Lighting's internal **value-engineering (VE) substitution finder** for\nestimating. An estimator uploads a bid sheet or fixture schedule; the app\nsuggests Premier catalog substitutions for each line, learns from accepted\nswaps recorded at export, and produces a corporate-template takeoff draft.\n\n**Live app:** deployed on Vercel from `main`. The exported workbook is a\ntakeoff draft, not a quote; pricing columns are intentionally blank.\n\n## How it works\n\n1. **Upload** a bid sheet (CSV/XLSX) or a fixture schedule as a Word document\n   (.docx), a PDF, or an image (PNG/JPEG/WebP/GIF — schedules arrive as phone\n   photos and screenshots too).\n2. The app **parses** it into line items (mark, quantity, manufacturer,\n   catalog number). Word files, PDFs and images are read by Claude; a long\n   schedule is read in several page passes and the rows are joined in document\n   order. A Word schedule is usually pasted-in screenshots of the drawing set's\n   schedule sheets, so the browser reads the .docx, recompresses those page\n   images, and posts them as pages — which is also how a Word file stays under\n   Vercel's 4.5 MB request-body limit (`app/prepareUpload.ts`).\n3. The **recommendation engine** scores each line against Premier's Airtable\n   catalogs (Premier Items, 3rd Party Domestic, Fans) and **History** — past\n   estimator decisions — and returns up to three ranked substitutions per\n   line, pre-checking one only when the auto-select gate is confident.\n4. Lines the engine can't categorize can go through a **batched identify\n   pass** — one Claude call per ~18 lines, never automatic. The estimator picks\n   which lines it covers: lines with no manufacturer and no part number (a `TBD`\n   row whose whole spec is `9\" UNDER CABINET`) have nothing to look up and start\n   unchecked, because that call is theirs to spend.\n5. Lines the sheet alone can't identify can go through **per-line\n   identification**: Claude reads a pasted spec URL, searches the web, or\n   reads an uploaded cut sheet (PDF or image), then the engine re-runs. The web\n   lookup searches the **base item number** — `4430802-112` is looked up as\n   `4430802`, because the trailing code is a finish the estimator configures and\n   including it is what makes the search return nothing\n   (`lib/identify/catalogNumber.ts`).\n6. The estimator reviews/overrides selections and **exports** the workbook.\n7. When the export opts into recording (`recordToHistory`) and\n   `HISTORY_WRITEBACK` allows it, accepted substitutions are **written back\n   to History** — the learning loop that makes the next bid's suggestions\n   better.\n\n## Getting started\n\n```bash\nnpm install\nnpm run dev        # http://localhost:3000\n```\n\n### Environment variables\n\n| Variable | Purpose |\n|---|---|\n| `AIRTABLE_PAT` | Personal access token for the Premier estimating Airtable base (required for live data) |\n| `AIRTABLE_BASE_ID` | Overrides the default base ID (optional; defaults to the production base) |\n| `ANTHROPIC_API_KEY` | Claude API key for schedule/cut-sheet reading (Word, PDF or image) and per-line identification |\n| `ANTHROPIC_WORKSPACE_ID` | Required **only** for an identity-linked API key, which cannot make any request without naming its workspace (`400 anthropic-workspace-id is required…`). Leave unset for a workspace-scoped key |\n| `IDENTIFY_MODEL` | Overrides the Claude model used for identification (optional) |\n| `HISTORY_WRITEBACK` | `live` / `dry_run` / `off` kill switch. When unset: production defaults to `live`; previews and local dev default to `dry_run`, so non-production exports never write to History |\n\n### Commands\n\n```bash\nnpm run dev              # dev server\nnpm run build            # production build\nnpm run lint             # eslint\nnpm test                 # vitest suites (parse, tuning, parity, writeback, identify, export, eval guard)\nnpm run eval             # accuracy eval: replays 1,000+ labeled History outcomes through the engine\nnpm run eval:update      # accept new eval results as the baseline (do this deliberately)\nnpm run eval:fetch       # refresh the frozen Airtable snapshot the eval runs against\nnpm run build:series-map # regenerate lib/engine/series-categories.ts from History\n```\n\n## Engine changes are measured, not eyeballed\n\nCI (`.github/workflows/ci.yml`) runs typecheck, lint, and the full vitest\nsuite — including the **eval ratchet**, which fails the build if an engine\nchange regresses top-1 accuracy, junk rate, or auto-select-wrong rate against\nthe committed baseline. If you change anything in `lib/engine/`, run\n`npm run eval` and look at the per-case flip diff before you push. See\n`docs/EVAL-HARNESS.md`.\n\n## Repo layout\n\n| Path | Role |\n|---|---|\n| `app/page.tsx` | Client UI: upload, review, identify, export |\n| `app/api/{upload,recommendations,identify,export}/route.ts` | Thin API routes; logic lives in `lib/**` |\n| `lib/parse/` | CSV/XLSX parsing and request coercion |\n| `lib/identify/` | Claude-powered identification and schedule extraction (Word/PDF/image, page-chunked) |\n| `lib/engine/` | Matching, ranking, recommendation orchestration (pure TS — no Next.js/React imports) |\n| `lib/airtable/` | Schema/field IDs, fetch, in-memory cache, create-only History write-back |\n| `lib/export/` | Corporate-template workbook builder |\n| `lib/eval/`, `scripts/eval/` | Accuracy eval harness |\n| `__tests__/` | Vitest suites + frozen eval snapshot/baseline |\n| `docs/` | Hand-written phase primers and eval-harness reference |\n| `openwiki/` | Generated wiki (refreshed by a scheduled workflow — don't hand-edit) |\n\n## Further reading\n\n- `openwiki/quickstart.md` — generated wiki entry point (architecture, engine, data, ops)\n- `docs/PHASE4-PRIMER.md` — current phase: closing the spec-identification gap\n- `docs/PHASE3-PRIMER.md` — architecture map and conventions from the Phase 3 handoff\n- `docs/EVAL-HARNESS.md` — eval metrics, workflow, and how to read output\n\n> ⚠️ This is a **public** repository. Never commit customer bid workbooks,\n> pricing data, or Airtable exports. Test fixtures must use frozen,\n> already-committed snapshots or synthetic data.\n",
  "bytes": 6123,
  "sha": "9b9e2491bd53f163a205c498ac1f4cbb83f79136f8cacb29856c9d3bccdbf788",
  "repo_slug": "jesseww07/estimating_optimization",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/okf_jesseww07_estimating_optimization_openwi_50082d54/readme"
}