Compliance Engine
A Strategy Pattern engine that resolves a jobsite's country into the right permit/zoning paperwork — then hands the resulting form back to the frontend using the exact same server-driven-UI schema shape the UI Rendering Engine already produces, so no new rendering code was needed to display it.
What Is the Compliance Engine?
Construction paperwork is not the same everywhere. A Turkish jobsite files Ada, Parsel and Pafta cadastral references, a "Müteahhit SGK Sicil No" contractor registration number, a site manager's certificate number, and TAKS/KAKS floor-area ratios. A US jobsite instead files a Parcel ID, a Contractor License Number, an OSHA Certification Number, and a Zoning Classification. Hardcoding one form per country inside every screen that needs permit paperwork would mean re-deriving the same jurisdiction logic in three different places — and getting it wrong in at least one of them.
The Compliance Engine centralizes that logic behind a single Strategy Pattern: a PermitStrategy interface with one method, getPermitFormSchema(permitType), and one concrete class per jurisdiction — TRPermitStrategy, USPermitStrategy, and a DefaultPermitStrategy fallback that never throws for a country nobody has written a strategy for yet. resolveComplianceStrategy(countryCode) upper-cases the incoming code and switches between them. Adding a new jurisdiction is writing one new strategy class, not touching any of the screens that call this engine.
The engine's output is not raw HTML or a hardcoded React form — it's a UiComponent tree, the same server-driven-UI shape the UI Rendering Engine defines and other engines already produce. Compliance Engine directly reuses UiComponent/UiComponentKind/UiVariant from UiRenderingEngine.ts rather than inventing a parallel schema format, so the same <DynamicScreenRenderer> component that paints Matrix Agent's approval screens paints a Compliance Engine permit form too — no new frontend rendering code exists for this engine at all.
In one sentence
Given a country code and a permit type, this engine picks the right jurisdiction's PermitStrategy and returns a ready-to-render form schema — adding a new country is a new strategy class, not a new screen.
How It Works — Resolve, Build, Render
Every schema request follows the same three-stage path from "which country is this jobsite in" to "a working, jurisdiction-correct form on screen":
| Stage | What happens |
|---|---|
| 1. Resolve the Strategy | resolveComplianceStrategy(countryCode) upper-cases the code and switches on it — TR returns TRPermitStrategy, US returns USPermitStrategy, anything else (including an unrecognized or missing code) returns DefaultPermitStrategy. The function never throws. |
| 2. Build the Schema | The resolved strategy's getPermitFormSchema(permitType) returns a Form component tree — a set of labeled Input fields (some with a regex validation pattern, some numeric with min/max) plus one submit Button. |
| 3. Render with DynamicScreenRenderer | The frontend's fetchComplianceSchema() POSTs to /api/compliance-engine/generate-schema and hands the returned UiComponent straight to <DynamicScreenRenderer> — the exact same component the UI Rendering Engine's own approval screens use. |
Why reuse UiComponent instead of a bespoke Compliance schema type?
Reusing the UI Rendering Engine's UiComponent/UiComponentKind/UiVariant types means the frontend never needs a second renderer, a second style catalog, or a second set of component-kind handling. A jurisdiction-specific permit form and a Matrix Agent approval screen are structurally the same kind of thing — a small tree of inputs, badges, and buttons — so they share one rendering path end to end.
Input — What a Schema Request Carries
To build a permit form, the engine needs only enough to pick the right jurisdiction and label the request:
Country Code
The two-letter code (TR, US, or any other) that decides which PermitStrategy answers the request. Required on every call — the handler rejects a request missing it.
Permit Type
A free-form string identifying what kind of permit or record this is (e.g. building_permit, zoning_land, legal_liens). Required, but today it only labels the form — it does not change which fields a strategy returns.
Jobsite ID (optional)
Accepted for the audit trail and future use. The caller is responsible for deriving countryCode from the jobsite before calling this engine — passing a jobsiteId does not itself change the schema returned.
permitType doesn't change the field set — yet
The three strategies branch only on countryCode. A Turkish building permit and a Turkish zoning record both currently return the exact same Ada/Parsel/SGK/TAKS/KAKS field set — permitType is threaded through as a label and used to name the generated form (permit_form_tr_${permitType}), but it does not itself branch the schema. This is an honest architectural note about the engine's current scope, not a hidden bug.
Output — A Jurisdiction-Specific Form Schema
The response is a UiComponent Form tree whose fields differ per strategy:
| Strategy | Fields returned |
|---|---|
| TRPermitStrategy | Ada, Parsel, Pafta (optional), Müteahhit SGK Sicil No (regex-validated), Şantiye Şefi Belge No (regex-validated), TAKS (numeric, 0–1), KAKS / Emsal (numeric, 0–10), then a submit button. |
| USPermitStrategy | Parcel ID, Contractor License Number (regex-validated), OSHA Certification Number (regex-validated), Zoning Classification, then a submit button. |
| DefaultPermitStrategy | A disabled Permit Type field pre-filled with the request's permitType, a required Site Address field, an optional Notes field, then a submit button — the safe generic fallback for any country without a dedicated strategy. |
Regex validation travels inside the schema itself
Fields like Müteahhit SGK Sicil No or Contractor License Number carry a regex prop directly on the Input component — the same server-driven-UI convention UiRenderingEngine establishes, so the frontend's shared input renderer already knows how to enforce it without any Compliance-specific validation code.
How End Users Actually Use It
Filing a Permit, Zoning Record, or Legal Contract
Inside the Add/Edit modal on Permit Applications, Zoning & Land Use Rules, or Legal Contracts & Liens (all three in Construction → Permits & Zoning Compliance), a "Generate Form" button calls this engine with the jobsite's resolved jurisdiction and renders the result inline. A Turkish jobsite's user sees Ada/Parsel/SGK fields; a US jobsite's user sees Parcel ID/OSHA fields — without either screen containing a single line of country-specific form code.
Switching Jurisdiction Mid-Record
Each of the three screens carries its own jurisdiction picker (a Globe/ChevronDown-styled SearchableCombobox), pre-filled from the selected jobsite via useJobsiteJurisdiction() but overridable per record. Changing it re-calls this engine and swaps the rendered field set to match — useful for a holding company filing paperwork in a country different from where the jobsite itself is registered.
Where the Compliance Engine Fits
This engine doesn't render anything itself — it builds a schema, and the UI Rendering Engine's renderer is what turns that schema into a screen:
| Consumer Screen | What it requests |
|---|---|
| Permit Applications | permitType of {category}_permit — building_permit, environmental_permit, or scaffolding_permit depending on the category picked in the modal. |
| Zoning & Land Use Rules | A fixed permitType of zoning_land. |
| Legal Contracts & Liens | A fixed permitType of legal_liens. |
All three call fetchComplianceSchema() in src/services/complianceEngineService.ts, which reuses (not duplicates) the UiComponent type from uiEngineService.ts — unlike uiEngineService.ts itself, which has to duplicate server-side types since the frontend can't import server code directly. Every one of these three screens' output is painted by the same <DynamicScreenRenderer> component (src/components/app-factory/DynamicScreenRenderer.tsx) that renders the UI Rendering Engine's own approval screens — no new rendering code was written for Compliance Engine's output.
Like every screen the UI Rendering Engine builds, each successful or failed schema generation is logged as a tracked event via the Event & Analytics Engine (engineName: 'ComplianceEngine', eventType: 'compliance-engine.schema.generated' on success, '.failed' on error) — so a jurisdiction that starts failing shows up in the same usage and health history as every other engine call in TotalApp.
A jurisdiction resolver that plugs into an existing renderer, not a new one
This engine's entire job is picking the right PermitStrategy and building its schema. It has no rendering logic, no persistence layer, and no settings screen of its own — it exists specifically so that permit/zoning/legal screens across Construction (and, in principle, any future add-on with country-specific paperwork) can ask "what does this jurisdiction need?" once, in one place.
Frequently Asked Questions
POST /api/compliance-engine/generate-schema) validates that countryCode is present and is a string before building anything — a missing or non-string value returns a 400 error rather than falling through to a default strategy.resolveComplianceStrategy() falls through to DefaultPermitStrategy, which returns a generic permit-type/address/notes form and never throws. These countries are still selectable in every jurisdiction picker in the UI, specifically so a holding company's jobsites in those countries are represented even before a real strategy exists for them.jobsiteId is accepted and passed through for the audit trail and future use, but the schema is built purely from countryCode and permitType. The caller is responsible for deriving countryCode from the jobsite (via useJobsiteJurisdiction()) before calling this engine.PermitRecord.formData, ZoningRegulationRecord.formData, or LegalContractRecord.formData depending on which screen made the request.PermitStrategy pattern and the UiComponent output shape would work equally well for any future add-on that needs country-specific paperwork.PermitStrategy (e.g. DEPermitStrategy) and adding one new case to resolveComplianceStrategy()'s switch statement. No screen that calls this engine needs to change — they already pass whatever countryCode the jobsite carries.