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.
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
| Concept | Persistence today | Badge |
|---|---|---|
SiteImage | Airtable site_images (domain_id = SiteImageId, ADR-0048) | Implemented in api-v1 |
ReportDate | Value object on Site Image (YYYY-MM-DD) | Implemented in api-v1 |
SiteImageAttachment | Value object — exactly one http(s) image | Implemented in api-v1 |
ProjectId | Reference 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
Projectaggregate owned by this domain - A
SiteImageBatchaggregate (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(moduleGLOSSARY.mdonly 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.
| Method | Path | OpenAPI operationId | Behavior |
|---|---|---|---|
POST | /site-images/actions/bulk-upload | queueBulkUploadSiteImages | Accept N photos; HTTP 202; poll status |
GET | /operations/:operationId | getSiteImageBulkUploadOperation | Poll { 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.
Documentation Links
- api-v1 service:
/docs/services/api-v1 - Ubiquitous language:
/docs/domains/construction-management/language - Backend platform:
/docs/systems/backend-platform - ADRs:
docs/adr/README.md
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
Projectaggregate lives in this domain,project-coordination, or another owner. Today this module only storesProjectId.