← All case studies
AI automation · Lighting supply

From a PDF lighting schedule to a quote you can defend.

Light Box, an AI quoting assistant for a lighting supplier: it reads luminaire schedules from tender PDFs, proposes matching fittings from a catalogue of 35,000+ products with a reason for each choice and turns the reviewed result into an exportable quote.

From a PDF lighting schedule to a quote you can defend.
80%faster quote preparation
35k+products in one catalogue
6steps from PDF schedule to exported quote
20real project schedules in the benchmark
776automated backend tests
0matches decided by an LLM
Industry
Lighting supply / tender quoting
Engagement
Full-stack AI product build (backend + web app)
Market
Ireland

Every lighting tender comes with a luminaire schedule: a PDF listing each fitting the building needs, often by a competitor's part code. A supplier's estimators read it row by row, find the closest product in their own range and have to defend that choice in the quote. We built Light Box to take over the reading and the searching, so the team spends its time on decisions.

Light Box reads the schedule with AI and fills blanks for control, colour temperature and emergency lighting from project-wide settings. A rule-based engine that explains each decision checks every row against a unified catalogue of two European luminaire manufacturers, and the reviewed result becomes a quote exported to Excel, CSV or JSON. Only clear-cut rows are matched automatically; everything else goes to a person with the reasons attached.

What we were up against

  • No two schedules look alike. Schedules arrive as a neat three-page table, as one spec block per fitting spread over eleven pages, or buried on ten pages of a 141-page electrical specification. They also carry their own errors and gaps: part codes split across line breaks, rows that contradict their own description, 'TBC' and 'by others' entries.
  • Many rows ask for someone else's product. Across the real projects we tested on, a third of all rows named a competitor's fitting. Where the team offers an equivalent from its own range, it has to be able to justify it, so looking up exact codes covers only part of the job.
  • Product data in many shapes. The two manufacturers structure their ranges differently, from fixed articles to option tables and configurable products. Each manufacturer, and each schedule, also names the same things differently.
  • A perfect score can mean nothing. A product with missing data contradicts nothing, so a naive engine rates it a perfect match. Over-specifying is just as costly: an emergency module or dimmable driver the schedule never asked for changes both the spec and the price.

How we solved it

  • AI extraction into a fixed schema. One structured AI call reads the schedule PDF and answers in a fixed schema generated from the matching engine's own data model: 28 fields per row, from category and IP rating to colour temperature, control and emergency. Reading and matching share one vocabulary, and the AI quotes the source text behind each value it fills, kept with the raw extraction.
  • People review, with every value labelled. Estimators check the extracted rows next to the original PDF, edit any cell and add, duplicate or delete rows. Each value is tagged as extracted, filled from project settings, entered manually or still missing, and project settings only fill gaps, never overwrite the schedule.
  • A matching engine that explains every decision. Each row passes through nine ordered checks, from exact part code and range name to category, evidence and adjudication, and ends in a named outcome with a full decision trace. Scores are plain formulas over catalogue data, not an LLM's opinion. Unless an exact part code pins down the product, a match is auto-approved only when it fits the row, the catalogue data allows a real comparison and it clearly beats the best alternative from another range; near-ties become short questions instead of guesses.
  • Two manufacturers, one catalogue. We ingested both manufacturers' ranges into a single catalogue of more than 35,000 products, described in one set of standard fields and mapped to the client's own 23 product categories. A shared vocabulary of 163 aliases translates each manufacturer's wording, and the source of every value is recorded.

Measurable impact

  • 80% faster quote preparation. Estimators no longer retype schedules or search two catalogues by hand. They start from extracted rows and ranked proposals and focus their time on the lines the system flags.
  • Proposals the team can defend. Every proposed fitting carries the checks it passed and the reason it beat the alternatives, so a quote can be explained to a consultant line by line.
  • No silent over-specification. Where a schedule is silent, the engine leans to the plain base variant rather than the most featured one, keeping quotes aligned with the spec and competitive on price.
  • Tuned on real projects. The engine is tuned against a benchmark of 20 real project schedules with 588 rows. Over the tuning rounds, together with adding the second manufacturer's catalogue, auto-approved equivalent products on that benchmark grew from 9 to 78.

Under the hood

  • Extraction Native PDF input to Anthropic Claude with schema-constrained structured output, generated from the engine's Pydantic row model and closed enums from a shared field registry, with a guard that fails fast when the registry holds a value the prompt does not explain. Background workers validate the file before the paid call, retry transient failures with backoff and log tokens and estimated cost for every run.
  • Matching A domain-blind policy ladder of nine ordered policies: the first terminal decision wins and every evaluation is logged. Fit, coverage and comparability are computed in code, together with a dominance margin over the best rival family, and part-code identity is graded by confidence. The rule set is versioned and stamped on every proposal.
  • Data PostgreSQL 16 with trigram search. Manufacturer-specific adapters normalise raw records into a 31-field registry and the client's 23 categories, keeping per-value provenance and the original record in JSONB.
  • Backend Layered FastAPI service on Python 3.13 with command and query handlers behind a Unit of Work, async SQLAlchemy over two PostgreSQL databases (app and catalogue) and 20 Alembic migrations. Celery on Redis runs extraction and matching as background jobs. JWT auth with rotating refresh tokens and argon2 hashing; quotes stream out as XLSX, CSV or JSON.
  • Frontend Next.js 16 with React 19, Tailwind 4 and shadcn. Server Components prefetch queries in parallel and hydrate TanStack Query, and every API call is typed from the backend's OpenAPI. A backend-for-frontend proxy keeps tokens in httpOnly cookies, and inline cell edits are optimistic with rollback.
  • Delivery 776 automated backend tests run in CI with ruff, mypy and pytest against a real PostgreSQL; the frontend pipeline runs lint, typecheck and a production build. Docker containers on AWS, deployed through GitHub Actions, with structured JSON logs, request IDs carried into background jobs, Sentry, OpenTelemetry and Prometheus.

What we used

  • AI
  • Anthropic Claude
  • Python
  • FastAPI
  • Next.js
  • React
  • TypeScript
  • PostgreSQL
  • SQLAlchemy
  • Celery
  • Redis
  • TanStack Query
  • Tailwind CSS
  • OpenAPI
  • Docker
  • GitHub Actions
  • AWS
  • Sentry
Your project

Have a workflow worth improving?

Show us what is happening today. We will help map what should change and what is worth building.

Discuss your project