1. Purpose
A Reference Rate is an admin-defined, manual benchmark unit rate for a category of work, used by Anomaly Review as the anchor layer for rate-based outlier detection. It is not used in cost build-up — purely a sanity-check mechanism to flag estimating outliers before Commercials.
Not versioned: Reference Rates store a single current rate per Reference Rate. When edited, the old value is overwritten. No rate history is tracked on Reference Rates themselves.
Reference Rates sit in Layer 2 of Anomaly Review’s three-layer model:
- Layer 1: deterministic rule checks (Alpha)
- Layer 2: Reference Rate checks — rules-first match (Alpha · BR-145), AI-assisted match (Release · BR-146). Reference Rates are manual, admin-defined benchmarks, not derived from historical data.
- Layer 3: historical AI comparison (Release · distinct mechanism using archived Price Books and completed estimates, not a Reference Rate versioning feature)
Analogy (glossary cooking model): Reference Rates are expected ingredient costs — a benchmark against which to sanity-check the rates being used in the current estimate.
2. Attributes
| Attribute | Type | Required | Default | Notes |
|---|---|---|---|---|
id | UUID | ✅ | generated | System-managed |
name | string (text) | ✅ | — | e.g., “Reinforced concrete works”, “Bored piles” |
unit | Unit ref | ✅ | — | One of the managed Unit library (e.g., m³, lm, m²). See Unit glossary |
rate | number (decimal) | conditional | null | Single point-value rate. Mutually exclusive with rate_min and rate_max; use this or the range pair, not both |
rate_min | number (decimal) | conditional | null | Minimum of expected range. Must be paired with rate_max if either is used |
rate_max | number (decimal) | conditional | null | Maximum of expected range. Must be paired with rate_min if either is used |
tolerance | number (percent) | ❌ | null | Optional tolerance around point rate (e.g., 5 = ±5%). Only valid if rate is set, not with range |
categorization_option_id | Categorization Option ref | ❌ | null | Optional scope — when set, this Reference Rate matches Items carrying this specific Categorization Option (in addition to Unit match) |
description / notes | long text | ❌ | null | Optional context — e.g., “Reinforced concrete, 25–30 MPa, inland NZ”, “Includes cartage” |
created_at / updated_at | timestamp | ✅ | system | Audit |
created_by / updated_by | User ref | ✅ | system | Audit |
3. Rate specification (point vs range)
Reference Rates support two modes — exactly one must be supplied:
Point rate + tolerance
rateset (e.g., 500.00)toleranceoptional (e.g., 5 = ±5%, so 475–525 is accepted)- If no tolerance supplied, only exact matches (or within system precision epsilon) flag
Range (min / max)
rate_minandrate_maxboth set (e.g., 450.00 and 550.00)tolerancemust be null- Any Item rate outside [min, max] inclusive flags an anomaly
Validation invariant:
- Exactly one of: (1)
rate+ optionaltolerance, or (2)rate_min+rate_max - Cannot mix modes (e.g.,
rate+rate_min)
4. Matching logic (two-tier)
Alpha — Rules-first (BR-145)
Anomaly Review matches an Item against a Reference Rate when:
- Unit match: Item’s Unit = Reference Rate’s Unit (required)
- Categorization scope (if set): If the Reference Rate has a
categorization_option_id, the Item must carry that Categorization Option (tagged)
If both conditions hold and the Item’s unit rate falls outside the Reference Rate’s range/tolerance, a firm anomaly flag is raised with a deterministic explanation: “Unit rate $620/m³ exceeds Reference Rate range $450–550/m³”.
Multiple Reference Rates can match one Item (different scopes, different categories); all are surfaced.
Release — AI-assisted (BR-146)
When an Item isn’t matched by BR-145 (typically untagged, missing Categorization), the AI layer:
- Semantically matches the Item’s description against the Reference Rate library
- High-confidence matches → raise a firm flag (as if BR-145 matched)
- Low-confidence matches → surface as “possible anomalies” for estimator acceptance/dismissal
- Feedback improves future matching
- AI provides plain-English explanation cross-referencing similar items, rates, and project context
5. Relationships
Inbound (things referring to Reference Rate)
None — Reference Rates are a lookup; Anomaly Review references them, but no entity owns or links to a Reference Rate persistently. The match is computed at review time.
Outbound (things Reference Rate references)
| To | Cardinality | Required | Notes |
|---|---|---|---|
| Unit | Reference Rate M:1 Unit | ✅ | Every Reference Rate has a Unit (e.g., m³, lm, m²) |
| Categorization Option | Reference Rate M:0..1 Categorization Option | ❌ | Optional scope; when set, Reference Rate only matches Items tagged with that Option |
6. Validation / Invariants
Rules that must hold at all times:
- Unit mandatory.
unitcannot be null (BR-144). - Rate specification exactly one. Either (
rate+ optionaltolerance) or (rate_min+rate_max), not both, never neither. - Range ordering. If using range,
rate_min ≤ rate_max. - Tolerance only with point rate.
tolerancecan only be set ifrateis set; ignored or null if using range. - Categorization scope optional.
categorization_option_idis optional (null = matches all Items with matching Unit, regardless of tags). - Scope must be valid. If
categorization_option_idis set, it must reference an existing Categorization Option; the Option’s parent Categorization’s scope must include Item (since Reference Rates are used in Item matching).
7. Derived / Computed Attributes
| Attribute | Derivation | Notes |
|---|---|---|
effective_min | rate_min if range, else rate - (rate × tolerance / 100) if tolerance, else rate | Upper and lower bounds; used by Anomaly Review to determine in-range |
effective_max | rate_max if range, else rate + (rate × tolerance / 100) if tolerance, else rate | |
is_scoped | categorization_option_id is not null | UI badge: “Scoped to [Category]” if true |
8. Admin management & seeding
Who can manage Reference Rates: Admin and Lead Estimator (see roles-permissions.md).
Post-migration seeding (workflow idea): Benchmark’s archived Price Book contains historical rate ranges (min/max from historical transactions per category). A post-migration seeding workflow could:
- Extract category + min/max pairs from Benchmark’s Price Book
- Create Reference Rate records in bulk, tagged with the source category’s Categorization Option
- Flag for admin review before activation
9. Worked Examples
Example A — Reinforced concrete works (range, scoped)
Reference Rate:
name: "Reinforced concrete works"
unit: m³
rate_min: 450.00
rate_max: 550.00
tolerance: null
categorization_option_id: <Categorization Option "Concrete Works">
description: "In-situ reinforced concrete, 25–30 MPa, placed and finished. Inland NZ"
Anomaly Review match (Alpha, BR-145):
Item "Concrete pour — foundation slab":
unit: m³
quantity: 85
Item unit rate: $620/m³
Item Categorization tags: ["Concrete Works", "Foundation"]
Match result:
✅ Unit matches (m³ = m³)
✅ Categorization matches (Item has "Concrete Works" tag)
⚠️ Rate out of range: $620/m³ exceeds max $550/m³
Flag: "Unit rate $620/m³ exceeds Reference Rate range $450–$550/m³ for Reinforced concrete works"
Example B — Bored piles (range, no categorization scope)
Reference Rate:
name: "Bored piles"
unit: lm (linear metre)
rate_min: 650.00
rate_max: 850.00
tolerance: null
categorization_option_id: null
description: "Bored piles, 600mm–1000mm diameter, 0–30m depth, inland NZ"
Anomaly Review match (Alpha, BR-145):
Item "Piles — dock structure":
unit: lm
quantity: 230
Item unit rate: $720/lm
Item Categorization tags: ["Structural", "Piling"]
Match result:
✅ Unit matches (lm = lm)
✅ No Categorization scope set → Unit match alone is sufficient (Item tags irrelevant)
✅ Rate in range: $720/lm within $650–$850/lm
Flag: None. Item passes Reference Rate check.
Example C — AI-assisted semantic match (Release, BR-146)
This example shows the AI layer kicking in because the Item is missing the Categorization scope the Reference Rate requires — so the deterministic BR-145 path doesn’t fire.
Reference Rate:
name: "Concrete deck pour — bridge works"
unit: m²
rate_min: 380.00
rate_max: 450.00
tolerance: null
categorization_option_id: <CatOption "Bridge works" (Trade category)>
Item (untagged for Trade):
description: "Pour concrete for bridge deck"
unit: m²
quantity: 300
Item unit rate: $320/m²
Item Categorization tags: [] (no "Bridge works" tag applied)
Anomaly Review match (Release, BR-146):
⚠️ Not matched by BR-145: Unit matches but the Reference Rate is scoped to
Categorization "Bridge works", which this Item does not carry.
→ AI layer attempts semantic match:
Item description "Pour concrete for bridge deck" →
Candidate Reference Rate "Concrete deck pour — bridge works" (high confidence, 0.87)
High-confidence match → firm flag:
"AI matched to 'Concrete deck pour — bridge works' ($380–$450/m²).
Item rate $320/m² is below range. Similar items cost $400–$420/m²."
Estimator can:
✅ Accept the flag (retain it for review; may also tag the Item with
"Bridge works" so future matches hit BR-145 deterministically)
❌ Dismiss it (feedback improves future matching)
Note: if the Reference Rate above had categorization_option_id: null (unscoped), Unit alone would match and BR-145 would flag the Item deterministically — BR-146 only handles the cases BR-145 cannot reach.