diff --git a/CONTEXT.md b/CONTEXT.md index 30eb39f0..70877a02 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -119,17 +119,45 @@ _Avoid_: property query, segment, search (the postcode-search journey is unrelat ### Live projects **Project**: -A delivery programme within a Portfolio — a batch of Properties taken through retrofit together — identified by its HubSpot **project code** (`hubspot_deal_data.project_code`; a stable **project id** will replace the code once the HubSpot ops board is settled, so the code is the *current* identifier, not the essence). A Property whose deal carries no project code sits outside every Project (the "Unknown Project" grouping) and cannot be reached by selecting Projects. +A delivery programme within a Portfolio — a batch of Properties taken through retrofit together — identified by its HubSpot **project code** (`hubspot_deal_data.project_code`; a stable **project id** will replace the code once the HubSpot ops board is settled, so the code is the *current* identifier, not the essence). A Property whose deal carries no project code sits outside every Project (the "Unknown Project" grouping) and cannot be reached by selecting Projects. Distinct from the **Ara Project** (next section) — the app-owned works programme that will eventually supersede this feature; where "Project" alone is ambiguous, say **HubSpot Project**. See [ADR-0018](./docs/adr/0018-ara-projects-supersedes-hubspot-live-projects.md). _Avoid_: programme, scheme, deal group, batch (a **Batch** is a separate per-deal field) **Bulk document download**: An app-triggered, asynchronous assembly of a single ZIP of the documents held against a chosen set of Properties — picked as a row selection in the Documents tab, scoped by the tab's current Project/group — delivered as a time-limited link emailed to the requester (and, within the session, retrievable in-app). A selection that matches no documents produces no ZIP: it fails rather than emailing an empty archive. Properties are matched to their documents by **landlord property id**. See [ADR-0011](./docs/adr/0011-app-owned-task-marker-in-task-source.md). _Avoid_: bulk export, document export, zip download +### Ara Projects + +The works-management module at top-level `/projects` (schema in `src/app/db/schema/projects/`, merged in PR #404). The strategic successor to the HubSpot-sourced "Live projects" feature above — both coexist for now, with no migration in v1. See [ADR-0018](./docs/adr/0018-ara-projects-supersedes-hubspot-live-projects.md) and the schema's own docs at `src/app/db/schema/projects/schema.md`. + +**Project** (Ara): +An organisation-owned works programme — the aggregate root of the Ara Projects module. Belongs to an `organisation` and is **many-to-many with Properties** (via `project_property`), unlike the HubSpot **Project** above (externally sourced, at most one per Property). Owns its **Project Workstreams**, which in turn own Stages, Evidence requirements and Contractor assignments. Where "Project" alone is ambiguous, say **Ara Project**. +_Avoid_: programme, scheme, works package + +**Workstream**: +A category of works (Windows, Doors, Roofs, Electrical, Heating, …) — reference data in `workstream`, instantiated for one Ara Project as a **Project Workstream** (`project_workstream`), which owns its own Stage ladder, Evidence requirements and Contractor assignments. +_Avoid_: measure (Measure is taken by the Scenarios domain), trade, work type + +**Stage**: +One step in a Project Workstream's ordered ladder (`project_workstream_stage`, positioned by `order`). Each Project Workstream owns its own ladder — different workstreams may follow different workflows. The v1 default ladder is Ordered / In progress / Completed / Charged / Closed, and the terminal stage is the one with the highest `order`. A Work order's stage FK (`work_order.project_workstream_stage_id`) is the **only lifecycle mechanism in v1** — `work_order.status`, `work_order.priority` and `project_workstream.current_stage_id` exist in the schema but are untouched. +_Avoid_: status (the unused text column), phase, step + +**Work order**: +One unit of work issued to a contractor: a (Property, Project Workstream Stage, contractor organisation, unique reference, forecast end date) row in `work_order`. Its position in the workflow is its Stage FK (see **Stage**). +_Avoid_: job, ticket, task (a **Task** is the BulkUpload orchestration handle) + +**Evidence requirement** / **Evidence**: +An **Evidence requirement** (`project_workstream_evidence_requirement`) is configuration: a required `file_type` (the existing enum) per Project Workstream, optionally narrowed to a single Stage. **Evidence** is a submission: an `uploaded_files` row with `file_source = 'projects'`, linked to its Work order (`work_order_id`) and optionally to the requirement it satisfies. Evidence is **advisory-only in v1** — badges and counts, never a gate: it does not block stage transitions and there is no approval state. +_Avoid_: document (generic), attachment, proof + +**Contractor**: +An `organisation` assigned to a Project Workstream via `project_workstream_contractor`, carrying per-assignment `update_stages` / `upload_documents` permission flags — not a separate entity or table. Contractor users sign in through the existing `team` / `team_members` structures (each organisation gets a default team). +_Avoid_: supplier, installer, vendor, subcontractor + ### Tags **Tag**: -A user-created, coloured label owned by a Portfolio, used to form an arbitrary **group** of its Properties for bulk actions (e.g. running Scenarios over the group, or excluding it from a Reporting view). Flat (no hierarchy) and many-to-many: a Property carries zero or more Tags, and a Tag labels zero or more Properties. Distinct from a **Project** (a HubSpot-sourced grouping, at most one per Property) and from a **Scenario** (a modelling question): a Tag is an app-owned, hand-curated selection with no external source and no modelling semantics of its own. +A user-created, coloured label owned by a Portfolio, used to form an arbitrary **group** of its Properties for bulk actions (e.g. running Scenarios over the group, or excluding it from a Reporting view). Flat (no hierarchy) and many-to-many: a Property carries zero or more Tags, and a Tag labels zero or more Properties. Distinct from a **HubSpot Project** (a HubSpot-sourced grouping, at most one per Property) and from a **Scenario** (a modelling question): a Tag is an app-owned, hand-curated selection with no external source and no modelling semantics of its own. _Avoid_: label, category, group (the group is the *effect* of a Tag, not a separate entity), segment, tag group **Bulk tag assignment**: diff --git a/docs/adr/0018-ara-projects-supersedes-hubspot-live-projects.md b/docs/adr/0018-ara-projects-supersedes-hubspot-live-projects.md new file mode 100644 index 00000000..282790e5 --- /dev/null +++ b/docs/adr/0018-ara-projects-supersedes-hubspot-live-projects.md @@ -0,0 +1,86 @@ +# 18. Ara Projects supersedes HubSpot Live projects, coexisting at top-level /projects + +Date: 2026-07-21 + +## Status + +Accepted + +Numbering note: 0015–0017 are in flight on the reporting-redesign branch +(PR #416), alongside 0010; 0018 avoids collision with all of them (the same +convention as ADR-0013's numbering note). + +## Context + +PR #404 merged the schema for a new works-management module +(`src/app/db/schema/projects/`) whose aggregate root is called **Project**. But +"Project" is already taken in our domain language: CONTEXT.md ("Live projects") +and ADR-0013 define a Project as the HubSpot-sourced delivery grouping — at +most one per Property, identified by `hubspot_deal_data.project_code`, served +by the existing `/portfolio/[slug]/your-projects` feature. + +The two are not one concept wearing two names — they differ on every axis: + +- **HubSpot Project**: externally owned, read-only in the app, at most one per + Property, no internal structure beyond membership. +- **Ara Project**: app-owned, organisation-scoped, many-to-many with + Properties, and structured — it carries configuration (Workstreams, Stages, + Evidence requirements) and operational data (Contractor assignments, Work + orders). + +Something had to give: rename the new module, converge the old feature into it +now, or run both under disambiguated names. + +## Decision + +- **Ara Projects is the strategic successor** to the HubSpot "Live projects" + feature (`your-projects`). Long-term, works delivery lives in Ara Projects + and the HubSpot grouping is retired. +- **Both coexist for now.** Convergence and data migration are explicitly out + of scope for v1 — `your-projects` keeps working unchanged, and no Ara + Projects work may depend on changing it. Retiring it gets its own ADR when + it is actually scheduled. +- **The route namespace is top-level `/projects`**, with its own layout — not + under the portfolio chrome. An Ara Project belongs to an `organisation` and + spans Properties without regard to Portfolio boundaries, so + `/portfolio/[slug]/...` is the wrong scope; it would also collide with the + very feature it supersedes. +- **Domain language is disambiguated in CONTEXT.md** (Ara Projects section): + where "Project" alone is ambiguous, qualify — **HubSpot Project** vs + **Ara Project**. The Ara Projects glossary terms (Workstream, Stage, Work + order, Evidence requirement, Evidence, Contractor) match + `src/app/db/schema/projects/schema.md` and are used verbatim by all Ara + Projects issues (#405–#429). +- **ADR-0013 is unaffected**: its contrast of Tags with "Projects" + (HubSpot-owned, one-per-Property, not user-editable) refers to HubSpot + Projects and remains correct as written. + +## Alternatives considered + +- **Rename the new module** (Programmes, Works, Schemes). Avoids the clash + permanently, but the merged schema, the product and the business all say + "Project" — a rename trades a bounded period of ambiguity for a permanent + mismatch with the business vocabulary, and it protects the name of the + feature that is scheduled to disappear. +- **Converge now** — migrate `your-projects` data into Ara Projects in v1. + Removes the coexistence cost, but front-loads a migration out of an + externally-owned system before the new module has proven its shape, and + couples v1 delivery to HubSpot ops decisions (the project-code → project-id + switch is itself still pending on the HubSpot side). +- **Nest under the portfolio chrome** (`/portfolio/[slug]/projects`). Matches + where the old feature lives, but imposes a Portfolio scope the aggregate + doesn't have, and makes the old and new features near-indistinguishable in + the URL while both exist. + +## Consequences + +- Two meanings of "Project" coexist in code and conversation until + `your-projects` is retired. The mitigation is linguistic and enforced by + review: CONTEXT.md is canonical, and ambiguous uses get qualified. +- `/projects` becomes a reserved top-level namespace (it was unused before + this decision). +- Existing HubSpot-Project surfaces (the "Unknown Project" grouping, the + Documents tab's Project scoping for Bulk document download) continue to mean + HubSpot Projects; nothing there is renamed. +- The eventual retirement of `your-projects` will need its own ADR covering + data migration and user transition; nothing in v1 begins or blocks it.