Introduction

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)

Platform Guide

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.

First time? Ask your platform administrator to invite you to a project. You will receive an email with an invitation link — clicking it automatically joins you to the project with the role your admin selected.

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.

ControlWhat it does
Left-click elementSelect it — opens the Inspect panel on the right
Left-click + dragOrbit the model
Right-click + drag / middle-dragPan the camera
Scroll wheelZoom in / out
Fit to viewReset camera to show the full model
Grid toggleShow / hide the ground reference grid
Colour codingColour elements by inspection status (passed / issue / pending / uninspected)
MeasureClick two points on the model to measure the straight-line distance
Hide selectedRemove selected elements from view without deleting them
Show all hiddenRestore all previously hidden elements
Explode toggleSeparate components outward so individual elements are easier to select
Section planeSlice through the model horizontally — drag the right-side slider to move the cut up or down
IsolateHide 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.

FormScopeFields collected
DB · Design Brief (TW1)Usually package-widePackage number; brief / scope; risk category; design brief reference; Temporary Works Coordinator; Temporary Works Designer
DCG · Design Change (TW2)Usually package-widePackage 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-widePackage 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-levelPackage 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-levelPackage number; permit-to-remove date; removal method statement; removal location; responsible site person; TWC authorising PTR; authorised signatory and role
Post-removal assessmentRecovered componentCondition 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 packageId is 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.

Viewer-only role? You can read inspection data but all form fields will be disabled. Ask a project admin to upgrade your role to Editor if you need to record inspections.

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.

Pending transfers appear on the Assets page in the Management Hub. Either party can cancel a pending transfer before both approvals are collected.

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
RoleView & inspectRecord inspectionsRegister / link assetsTransfer assetsManage membersEdit project settings
Viewer✓—————
Editor✓✓✓———
Project Admin✓✓✓✓✓✓
Platform Admin is a separate global role that supersedes project-level roles. Platform admins can access all projects, manage users, configure circularity formula weights, and view system-wide analytics from the Admin page.

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.


Cross-Organisation Trading

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

FilterHow it works
SearchFull-text search across asset number, description, manufacturer, type, category, and serial number
IFC typeFilter by IFC element class (derived from the linked BIM element)
StatusActive · In Storage · In Transit · Retired
Value rangeMin / max purchase value (advanced filters panel)
Availability windowFilter 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.

Submitting a request does not initiate a transfer — it signals interest to the asset owner. The actual transfer is always initiated separately through the dual-approval Transfer workflow (see section 6).
Already requested? Assets you have already submitted a request for are shown with a Requested badge so you don't submit duplicates.

System Design

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.


Dependencies

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.

TechnologyVersionPurpose
Next.js (Pages Router)15SSR framework, file-based routing, API routes
React19UI rendering
TypeScript5Type safety across the full stack
Express5Custom HTTP server wrapping Next.js; owns auth middleware, rate-limiting, and object-storage routes
@thatopen/components2.xHigh-level BIM viewer: IFC loader, fragment manager, raycasting, measurement, section planes, explode tool
@thatopen/fragments2.xGeometry representation and streaming for large IFC files
Three.js0.160+Underlying WebGL 3D engine used by That Open Engine
Drizzle ORM0.39+Type-safe SQL query builder and schema definition
PostgreSQL16Relational database — projects, inspections, elements, analytics
@clerk/express1.xServer-side authentication; attaches req.auth via Express middleware
styled-jsx—Scoped per-component CSS (bundled with Next.js)
qrcode1.xServer-side QR code generation for asset identification
express-rate-limit7.xRate 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.

TechnologyVersionPurpose
React18UI rendering
Vite5Dev server and production bundler
TypeScript5Type safety
Wouter3.xLightweight client-side routing (no React Router overhead)
Tailwind CSS3Utility-first styling
shadcn/ui—Accessible component library built on Radix UI primitives
TanStack Query5Server-state management, caching, and background refetching
Recharts2.xComposable charts for visitor analytics and project metrics
react-simple-maps3.xWorld map for geography analytics
@clerk/clerk-react5.xClient-side auth hooks and sign-in/out UI
Lucide React—Icon library

Infrastructure & Tooling

TechnologyPurpose
pnpm workspacesMonorepo package management — shared dependencies hoisted to root
ReplitCloud hosting, path-based proxy routing, object storage (IFC files), secrets management
ClerkAuth platform — user management, session tokens, organisation invitations
PostgreSQL (Replit-managed)Primary database, shared across both apps via the same connection string
drizzle-kitSchema push and migration tooling

Reference Documentation


Setup

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:

CategoryRequiredNotes
Clerk API keysYesPublishable + secret keys from your Clerk dashboard
Database URLYesPostgreSQL connection string
Session secretYesLong random string — generate with openssl rand -hex 32
Object storageYesBucket identifier for IFC file storage

Database migrations

# Push schema changes to Postgres (run from the viewer app directory)
npx drizzle-kit push --force

Backend

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

MethodPathDescription
GET/api/projectsList projects the user is a member of
POST/api/projectsCreate a new project
GET/api/projects?id=:idGet single project
GET/api/projects/:id/summaryProject stats + caller's role
GET/api/projects/:id/membersList members and pending invitations
POST/api/invitationsInvite a user by email
POST/api/invitations/:id/acceptAccept an invitation
GET / POST / DELETE/api/projects/:id/api-keysProject-admin management of external-agent keys; a plaintext key is returned only on create or rotation

Inspections

MethodPathDescription
GET/api/inspection-events?projectId=:idList immutable lifecycle evidence and calculated lifecycle state
POST/api/inspection-eventsAppend immutable lifecycle evidence; a target with one package is linked automatically, while multiple packages require an explicit packageId
GET/api/inspection-events/element/:elementIdGet 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-packagesBrowser-session package management; membership is read-only after lifecycle evidence exists

Assets & Transfers

MethodPathDescription
GET/api/elementsAsset inventory
POST/api/elementsRegister a new asset
POST/api/elements/transferInitiate dual-approval transfer
GET/api/elements/check-linking/:elementIdCheck if a BIM element is linked to an asset

Circularity

MethodPathDescription
GET/api/circularity/:projectIdDCI / FDCI scores for all elements in a project
GET/api/tw-packagesList TW control packages associated with a project

Marketplace

MethodPathDescription
GET/api/marketplaceList 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/requestSubmit a sourcing request for an asset

Analytics

MethodPathDescription
POST/api/analytics/pageviewRecord a page-view beacon (unauthenticated, rate-limited)
POST/api/analytics/session-endRecord session duration on navigation away
GET/api/statsPlatform-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.


Machine Integration

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>.

Keep keys private. Do not place an agent key in a browser, source repository, or client-side environment variable. Rotate the key immediately if it is exposed.

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.

GrantPermits
readProject discovery, model status, and indexed element search
inspection:writeImmutable 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

MethodPathGrantPurpose
GET/api/agent/projectsreadList only projects granted to the key
GET/api/agent/projects/:projectId/modelsreadList active IFC model versions and index status
POST/api/agent/projects/:projectId/elementsreadSearch indexed elements in active versions only
GET/api/agent/projects/:projectId/packagesinspection:writeList package IDs and canonical element membership before an inspection write
POST/api/agent/projects/:projectId/elements/package-membershipinspection:writeResolve 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.

Use canonical IFC GlobalIds only. Take 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

MethodPathPurpose
POST/api/agent/inspection-eventsAppend one package-wide or up to 100 element-specific immutable lifecycle events
POST/api/agent/packagesCreate one active package with canonical active-IFC members
POST/api/agent/packages/bulkCreate up to 25 packages atomically
PATCH/api/agent/packages/:packageId/membersAdd or remove members before lifecycle evidence locks the package
POST/api/agent/lifecycle-importAtomically import package-wide DB/DCG/DCK plus member-specific PTL evidence
GET/api/agent/packages/:packageId/lifecycleRead members, inherited DB/DCG/DCK evidence, and their lifecycle state
GET/api/agent/projects/:projectId/lifecycle-summaryStage-coverage totals and lifecycle state counts — identical to the Viewer statistics panel
GET/api/agent/projects/:projectId/elements/:elementId/lifecyclePer-element lifecycle (stages, state, completedStageCount, nextStage) — identical to what the Viewer renders
POST/api/agent/inspectionsLegacy mutable upsert (compatibility window only)
POST/api/agent/inspections/bulkLegacy 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

StageScopeImportant rule
DB — Design BriefPackage-wideOne package event is inherited by each current package member
DCG — Design Change (TW2)Package-wideOne package event is inherited by each current package member
DCK — Design Certification (TW3)Package-wideCapture controlled drawing, revision and named certification evidence
PTL — Permit to LoadMember-specificRecord a target GlobalId for every member receiving approval
PTR — Permit to RemoveMember-specificOptional 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:

FieldTypePurpose
sourceStatusstring (≤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.
legacyImportbooleanWhen true, marks this record as imported from a historic source system. The timeline displays a blue Imported badge next to the stage label.
predecessorEvidencestringFree-text description of predecessor evidence at import time. Informational — stored but does not affect lifecycle computation.
validationDecisionstringThe 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.


Codebase

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 hooks

Key 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.ts and run the migration command above

Acknowledgements

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

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.