loc berlin, detz utc+02:00since 2010mode freelance · lead-developerrate on requestcontact dima@makaruk.dev
← back to work
#full-stack #ai #document-workflows #real-time

SDB-Scanner — AI-Driven Safety Data Sheet Management Platform

End-to-end ownership of an AI-driven platform for authoring, validating, and tracking chemical Safety Data Sheets — from PDF ingestion pipelines through to a Nuxt SPA.

Client
Occupational-safety SaaS (client confidential)
Role
Lead Developer
Dates
10.2022 – 02.2024, 03.2025 – 04.2026
LaravelPostgreSQLRedisSoketiDockerNuxtVueTypeScriptPiniaNuxtUITailwind CSSLaravel SanctumPusherPlaywright

Context

Chemical Safety Data Sheets (German: Sicherheitsdatenblätter, SDB) are the legal documents that travel with every hazardous substance sold in the EU. They follow a strict 16-chapter structure mandated by REACH and CLP, and each sheet runs to a dozen pages of dense, jurisdiction-specific data. For the safety officers whose job is to keep them current, the work is mostly a copy-and-paste cycle between supplier PDFs and an internal compliance database.

The client wanted to compress that cycle. The vision was a platform that could ingest a supplier's SDS PDF, extract its structured data automatically, and present it to a safety officer in a form they could review chapter-by-chapter — with the original document side-by-side, source cells highlighted, and a clear audit trail when something was changed. It also had to work on mobile, so that a warehouse manager could open and validate a sheet on the shop floor without going back to a desk.

I joined as Lead Developer with end-to-end responsibility for the technical platform: backend, frontend, infrastructure, and the integration surface.

Approach

The product split cleanly into two services.

SDSApi — a Laravel backend exposing a REST/JSON API. Its core job is taking a PDF and turning it into a normalized, chapter-keyed data structure, with provenance pointers back to the source page coordinates. Around that core, SDSApi handles auth, persistence, validation, and pushing real-time updates out as ingestion progresses.

SDSApp — a Nuxt SPA on NuxtUI and Tailwind CSS. It's a thick client: authenticated against SDSApi via token-based Laravel Sanctum, with all authoring and review happening client-side. The form layer is config-driven, so adding a new chapter or a new field is a JSON change rather than a Vue component change.

The two services deploy independently behind a shared domain. Soketi runs alongside SDSApi for WebSocket fan-out; the SPA subscribes via Pusher's protocol over private channels.

Implementation highlights

A queued chain-of-responsibility for PDF ingestion. Extracting structured data from an safetydatasheet PDF isn't one job — it's a sequence: page split, OCR (for scanned pages), chapter segmentation, cell detection, field mapping, validation, persistence. Each stage is an idempotent processor in a chain-of-responsibility pipeline, and each runs as its own queued Laravel job. A failure in OCR retries OCR without re-doing the chapter split that already succeeded. A new processor can be inserted in a single line of pipeline config. Provenance metadata — page number, bounding box, confidence score — flows alongside the payload, so by the time data lands in PostgreSQL every field knows where in the source PDF it came from.

An integrated PDF viewer with bounding-box overlays. On the frontend, the SDS form sits next to a PDF viewer that renders the source document at its original layout. When the user clicks a field in the form, the viewer scrolls to the right page and highlights the bounding box that populated that field. When the user edits the field, the link is preserved. This single feature is what makes the platform credible to safety officers — it lets them verify the AI's output rather than trust it.

Config-driven multi-chapter safetydatasheet form with Zod validation. Each of the 16 SDS chapters has its own field set and validation rules, and those rules vary by jurisdiction and substance type. Hard-coding them into Vue components would have meant a rewrite every time regulations shifted. Instead, the form layer reads its schema at runtime — field types, options, conditional visibility, and validation are declared in TypeScript with Zod, and the renderer is generic. Adding a chapter or rule is an edit, not a release.

Real-time progress over private websocket channels. A long ingestion run (large PDF, lots of OCR) can take a minute or more. The frontend subscribes to a per-document private channel and renders progress as each pipeline stage completes. The same channel is reused for collaborative edits — when two officers open the same sheet, they see each other's changes live.

Test strategy. PHPUnit integration tests run against a real PostgreSQL database in CI rather than mocking the persistence layer — this caught several issues with Doctrine-style queries and PostgreSQL-specific JSON behavior that mocks would have hidden. Playwright covers the critical end-to-end flows on the SPA: login, ingest, review, save, export.

Outcome

The platform is currently in production.

The architectural choices that paid off most:

  • Increased the speed of ingestion by 75% by using a queueing system to parallelize PDF splitting and OCR.
  • The queued chain-of-responsibility absorbed three new ingestion stages over 24 months without rework of existing stages.
  • The config-driven form survived a substantial regulatory change with a single TypeScript edit and zero component changes.

Stack

Backend — Laravel , PostgreSQL, Redis, Soketi, Docker. Frontend — Nuxt , Vue , TypeScript, Pinia, NuxtUI , Tailwind CSS . Auth & real-time — Laravel Sanctum, Pusher protocol over private channels. Integrations — sdbhub.de SDS data API, external OCR. Testing — PHPUnit (integration against PostgreSQL), Playwright (e2e). Internationalization — DE / EN.

need an orocommerce / b2b e-commerce developer for something similar? i take on one new engagement at a time.