domain

Construction Management

Thin bounded context for field site-visit photos. Owns the Site Image aggregate and bulk-upload use case only. Estimates, BOM, and schedules are not modeled yet.

DomainStatus: Implemented in api-v1Airtable-backed

Overview

Construction Management in apps/api-v1/src/modules/construction-management currently owns Site Image only. A SiteImage is one site-visit photo for a field report. The only write path is Bulk Upload Site Images (QueueBulkUploadSiteImagesUseCase).

This slice is create-only. There is no nested System (single aggregate) and no new deployable — the module is served by api-v1.

Estimates, BOM, schedules, and other construction aggregates are not modeled yet. Do not treat this domain page as a construction execution catalog.

Project is not an aggregate in this domain. Site Image references a construction project by ProjectId (projects.domain_id). The API resolves projects by domain_id only and never accepts Airtable rec… ids as projectId. Do not invent a Project entity here.

Implementation Status

Status: Implemented in api-v1

Airtable construction base (AIRTABLE_CONSTRUCTION_BASE_ID, default appQV2WVHMyLGkNx0). Persistence is Airtable site_images; bulk-upload status lives on ui_site_images_bulk_upload. There is no outbox. After persist, the use case drains SiteImageRecorded and dispatches in-process to DomainEventAuditLogger. Dispatch failure must not roll back persisted site images.

OCC is omitted (glossary deviation): create-only Airtable records have no compare-and-set.

Aggregates and supporting types

ConceptPersistence todayBadge
SiteImageAirtable site_images (domain_id = SiteImageId, ADR-0048)Implemented in api-v1
ReportDateValue object on Site Image (YYYY-MM-DD)Implemented in api-v1
SiteImageAttachmentValue object — exactly one http(s) imageImplemented in api-v1
ProjectIdReference only (projects.domain_id)Implemented in api-v1

room, notes, and issueNumber exist on the Airtable table and the aggregate but are not part of the bulk-upload form.

Not modeled yet

Do not invent catalog pages for:

  • Estimates
  • Bill of materials (BOM)
  • Schedules
  • Change orders, RFIs, submittals, budgets, or other construction execution aggregates
  • A Project aggregate owned by this domain
  • A SiteImageBatch aggregate (N images is a use-case concern)

Primary Code References

  • Module root: apps/api-v1/src/modules/construction-management
  • Glossary: docs/api-v1/construction-management/GLOSSARY.md (module GLOSSARY.md only points there)
  • Aggregate: apps/api-v1/src/modules/construction-management/core/domain/site-image/SiteImage.ts
  • Use case: apps/api-v1/src/modules/construction-management/core/application/use-cases/QueueBulkUploadSiteImagesUseCase.ts
  • OpenAPI: apps/api-v1/src/modules/construction-management/adapters/inbound/rest/site-images/siteImages.openapi.ts
  • Composition: apps/api-v1/src/modules/construction-management/composition/construction-management.composition.ts
  • Airtable base id: appQV2WVHMyLGkNx0 (AIRTABLE_CONSTRUCTION_BASE_ID)

HTTP surface

Canonical mount: /api/v1/construction.

MethodPathOpenAPI operationIdBehavior
POST/site-images/actions/bulk-uploadqueueBulkUploadSiteImagesAccept N photos; HTTP 202; poll status
GET/operations/:operationIdgetSiteImageBulkUploadOperationPoll { operationId, status, message }

Idempotency-Key is required on the POST. The 202 body includes operationId and statusUrl. requestRecordId is HTTP/adapter-only (the Airtable ui-row id) and must not appear on SiteImage. Polling requires requestRecordId on the original POST so there is a ui row to look up.

Do not invent additional operation ids. Link generated OpenAPI rather than copying request/response schemas.

Open Questions

  • When estimates, BOM, or schedules are modeled, they belong in this bounded context — they are not documented here until domain types exist.
  • Whether a future Project aggregate lives in this domain, project-coordination, or another owner. Today this module only stores ProjectId.