TWX Developer Documentation
Welcome to the TWX (Digital Temporary Works Inspection) developer documentation. This guide covers the codebase architecture, tech stack, and API surface so you can understand and extend the platform.
What is TWX?
TWX is a web application for managing digital temporary works inspections integrated with BIM (Building Information Modeling) data through self-hosted IFC models. The core purpose is to track the reuse of temporary works materials (props, scaffolding, formwork, etc.) from one construction project to another, capturing a full inspection audit trail at every stage.
Key Capabilities
- 3D visualisation of BIM models using That Open Engine (WebGL)
- Interactive inspection recording directly on 3D elements
- Explode, section-plane, and isolate tools for deep model inspection
- Material lifecycle tracking across multiple construction projects
- QR code-based asset identification and linking
- Dual-approval transfer workflow for element reuse
- Cross-organisation Marketplace — browse and request assets from any project
- DCI/FDCI circularity analytics per project lifecycle
- Role-based access (project admin · editor · viewer)
- Visitor analytics and geography tracking (management hub)
User Guide
This guide walks through the core workflows in TWX — from signing in and creating a project, through recording inspections in the 3D viewer, to managing your asset inventory, transferring materials between projects, and sourcing assets on the cross-organisation Marketplace.
1 · Signing In
Go to /sign-in and enter your email. TWX uses a magic-link / password flow powered by Clerk. Once authenticated you land on the Management Hub at /manage.
2 · Projects
The Projects page in the Management Hub lists every project you are a member of. Each card shows the project name, status, location, and a summary of inspection progress. From the card you can:
- Click Open Viewer to launch the 3D BIM viewer for that project
- Click the project name to open its Project Hub — a dedicated page with full details, member management, activity feed, and inspection summary
- Click + New Project (top-right) to create a fresh project
Each project has a status (Active · On Hold · Completed · Archived) and optional metadata including project number, company, location, and description. Only project admins and platform admins can edit project details or manage membership.
3 · The 3D Viewer
The viewer loads your project's IFC model in WebGL. The sidebar on the left gives you navigation links; the toolbar across the top contains viewer controls.
| Control | What it does |
|---|---|
| Left-click element | Select it — opens the Inspect panel on the right |
| Left-click + drag | Orbit the model |
| Right-click + drag / middle-drag | Pan the camera |
| Scroll wheel | Zoom in / out |
| Fit to view | Reset camera to show the full model |
| Grid toggle | Show / hide the ground reference grid |
| Colour coding | Colour elements by inspection status (passed / issue / pending / uninspected) |
| Measure | Click two points on the model to measure the straight-line distance |
| Hide selected | Remove selected elements from view without deleting them |
| Show all hidden | Restore all previously hidden elements |
| Explode toggle | Separate components outward so individual elements are easier to select |
| Section plane | Slice through the model horizontally — drag the right-side slider to move the cut up or down |
| Isolate | Hide every element except the currently selected one; exit via the orange badge or the button |
The right panel has three tabs: Inspect (form data for the selected element), Table (spreadsheet view of all elements and inspections in the project), and Circularity (DCI / FDCI metrics). Switch between tabs using the toolbar icons.
4 · Recording the Temporary Works Lifecycle
Click an element in the 3D model to open the Inspect tab. Supporting fields may still be grouped into form tabs such as Basic Info, Technical, Quality, and Stakeholders, but TWX no longer treats the form as one mutable inspection record. Approval evidence follows the DB → DCG → DCK → PTL lifecycle, with PTR and the post-removal assessment tracked separately.
| Form | Scope | Fields collected |
|---|---|---|
| DB · Design Brief (TW1) | Usually package-wide | Package number; brief / scope; risk category; design brief reference; Temporary Works Coordinator; Temporary Works Designer |
| DCG · Design Change (TW2) | Usually package-wide | Package number; member mark / IFC Mark; reason for change; change description; revised drawing reference and revision; TWC review / decision; independent check review |
| DCK · Design Certification (TW3) | Usually package-wide | Package number; controlled drawing / document and revision; certification statement; design certifier; independent checking engineer; check certificate / remarks; authorised signatory and role |
| PTL · Permit to Load (TW4) | Element-level | Package number; permit-to-load date; loading criteria / restrictions; installed location; installation / inspection reference; responsible site person; TWC authorising PTL; authorised signatory |
| PTR · Permit to Remove (TW4) | Element-level | Package number; permit-to-remove date; removal method statement; removal location; responsible site person; TWC authorising PTR; authorised signatory and role |
| Post-removal assessment | Recovered component | Condition grade (A–D); end-of-life route; observed defects / usage history; remedial actions / restrictions; photo / evidence references |
Every form also records shared metadata: sign-off status, form date, recorded by, source form number, source form revision, source-linkage confidence, review rationale, and notes. DB, DCG, and DCK can be recorded once for a package and inherited by its members; PTL and PTR identify an individual element. Each stage can be signed off independently — recording or signing off a later stage never requires earlier stages to be signed off first. An element is lifecycle-complete only when all four required stages (DB, DCG, DCK, and PTL) are signed off; PTR is separately optional and independent. Historic generic Design Check records remain readable and are shown asLegacy DCG.
Packages and membership
Packages group the elements that share DB/DC evidence. Open the Tabletab, select one or more element rows, and choose Create TW Package; enter a package name and save. Existing packages appear as expandable group rows. Use the package filter to select a package or show only ungrouped elements, and use the package header's Edit action to review or change membership before evidence has been recorded.
- An element can belong to more than one package when the work scope requires it.
- After a package has lifecycle evidence, its membership is locked. Create a new package for a revised member list; packages with evidence are archived rather than deleted.
- For an element in multiple packages, do not infer one package from a flat element view. TWX keeps package evidence separate and rejects an ambiguous write unless the intended
packageIdis supplied. - Un-packaged elements can still have direct evidence. The viewer shows that evidence, while package-scoped evidence is shown in the relevant package context.
Timeline, autosave, and corrections
The inspection panel's Timeline shows the stage history for the selected element, including package-inherited DB/DC evidence and later PTL/PTR decisions. Browser edits are still autosaved, and the status indicator confirms the save state, but autosave appends a new event rather than overwriting the prior approval. A correction supersedes the current event for the same stage and subject while keeping the earlier event in the audit history. Clearing a PTR creates a recorded blank correction; it does not erase the previous approval.
5 · Asset Inventory
TWX tracks physical temporary-works assets independently from BIM elements. An asset is a real-world item (a prop, a scaffold bay, a formwork panel) with a unique asset number. A BIM element can be linked to an asset to attach that element's 3D data to the physical item's lifecycle record.
There are two ways to connect an element to an asset from inside the viewer:
- Register New — creates a brand-new asset and immediately links it to the selected element. Use this the first time a physical item enters your inventory.
- Link Existing — opens a search to find an asset already in the system (by asset number, description, or serial number) and links it to the selected element. Use this when the asset was registered in a previous project.
Once linked, the asset status banner in the Inspect panel turns orange and shows the asset number, current status, and a QR code link. You can view the full asset inventory across all projects from the Assets page in the Management Hub, where you can filter by status, project, type, and more.
6 · Transferring Assets Between Projects
When a physical asset moves from one project to another, use the Transfer workflow. Click Transfer… in the linked-asset banner in the Inspect panel to begin. The transfer requires dual approval: both the sending project admin and the receiving project admin must confirm before the asset's project assignment changes. This creates an immutable audit record with timestamps and approver names.
7 · Managing Your Team
Open a project's Project Hub (click the project name on the Projects page) then select the Members tab. From there you can:
- See all current members, their roles, and when they joined
- Invite a new member by email address — they receive an invitation email with a direct join link
- Change a member's role at any time (Viewer → Editor → Project Admin)
- Remove a member from the project
- See and cancel pending invitations that have not yet been accepted
| Role | View & inspect | Record inspections | Register / link assets | Transfer assets | Manage members | Edit project settings |
|---|---|---|---|---|---|---|
| Viewer | ✓ | — | — | — | — | — |
| Editor | ✓ | ✓ | ✓ | — | — | — |
| Project Admin | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
8 · Circularity Metrics (DCI / FDCI)
The Circularity tab in the viewer shows the Design for Circularity Index (DCI) and Full Design for Circularity Index (FDCI) for each element in the project, calculated from the inspection and material passport data you have recorded. The Management Hub Dashboard also shows portfolio-level circularity scores across all your projects.
The weighting formulas for these indices are configurable by a platform administrator on the Admin page — contact your admin if the default weights do not match your organisation's circularity standards.
9 · Your Account & Preferences
Click your avatar in the bottom-left of any page to open Account Settings. From there you can update your display name, company, job title, timezone, switch between dark and light theme, and sign out. Theme preference is saved per-account and syncs instantly across tabs.
Marketplace
The Marketplace is a cross-organisation exchange where any authenticated user can browse registered temporary-works assets held across all projects on the platform, identify items that match their project's needs, and submit a sourcing request to the asset's owner.
Access the Marketplace from the left sidebar of the Management Hub (the shopping bag icon) or directly at /manage/marketplace.
What you can see
Every registered asset is visible to all authenticated users regardless of which project they belong to. Each asset card shows:
- Asset number and IFC element type (e.g. IfcBeam, IfcSlab)
- Description and manufacturer
- Current status — Active · In Storage · In Transit · Retired
- Current project holding the asset
- Purchase value and forecasted availability date
- Age (time since first registration) and rotation count (number of project transfers completed)
Filtering & search
| Filter | How it works |
|---|---|
| Search | Full-text search across asset number, description, manufacturer, type, category, and serial number |
| IFC type | Filter by IFC element class (derived from the linked BIM element) |
| Status | Active · In Storage · In Transit · Retired |
| Value range | Min / max purchase value (advanced filters panel) |
| Availability window | Filter by forecasted availability from / to date (advanced filters panel) |
Submitting a request
Click any asset card to open the detail view. The detail panel shows the full asset profile including all linked inspection and material-passport data. Click Submit Request to log your interest. A request record is created against your user account and the asset is marked as having a pending enquiry.
Architecture
TWX uses a hybrid architecture: a custom Express.js server wraps Next.js (for the viewer + API), while a separate Vite/React SPA handles the management console. Both are served from the same host via path-based routing.
┌─────────────────────────────────────────────────────────┐
│ Client Browser │
│ ┌──────────────────┐ ┌───────────────────────────┐ │
│ │ React SPA │ │ That Open Engine (WebGL) │ │
│ │ /manage/* │ │ IFC / BIM 3D Viewer │ │
│ └──────────────────┘ └───────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│ HTTPS
▼
┌─────────────────────────────────────────────────────────┐
│ Custom Express Server + Next.js │
│ ├── Authentication middleware (server-side) │
│ ├── /api/projects/* Project management │
│ ├── /api/inspections/* Inspection CRUD │
│ ├── /api/elements/* Asset inventory │
│ └── /api/circularity/* DCI/FDCI engine │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ PostgreSQL Database │
│ Structured relational schema covering │
│ projects, users, inspections, and assets │
└─────────────────────────────────────────────────────────┘Authentication
Authentication is handled server-side by Clerk via an Express middleware layer. All API routes require a valid session; a small set of public pages (the landing page, sign-in, and this docs page) are explicitly exempted. Role-based access control distinguishes regular users, project-level roles (admin / editor / viewer), and global platform administrators.
Tech Stack
TWX is a TypeScript-first pnpm monorepo. The viewer and management app share a single database schema and run as separate services behind Replit's path-based proxy.
Viewer App — artifacts/twx
A custom-server Next.js app. Express owns authentication and rate-limiting; Next.js handles pages and API routes.
| Technology | Version | Purpose |
|---|---|---|
| Next.js (Pages Router) | 15 | SSR framework, file-based routing, API routes |
| React | 19 | UI rendering |
| TypeScript | 5 | Type safety across the full stack |
| Express | 5 | Custom HTTP server wrapping Next.js; owns auth middleware, rate-limiting, and object-storage routes |
| @thatopen/components | 2.x | High-level BIM viewer: IFC loader, fragment manager, raycasting, measurement, section planes, explode tool |
| @thatopen/fragments | 2.x | Geometry representation and streaming for large IFC files |
| Three.js | 0.160+ | Underlying WebGL 3D engine used by That Open Engine |
| Drizzle ORM | 0.39+ | Type-safe SQL query builder and schema definition |
| PostgreSQL | 16 | Relational database — projects, inspections, elements, analytics |
| @clerk/express | 1.x | Server-side authentication; attaches req.auth via Express middleware |
| styled-jsx | — | Scoped per-component CSS (bundled with Next.js) |
| qrcode | 1.x | Server-side QR code generation for asset identification |
| express-rate-limit | 7.x | Rate limiting on sensitive mutation endpoints, backed by PostgreSQL |
Management App — artifacts/manage
A Vite-bundled React SPA. No server-side rendering — all data is fetched from the viewer app's API.
| Technology | Version | Purpose |
|---|---|---|
| React | 18 | UI rendering |
| Vite | 5 | Dev server and production bundler |
| TypeScript | 5 | Type safety |
| Wouter | 3.x | Lightweight client-side routing (no React Router overhead) |
| Tailwind CSS | 3 | Utility-first styling |
| shadcn/ui | — | Accessible component library built on Radix UI primitives |
| TanStack Query | 5 | Server-state management, caching, and background refetching |
| Recharts | 2.x | Composable charts for visitor analytics and project metrics |
| react-simple-maps | 3.x | World map for geography analytics |
| @clerk/clerk-react | 5.x | Client-side auth hooks and sign-in/out UI |
| Lucide React | — | Icon library |
Infrastructure & Tooling
| Technology | Purpose |
|---|---|
| pnpm workspaces | Monorepo package management — shared dependencies hoisted to root |
| Replit | Cloud hosting, path-based proxy routing, object storage (IFC files), secrets management |
| Clerk | Auth platform — user management, session tokens, organisation invitations |
| PostgreSQL (Replit-managed) | Primary database, shared across both apps via the same connection string |
| drizzle-kit | Schema push and migration tooling |
Reference Documentation
That Open Company Docs
Official reference for @thatopen/components and @thatopen/fragments — component API, tutorials, and IFC loading guides.
Drizzle ORM Docs
Schema definition, query builder reference, and migration workflow for the PostgreSQL layer.
orm.drizzle.team ↗Clerk Docs
Authentication setup, session management, and SDK reference for both @clerk/express and @clerk/clerk-react.
Next.js Docs
Pages Router, custom server configuration, API routes, and deployment reference.
nextjs.org/docs ↗Getting Started
The application is a pnpm monorepo. All commands run from the workspace root.
# Install all workspace dependencies pnpm install # Start the viewer + API server pnpm --filter @workspace/twx run dev # Start the management SPA (separate terminal) pnpm --filter @workspace/manage run dev
Environment Variables
Copy .env.example to .env and fill in the values. The following categories of secrets are required:
| Category | Required | Notes |
|---|---|---|
| Clerk API keys | Yes | Publishable + secret keys from your Clerk dashboard |
| Database URL | Yes | PostgreSQL connection string |
| Session secret | Yes | Long random string — generate with openssl rand -hex 32 |
| Object storage | Yes | Bucket identifier for IFC file storage |
Database migrations
# Push schema changes to Postgres (run from the viewer app directory) npx drizzle-kit push --force
API Reference
All API routes live under /api/. Successful routes return a{ success: true, data: … } envelope; failures return{ success: false, error: "…" } and may include route-specific validation details. Browser routes require a Clerk session and enforce project roles server-side. The machine-to-machine routes under /api/agent/ use external-agent API keys instead; see the integration guide below.
Projects
| Method | Path | Description |
|---|---|---|
| GET | /api/projects | List projects the user is a member of |
| POST | /api/projects | Create a new project |
| GET | /api/projects?id=:id | Get single project |
| GET | /api/projects/:id/summary | Project stats + caller's role |
| GET | /api/projects/:id/members | List members and pending invitations |
| POST | /api/invitations | Invite a user by email |
| POST | /api/invitations/:id/accept | Accept an invitation |
| GET / POST / DELETE | /api/projects/:id/api-keys | Project-admin management of external-agent keys; a plaintext key is returned only on create or rotation |
Inspections
| Method | Path | Description |
|---|---|---|
| GET | /api/inspection-events?projectId=:id | List immutable lifecycle evidence and calculated lifecycle state |
| POST | /api/inspection-events | Append immutable lifecycle evidence; a target with one package is linked automatically, while multiple packages require an explicit packageId |
| GET | /api/inspection-events/element/:elementId | Get the current event snapshot for a specific element and optional stage; pass packageId when reading a package-specific element |
| GET / POST / PATCH / DELETE | /api/tw-packages | Browser-session package management; membership is read-only after lifecycle evidence exists |
Assets & Transfers
| Method | Path | Description |
|---|---|---|
| GET | /api/elements | Asset inventory |
| POST | /api/elements | Register a new asset |
| POST | /api/elements/transfer | Initiate dual-approval transfer |
| GET | /api/elements/check-linking/:elementId | Check if a BIM element is linked to an asset |
Circularity
| Method | Path | Description |
|---|---|---|
| GET | /api/circularity/:projectId | DCI / FDCI scores for all elements in a project |
| GET | /api/tw-packages | List TW control packages associated with a project |
Marketplace
| Method | Path | Description |
|---|---|---|
| GET | /api/marketplace | List all registered assets cross-platform with enriched metadata (age, rotation count, pending requests). Supports query params: q, type, category, status, project, minValue, maxValue, availableFrom, availableTo |
| POST | /api/marketplace/request | Submit a sourcing request for an asset |
Analytics
| Method | Path | Description |
|---|---|---|
| POST | /api/analytics/pageview | Record a page-view beacon (unauthenticated, rate-limited) |
| POST | /api/analytics/session-end | Record session duration on navigation away |
| GET | /api/stats | Platform-wide visit counts and geography data (admin only) |
Admin
Administrative API routes exist under /api/admin/* and are restricted to global platform administrators. These routes are not documented publicly — refer to the source code for the full surface.
External Agent Inspection API
Project administrators create external-agent keys from the project hub. A key is restricted to its permitted project grants, is stored as a hash, can expire, and can be rotated or revoked at any time. Its full value is visible only once. Send it withAuthorization: Bearer <key> or X-API-Key: <key>.
Key lifecycle, scopes, and response rules
Create, rotate, inspect, or revoke a key in Management → Project hub → Agent API keys. Project administrators choose the key name and an expiry between 1 and 3,650 days. Rotating a key revokes the previous credential in the same transaction. The key management endpoint is intentionally a browser-session, project-admin API; external agents cannot mint or change their own credentials.
| Grant | Permits |
|---|---|
read | Project discovery, model status, and indexed element search |
inspection:write | Immutable events, package reads and changes, lifecycle imports, and legacy inspection compatibility writes |
Valid requests return { success: true, data: … }. Validation failures return400; a missing, expired, revoked, or malformed key returns 401; an ungranted project is intentionally returned as 404; and an unsafe or unavailable write target returns409. Agent traffic is limited to 240 requests per 15 minutes per credentialand returns standard rate-limit headers. Successful writes are attributed to the key and logged in the project activity trail.
Discovery flow
| Method | Path | Grant | Purpose |
|---|---|---|---|
| GET | /api/agent/projects | read | List only projects granted to the key |
| GET | /api/agent/projects/:projectId/models | read | List active IFC model versions and index status |
| POST | /api/agent/projects/:projectId/elements | read | Search indexed elements in active versions only |
| GET | /api/agent/projects/:projectId/packages | inspection:write | List package IDs and canonical element membership before an inspection write |
| POST | /api/agent/projects/:projectId/elements/package-membership | inspection:write | Resolve selected active IFC IDs and return their current package memberships |
POST /api/agent/projects/project_123/elements
Authorization: Bearer twx_agent_…
Content-Type: application/json
{
"page": 1,
"pageSize": 50,
"filters": {
"all": [
{ "field": "ifcType", "operator": "equals", "value": "IFCWALL" },
{ "field": "property", "pset": "Pset_WallCommon", "property": "FireRating", "operator": "contains", "value": "60" }
],
"any": [
{ "field": "name", "operator": "contains", "value": "core" }
]
}
}Filters support all (AND) and any (OR) groups. Allowed fields arename, ifcType, and property; operators areequals, contains, and exists. Property filters require bothpset and property. Results include the IFC GlobalId as elementIdand the originating modelVersionId. Search accepts at most 20 filters and clampspageSize to 1–100. First inspect /models: element search and all element-addressed writes fail closed with 409 and unavailableVersions untilevery active model version reports an index status of ready.
elementId values from active-index discovery responses and send that value unchanged as an element target. If a GlobalId appears in more than one active IFC version, the server rejects package reconciliation and writes rather than guessing which model element you mean.Inspection writes
| Method | Path | Purpose |
|---|---|---|
| POST | /api/agent/inspection-events | Append one package-wide or up to 100 element-specific immutable lifecycle events |
| POST | /api/agent/packages | Create one active package with canonical active-IFC members |
| POST | /api/agent/packages/bulk | Create up to 25 packages atomically |
| PATCH | /api/agent/packages/:packageId/members | Add or remove members before lifecycle evidence locks the package |
| POST | /api/agent/lifecycle-import | Atomically import package-wide DB/DCG/DCK plus member-specific PTL evidence |
| GET | /api/agent/packages/:packageId/lifecycle | Read members, inherited DB/DCG/DCK evidence, and their lifecycle state |
| GET | /api/agent/projects/:projectId/lifecycle-summary | Stage-coverage totals and lifecycle state counts — identical to the Viewer statistics panel |
| GET | /api/agent/projects/:projectId/elements/:elementId/lifecycle | Per-element lifecycle (stages, state, completedStageCount, nextStage) — identical to what the Viewer renders |
| POST | /api/agent/inspections | Legacy mutable upsert (compatibility window only) |
| POST | /api/agent/inspections/bulk | Legacy mutable bulk upsert for up to 100 explicit or filtered active elements |
POST /api/agent/inspection-events
Authorization: Bearer twx_agent_…
Idempotency-Key: job-2026-08-20-001
Content-Type: application/json
{
"projectId": "project_123",
"packageId": "package_123",
"elementIds": ["2gTJhghMT81QThk15l2VwR"],
"stage": "design_certification",
"inspector": "Site inspection agent",
"approvalStatus": "passed",
"sourceFormNumber": "DCK-001",
"sourceFormRevision": "P02",
"evidenceLinks": ["https://example.invalid/checklist.pdf"]
}Each request creates append-only evidence. Idempotency-Key is optional on this endpoint but strongly recommended for any retryable job: a same-key, same-payload retry returns 200with replayed: true; using that key with a changed payload returns 409. Before appending, callGET /api/agent/projects/:projectId/packages with a key that hasinspection:write access. Select the existing package's idand use the returned member elementId / globalElementIdvalues as the canonical package membership. UseGET /api/agent/packages/:packageId/lifecycle when you also need that package's current inherited DB/DCG/DCK state.
Omit elementIds only when providing a packageId for package-wide Design Brief, Design Change, or Design Certification evidence; those events are inherited by every listed package member. For an element-targeted event, the server applies this exact package-resolution rule: with no packageId, zero memberships leaves the event unscoped, exactly one membership automatically attaches that package, and more than one membership rejects the request withelement belongs to multiple packages; packageId is required to record lifecycle evidence. If packageId is supplied, it must belong to the project and the target element must be a member of it; otherwise the write is rejected rather than guessed. Stages are independent — any stage can be signed off without predecessor stages being signed off first. Condition grade and end-of-life route are recorded in the separate post-removal assessment, not on the PTR approval. A clear or replacement is recorded as an immutable correction that supersedes prior evidence; it never erases the historical audit trail.
Evidence scope and stage rules
| Stage | Scope | Important rule |
|---|---|---|
| DB — Design Brief | Package-wide | One package event is inherited by each current package member |
| DCG — Design Change (TW2) | Package-wide | One package event is inherited by each current package member |
| DCK — Design Certification (TW3) | Package-wide | Capture controlled drawing, revision and named certification evidence |
| PTL — Permit to Load | Member-specific | Record a target GlobalId for every member receiving approval |
| PTR — Permit to Remove | Member-specific | Optional removal approval; condition and end-of-life assessment follows removal separately |
Package membership becomes immutable after direct package evidence, inherited evidence, or matching legacy lifecycle evidence is present. Do not reuse an archived, corrected, or replacement package for a new member list; create a new package instead. Package lifecycle responses expose the members, package-wide DB/DCG/DCK evidence, member-level evidence, and the current derived lifecycle state.
Package reconciliation and lifecycle imports
POST /api/agent/packages
Authorization: Bearer twx_agent_…
Content-Type: application/json
{
"projectId": "project_123",
"packageNumber": "T-B003",
"name": "H2681-TW4-01603 — ELS for T2 Basement",
"description": "Exact source member-mark to active IFC Mark reconciliation.",
"memberElementIds": ["2gTJhghMT81QThk15l2VwR"],
"linkageBasis": "exact_source_mark_to_ifc_mark"
}Package creation resolves every requested value against the active IFC index and returns canonical members plus membershipLocked. UsePOST /api/agent/packages/bulk with the same projectId and apackages array of at most 25 objects to create a batch. The complete batch is rejected and rolled back if any package cannot be reconciled. To avoid duplicate or ambiguous assignments, send { "elementIds": ["IFC_GLOBAL_ID"] } toPOST /api/agent/projects/:projectId/elements/package-membership first.
PATCH /api/agent/packages/package_123/members
Authorization: Bearer twx_agent_…
Content-Type: application/json
{
"addElementIds": ["2gTJhghMT81QThk15l2VwR"],
"removeElementIds": [],
"linkageBasis": "exact_source_mark_to_ifc_mark"
}Member changes require inspection:write access and are rejected after package evidence, or matching legacy evidence, has locked the membership. Archived packages are always read-only.
POST /api/agent/lifecycle-import
Authorization: Bearer twx_agent_…
Idempotency-Key: remaining-ptl-import-001
Content-Type: application/json
{
"projectId": "project_123",
"dryRun": false,
"package": {
"packageNumber": "T-B003",
"name": "H2681-TW4-01603 — ELS for T2 Basement",
"memberElementIds": ["2gTJhghMT81QThk15l2VwR"]
},
"designBrief": { "approvalStatus": "passed", "sourceFormNumber": "DB-003" },
"designCheck": { "approvalStatus": "passed", "sourceFormNumber": "DC-003" },
"permitToLoad": [
{
"elementId": "2gTJhghMT81QThk15l2VwR",
"approvalStatus": "passed",
"sourceFormNumber": "PTL-003"
}
]
}Lifecycle imports create the package only when it does not already exist, append package-wide DB and DC evidence, then append PTL evidence for each supplied member. All member IDs are reconciled against active IFC indexes, and all records are committed as one transaction. SetdryRun to true to run the identical validation and receive per-member results without retaining a package or event. An Idempotency-Key is required for every lifecycle import; reuse that same key for a retry of a committed import. PTR imports are intentionally unavailable here; they remain dependent on exact membership, condition grade, and end-of-life routing evidence.
Source-form idempotency and provenance fields
When an agent posts a lifecycle event with a sourceFormNumber, the server checks whether an active (non-superseded) event already exists for the same project, stage, element identity, and source form number. If one is found it is superseded by the new post rather than left as a duplicate. Re-running an import job that posts the same source forms is therefore safe: the Viewer converges to the latest sign-off status for each form, and the full audit history is preserved as superseded records. Omit sourceFormNumber when the source system does not have a stable form identifier.
Four optional provenance fields are available on every event write:
| Field | Type | Purpose |
|---|---|---|
sourceStatus | string (≤100 chars) | The source system's own status label (e.g. "Approved by TWC", "Superseded"). Stored alongside the TWX approvalStatus and shown in the element timeline. |
legacyImport | boolean | When true, marks this record as imported from a historic source system. The timeline displays a blue Imported badge next to the stage label. |
predecessorEvidence | string | Free-text description of predecessor evidence at import time. Informational — stored but does not affect lifecycle computation. |
validationDecision | string | The audited decision that accepted this legacy record without TWX predecessor sign-offs. |
POST /api/agent/inspection-events
Authorization: Bearer twx_agent_…
Content-Type: application/json
{
"projectId": "project_123",
"packageId": "package_123",
"elementIds": ["2gTJhghMT81QThk15l2VwR"],
"stage": "design_brief",
"approvalStatus": "passed",
"sourceFormNumber": "DB-001",
"sourceStatus": "Approved by TWC",
"legacyImport": true,
"predecessorEvidence": "Paper DB signed by TWC prior to digitisation",
"validationDecision": "Accepted by TWC on site audit 2024-01-15"
}Lifecycle read endpoints
Both read endpoints require a key with read scope and run the exact same package-aware lifecycle computation as the Viewer, so results match exactly what a user sees in the statistics panel and element timeline.
Project lifecycle summary
Runs buildPackageAwareLifecycles over every element in the active IFC model index — identical to what the Viewer computes after loading a model. Returns the same state counts and per-stage coverage the statistics panel displays.
Requires the IFC model index to be ready. Returns 409 with an unavailableVersions array while indexing is in progress, identical to element-addressed write endpoints.
GET /api/agent/projects/project_123/lifecycle-summary
Authorization: Bearer twx_agent_…
// elementCount matches the Viewer's model element count
{
"success": true,
"data": {
"elementCount": 150,
"lifecycle": {
"totalElements": 150, "completeElements": 7,
"inProgressElements": 5, "notStartedElements": 138,
"completionRate": 5,
"stagePassedCounts": { "design_brief": 10, "design_change": 8, … }
},
"stageCoverage": {
"design_brief": { "passed": 10, "issue": 1, "pending": 0, "not_started": 139 }, …
}
}
}
// 409 when model index is not ready
{ "success": false, "error": "…", "unavailableVersions": [{ "id": "v_123", "status": "indexing" }] }Per-element lifecycle
Applies the exact same package-aware visibility rules as the Table view and element timeline: single active package → package-scoped plus inherited design evidence; zero or multiple packages → unscoped legacy evidence only. Note that browser-legacy events are excluded from single-package members, matching Viewer behaviour.
Accepts either the IFC GlobalId or the viewer-local element ID — all aliases are resolved from the active package membership table so both identifiers return the same result. Does not require the IFC model index to be ready.
Code Structure
artifacts/
├── twx/ # Next.js viewer app
│ ├── pages/
│ │ ├── viewer/ # 3D viewer pages
│ │ └── api/ # API route handlers
│ ├── components/ # UI components (viewer, inspection, panels)
│ ├── hooks/ # Shared React hooks
│ ├── server/ # Express server, auth middleware, DB layer
│ └── shared/ # Drizzle schema (single source of truth)
│
└── manage/ # React SPA management console (/manage/*)
└── src/
├── pages/ # Dashboard, Projects, Assets, Admin
├── components/ # Sidebar, modals, UI primitives
└── hooks/ # Data-fetching and auth hooksKey conventions
- API responses always use
{ success: boolean, data?: any, error?: string } - Autosave uses ref-based debouncing to avoid stale closures — read the inline comments in the inspection form hook before modifying
- Auth is enforced via shared server-side helpers — every API route handler is responsible for checking the caller's identity and role before acting
- New DB columns: add to
shared/schema.tsand run the migration command above
Credits & Acknowledgments
TWX is part of academic research advancing digital construction management and Building Information Modeling (BIM) technologies for the built environment.
Research Programme
This application is developed as part of Francesco Tizzani's PhD research at the National University of Singapore (NUS), in collaboration with ETH Zürich's Future Cities Laboratory (FCL) programme, specifically within the Circular Future Cities (CFC) research group.
Academic Institutions
Industry Partnership
Asia
Leighton Asia
Francesco Tizzani holds the position of Group Manager of Information Systems & Digital at Leighton Asia.
Leighton Asia provides real case projects to test the research approach and inspection application, enabling practical validation of digital construction management methodologies in live construction environments.
Technology
Built with Next.js, React, TypeScript, and PostgreSQL. 3D visualisation powered by That Open Company — an open-source platform for BIM and AEC data.