1. Purpose
A Subcontract Package is a persistent scope object — a named bundle of Items selected from a single Estimate to be priced as a package by competing subcontractors. It is purely a scope container without internal state (state lives on the Subcontract Package Adjudications beneath it). Critically, the Package does not carry a list of pre-qualified Subcontractors; supplier/subcontractor selection lives inside the Adjudication workflow (step 1: Generate/select competing Subcontractors).
A Package can be re-adjudicated multiple times (scope changes, new suppliers) over its lifetime. Each adjudication round produces or updates a single system-generated Price Book containing Subcontract Resources — one per Item in the Package. This unified Price Book model ensures consistent pricing across rounds.
Analogy (glossary cooking model): the Subcontract Package is the shopping list — a named group of ingredients to be sourced from a single vendor, revisited when prices shift or suppliers change.
2. Attributes
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
name | string (short text) | ✅ | — | Human-readable name, e.g., “Electrical Package”, “Structural Steel” |
scope_description | string (long text) | ❌ | null | Free-text summary of what work is covered (inclusions/exclusions) |
estimate_id | Estimate ref | ✅ | — | Parent Estimate; Package is scoped to a single Estimate (BR-065) |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
Derived attributes (computed, not stored) — see §9.
3. No Internal State
The Subcontract Package itself carries no state machine or status attribute. State lives entirely on its child Subcontract Package Adjudications (BR-065):
- A Package in Draft state means all its Adjudications are in Draft
- A Package is Adjudicated if its latest Adjudication is Adjudicated
- A Package can be re-opened by reopening its latest Adjudication (back to Draft)
This design keeps the Package lightweight and focused on its single responsibility: grouping Items for procurement.
4. Relationships
Inbound (things referring to Subcontract Package)
| From | Cardinality | Notes |
|---|---|---|
| Subcontract Package Adjudication | SPA M:1 Package | Each Adjudication round belongs to exactly one Package; a Package has one-or-more rounds |
| Item | Item M:M Package | Package membership (Items can belong to multiple Packages; rare but allowed) |
Outbound (things Subcontract Package references)
| To | Cardinality | Required | Notes |
|---|---|---|---|
| Estimate | Package M:1 Estimate | ✅ | Parent Estimate; Package is estimate-scoped (BR-065). Locked: a Package belongs to exactly one Estimate (no cross-estimate bundling in v1). |
| Item | Package M:M Item | ✅ | The Items bundled for subcontract pricing; must have ≥1. Item membership is editable while current Adjudication is in Draft state; frozen when Adjudicated. Re-open Adjudication to Draft to modify Items (cascades to re-adjudication). |
| Subcontract Package Adjudication | Package 1:M SPA | ✅ | All adjudication rounds (one per re-pricing cycle) |
| Subcontractor list | — | ❌ | Locked: Package does NOT carry a pre-qualified Subcontractor roster. Supplier/Subcontractor selection lives in the Adjudication workflow (BR-068 step 1: Generate — the step where the Item bundle and competing Subcontractors are both assembled). |
5. Validation / Invariants
Rules that must hold at all times:
- Exactly one Estimate. A Subcontract Package belongs to exactly one Estimate; cannot span estimates (BR-065).
- Non-empty Item set. A Package must contain at least one Item (enforced on create and on update).
- Items belong to parent Estimate. All Items in a Package must belong to the same parent Estimate (cascading validation on Item addition).
- Unique name within Estimate. Package names must be unique within their parent Estimate (simple key constraint).
- Item membership locked on Adjudication lock. Item membership is editable while the latest Adjudication is in Draft state. Once latest Adjudication is Adjudicated, Item membership is frozen. To modify Items, estimator must re-open the Adjudication (back to Draft), which cascades to re-adjudication (BR-065a).
- At least one Adjudication. A Package must have at least one Subcontract Package Adjudication (created atomically on Package creation).
- State consistency. A Package’s derived state is determined by its latest Adjudication; no separate state attribute is stored.
- No pre-qualified Subcontractor list. Package does not carry a
pre_qualified_suppliersattribute or equivalent (BR-069a).
6. Worked Examples
Example 1 — Electrical Package: Multi-round Adjudication
Setup:
- Estimate: Bridge Project, Rev A
- Package: “Electrical Package” — covers 12 electrical Items (cabling, switchgear, lighting, etc.)
- Initial scope: 12 Items, total Estimated value ~$85,000
Round 1 — First adjudication:
Subcontract Package:
name: "Electrical Package"
scope_description: "All electrical supply, installation, testing, and commissioning"
estimate_id: <Bridge Project, Rev A>
item_ids: [E01, E02, ..., E12] (12 electrical items)
Subcontract Package Adjudication #1:
name: "Electrical Package — Round 1"
round_number: 1
status: Adjudicated
competing_subcontractors: [SubCo A, SubCo B, SubCo C]
awarded_subcontractor: SubCo A
system_generated_price_book: <PB-ELEC-R1>
source_type: system-generated
supplier: SubCo A
Price Book PB-ELEC-R1 contains:
Resource #1: "Electrical Item E01" — rate $6,800, unit LS
Resource #2: "Electrical Item E02" — rate $4,200, unit LS
... (12 Resources total, one per Item)
Each Item's Worksheet now has:
Worksheet Resource linking to its corresponding Resource in PB-ELEC-R1
(e.g., Item E01's Worksheet has WR → Resource #1 @ $6,800)
Derived state of Package:
latest_adjudication_status: Adjudicated
current_system_generated_price_book: PB-ELEC-R1
total_awarded_value: $52,400 (sum of all awarded Resource rates)
Round 2 — Scope increases; new suppliers:
Client requests two additional electrical items (E13, E14). Estimate is revised; 14 Items total.
Subcontract Package (updated):
item_ids: [E01, E02, ..., E14] (14 items — E13, E14 added)
Subcontract Package Adjudication #2:
name: "Electrical Package — Round 2 (scope increase)"
round_number: 2
status: Adjudicated
competing_subcontractors: [SubCo A, SubCo D, SubCo E] (B drops out, D & E are new)
awarded_subcontractor: SubCo D
system_generated_price_book: <PB-ELEC-R2>
source_type: system-generated
supplier: SubCo D
Price Book PB-ELEC-R2 (updated, same PB from Round 1):
All 14 Resources now present
Resources for E01–E12: re-priced by SubCo D (rates differ from SubCo A's Round 1 quotes)
Resources for E13–E14: newly added by SubCo D
Derived state of Package:
latest_adjudication_status: Adjudicated
current_system_generated_price_book: PB-ELEC-R2 (same ID, contents updated)
total_awarded_value: $59,800 (new SubCo D pricing across all 14 items)
Side-effect on Items:
Items E01–E12 keep their Worksheet Resources pointing to PB-ELEC-R2
(system silently updated the underlying Resource rates in PB-ELEC-R2)
Items E13–E14 have Worksheet Resources newly created, referencing PB-ELEC-R2
Example 2 — Structural Steel Package: Single Estimate, Persistent Object
Setup:
- Estimate: High-rise project
- Package: “Structural Steel” — 8 Items bundled under steel section headings
Subcontract Package:
name: "Structural Steel"
scope_description: "Supply, fabrication, and erection of all primary and secondary structural steelwork"
estimate_id: <High-rise project>
item_ids: [S01, S02, ..., S08] (8 steel Items)
Subcontract Package Adjudication #1:
round_number: 1
status: Adjudicated
awarded_subcontractor: Steel Fabricator Inc.
system_generated_price_book: <PB-STEEL>
All 8 Items now reference Resources in PB-STEEL.
Later (end of detailed design):
Scope change identified — one Item (S05) must be re-detailed
New Adjudication #2 opened to re-price:
Subcontract Package Adjudication #2:
round_number: 2
status: Draft
(estimator can import new quotes from Steel Fabricator Inc. or invite new suppliers)
The Package persists; its name and Item membership remain stable across rounds. State flows through the Adjudication(s) beneath it.
7. Derived / Computed Attributes
| Attribute | Derivation | Notes |
|---|---|---|
current_adjudication_status | The status of the latest (by round_number) Subcontract Package Adjudication | Draft or Adjudicated |
current_system_generated_price_book | Reference to the Price Book linked to the latest Adjudication | Singular across all rounds; ID remains stable, contents updated |
total_awarded_value | Sum of snapshot_rates of all Resources in current_system_generated_price_book | Only meaningful if current_adjudication_status = Adjudicated |
total_items | Count of Items in item_ids | Used for Package summary display |
item_unit_cost_variance | Optional: if Package’s Items span different Units, a flag indicating Unit mismatch | Helpful for audit trail |
8. Lifecycle (via Adjudication)
The Package itself has no explicit state machine. Its effective state is derived from its Adjudications:
Package is in Draft:
- Its latest Adjudication is in Draft
- No Resources have been awarded yet
- Estimator CAN adjust the Item membership (add/remove Items)
Package is Adjudicated:
- Its latest Adjudication is Adjudicated
- System-generated Price Book is locked and in use
- Items reference Resources from it
- Item membership is FROZEN. Attempting to add/remove Items while latest Adjudication is Adjudicated must raise a validation error.
Package is Re-opened (to modify Items):
- Estimator clicks “Re-open Adjudication” on the latest (Adjudicated) Adjudication
- Adjudication state moves back to Draft
- Resources remain in the Price Book but are marked as “pending update”
- Estimator can now add/remove Items
- On re-award (step 6 of BR-068 workflow), Price Book is re-adjudicated and updated in place