calculator.type is "pit". It measures a rectangular excavation, spoil per stratum, and a reinstatement build-up above whatever sits in the hole. The separate bedding layer and in-situ concrete pad are deprecated: the modal no longer offers them, but the engine still prices a stored one.
The engine lives in stralos-domain/packages/calculators/src/pit.ts. The modal imports the engine functions for its live preview, but nothing the preview computes is persisted.
The embedded
calculator column on work items and components is retired in the Estimate API (migration 0058). All embedded configs have been converted to project-level calculator entities. The Activities API still stores the column verbatim. New estimate and activity calculator work uses calculator entities and their link registers.This page describes the calculators 1.7 and calculator-ui 0.4 contract, with the structure fit display from calculator-ui 0.10.2. The Estimate and Activities remotes and APIs all pin calculators 1.7.0 or later, and both remotes pin calculator-ui 0.4.0 or later, so structure-position editing, the clearing of stored bedding and concrete-base fields, and position-aware displacement apply on both surfaces and on the server.
Calculator shape
PitCalculatorArm carries:
bedding and concreteBase are required and nullable. layers is required and may be empty. Normalisation maps older absent fields to null (bedding, concreteBase) or [] (layers), leaving one representation of each absent state. There are no clearance fields or automatic plan sizing.
Structures (PitStructure)
Structures are recorded for geometry purposes — their volume is subtracted from the excavation when calculating backfill, and their footprints drive the per-structure pad and the combined-footprint warning. Structure records generate no row of any kind, and their supply is priced outside the calculator.
The union is self-narrowing: no shape carries both a height and a length, so no code has to decide which to read. A horizontal cylinder occupies its diameter of both the W axis and the depth.
Vertical position
PitStructurePosition places a structure along the pit’s vertical axis. It carries a single distance measured from one of two reference ends:
With
from: "floor", the structure’s underside sits distanceM above the stack base (the top of the concrete base, or the bedding, or the design floor). distanceM: 0 places the underside on the stack base, which is identical to an absent position.
With from: "surface", the structure’s top sits distanceM below the ground surface. distanceM: 0 places the top flush with the surface — the position for a manhole or chamber lid at finished ground level.
The engine resolves each structure’s seat once and records every placement in structurePlacements on the engine output. The displacement, each layer’s net volume, fit warnings, and the diagram all read from structurePlacements, so the drawing and the quantities cannot place a structure differently.
Normalisation strips { from: "floor", distanceM: 0 } and any position whose from is not one of the two valid ends. “On the floor” has one representation: absent position. A surface-referenced or lifted floor-referenced position is preserved as-is.
A structure that reaches past the pit is not clamped. Its volume is clipped to the hole, and a structure-depth warning is emitted. The proud part of a floor-referenced structure — the volume above the surface — is still deducted from the lowest reinstatement layer, matching the behaviour for unpositioned structures.
The server-side schema validates structures with a plain union, not a discriminated union on shape. Because orientation splits cylinder into two arms, shape is not unique across the three members. A discriminated union on shape would throw a duplicate-discriminator error at parse time, producing an unhandled 500 on every pit save that included a structure. The plain union tries each arm in order and reports the first match.
A lying cylinder’s plan projection is
lengthM × diameterM exactly, not a bound on it. One pitStructureFootprint definition serves both the per-structure pad and the fit warning, so a pad can never be computed against a footprint the warning does not know about.
Excavation strata (CalculatorExcavationStratum)
Pit, volume, and trench calculators share the same live excavation fields.
The legacy
batterStyle and batterPerSideMm fields are converted to the current batter or overcut shape during normalisation and then stripped.
Overcut
PitCalculatorArm.overcut is the calculator-level allowance for the bucket taking more ground than the design line. It carries two independent members:
Both members are optional and there is no default for either. An absent
overcut key is different from overcut: {} — an untouched pit mints no key at all and computes byte-identically with any pit stored before the field existed.
Per-row width override
overcut.widthPerSideMm is the base for the whole excavation. Each stratum can override it with CalculatorExcavationStratum.overdigPerSideMm:
- A present value replaces the base for that stratum. It does not stack with it.
- Absent means inherit the calculator’s base.
- A stored
0means neat — the stratum is dug to the design line regardless of the calculator’s base.
Height band geometry
The height band hangs below the design floor. The bedding is measured up from the cut floor (reading A), so the bedding fills the band first:b >= h: bedding fills the whole band and a prism of(b − h)sits above the design floor.b < h: bedding fills the bottombof the band; the lowest reinstatement layer absorbs the remainder (h − b).b = 0(no bedding): the lowest reinstatement layer absorbs the whole band. A pit with no layers leaves the band as plain extra excavation with nothing to fill it back.
PitOvercutBand is published on the engine output:
The engine also publishes
beddingExtent and padExtent as { z1, z2 } depth ranges for diagram rendering, and overcutWidthPerSideM (the resolved base width in metres) and overcutHeightM (the resolved height in metres) alongside the existing cutDepth.
structurePlacements is published on the engine output alongside structuresVolume. It is an array in structures order, each entry carrying:
Overcut-needs-bedding warning
When a pit has a concrete base, a height overcut, and no bedding (or a bedding shallower than the overcut height), the engine emits a new warning:beddingDepthM is null when there is no bedding at all. requiredDepthM is the resolved overcut height. The warning fires on <, so equal depth is silent. It warns and does not re-price — the engine still hands the remainder of the band to the lowest reinstatement layer. The width overcut does not trigger this warning.
Normalisation and migration
normalizePitCalculator resolves the overcut before processing strata:
- Override-era config (the
overcutkey is present, even as{}or height-only): row values are per-row overrides. Rows that state nothing inherit the base. - Legacy config (the
overcutkey is absent): the base is set to the maximumoverdigPerSideMmacross all strata. Every row that stated a width keeps its own value. Every row that stated nothing is pinned at0(dug neat). Migrating a legacy pit moves no quantity — the pinned rows are dug exactly as they were under the per-row model.
{}.
Disposal substance outputs
WhenCalculatorExcavationStratum.substance is present, the engine derives physical disposal outputs from the snapshot’s bulkingPct and looseDensity:
DisposalSubstanceSnapshot freezes the organisation substance identity and physics at the moment it is selected or refreshed. It contains id, name, looseDensity, bulkingPct, nullable compactionPct, optional canUseForReinstatement, and resolvedAt. Pricing and the charge unit belong to the assigned destination line. A stratum without a substance can still emit its in-situ output, but it has no derived loose-volume or tonnage output.
canUseForReinstatement controls whether the stratum may appear in the From excavation list of the material picker. See Reinstatement eligibility for the derivation rules — they apply identically to the pit, trench, and volume calculators.
This substance parity behaviour is identical across the trench, pit, and bulk volume calculators: the same 12 solid m³ of the same substance produces the same disposal row quantity in all three.
Disposal output totals
calculatePitCalculator returns four per-unit disposal totals, mirroring the trench arm:
All four are per unit — multiply by
count to get the totals the UI shows.
A stratum WITH a disposition reports its whole remainder (gross in-situ less reinstatement reuse). A stratum with NO disposition reports only its implicit unallocated slice. This asymmetry is intentional: a named destination describes all of the material, while an unnamed stratum keeps the legacy implicit-only summary, which is what makes an untouched config byte-identical.
Reinstatement layers
Calculators 1.6.0 replaced the singlebackfill entry with an ordered list, layers[]. Each entry is a PitReinstatementLayer with the same shape as PitCalculatorLayer: it can be bought fill, a from-the-dig selection, or a non-bulk rate item.
Layers are ordered top to bottom, above the concrete base. The engine fills the available reinstatement volume from the first layer down.
Legacy backfill compatibility
A config stored before the list existed still carries a top-level backfill entry. The engine migrates it at compute time into the single layer pit-backfill, using the same layer id to keep row keys stable. Configs are never rewritten at rest, so old-shape pits continue to round-trip as stored.
The Activities service accepts backfillFrom on the legacy backfill entry for the same reason: refusing it would return 422 on a calculator’s next save when the engine still honours the key there.
backfillFrom on reinstatement layers
backfillFrom is accepted on any entry in pit.layers[], in addition to bedding and concreteBase. The field identifies which excavated stratum a from-the-dig layer draws from.
The Activities service validates backfillFrom on write. Two failure codes apply:
Both return
422 Unprocessable Entity and name the offending path. A well-formed reference beside isBackfill: false is stored as sent; the engine’s normaliser strips it at compute time.
The Estimate API enforces an additional eligibility rule on write:
The rule applies only to a fresh pick. On update (
PUT /calculators/:id), a stratum the stored config already draws from is exempt from this 400. A stratum with no substance, or whose snapshot does not carry the canUseForReinstatement flag, is accepted regardless of compaction. The UI advises on those strata; the save gate does not block them.
Every pit create or update that carries a config also runs the shared save gate, assertCalculatorSaveReady. It refuses an incomplete config with 422 calculator_incomplete (for example, a stratum with material left and no substance or disposal destination, a reinstatement layer with depth and no material, a from-the-dig layer whose stratum is explicitly ineligible even on update, or a non-bulk unit a pit cannot measure), and a config it cannot read with 422 calculator_config_unreadable. See Save validation for the full rules.
Picker and generated-row labels
For a from-the-dig reinstatement layer, the material picker presents one choice per eligible excavation stratum as Excavation — <stratum name> (each stratum is checked withisStratumReinstatementEligible; ineligible strata are omitted). When the pit has exactly one excavation stratum, normalisation resolves that source and the generic From the dig choice is not shown.
Generated rows use Excavation — <stratum name> (<calculation>) when the source is known. With two or more strata and no backfillFrom selection, the row uses Excavation (<calculation>) and the engine emits fill-layer-stratum-unspecified. Saving a resolved label updates the existing linked row in place, preserving its row id, generated key, quantity, and estimator-entered rate.
A reinstatement stack shorter than the pit leaves a gap at the surface; the engine does not fill it implicitly. A stack taller than the pit produces an overfill warning.
Bedding and concrete base (PitCalculatorLayer, PitConcreteBase)
bedding and concreteBase are deprecated since calculators 1.7.0. calculator-ui 0.4.0 writes null to both and drops any stored value on open. Use a reinstatement layer for the material that sits below the structure, and set PitStructure.position to lift the structure to the right height. The engine still prices a stored bedding or concrete base for backwards compatibility; other consumers may still read them.null. Turning a layer off keeps its draft values only for the current modal session.
Bedding always covers the full plan. When a stored concrete base and an overcut height are both active, the engine expects bedding at least as deep as the overcut band and otherwise raises
overcut-needs-bedding. The modal never shows this state, because it drops a stored bedding and concrete base on open.
Geometry
Plan area with batter
The pit plan at the surface is entered:z is
overcutWidth that is a total effective width, so each stratum is converted once, at the call boundary, by batterOvercutWidth:
z is recovered as off(z) = (w(z) − W0) / 2, which returns exactly batterPerSideMm / 1000 at an angled stratum’s top. Converting anywhere else as well is how the same value ends up meaning two things.
A(z) is quadratic and convex in z inside an angled segment, so a trapezoid over it lies above the curve and over-counts — in the customer’s disfavour. pitVolumeBetween therefore integrates each segment with Simpson’s rule, which is exact for a quadratic:
L0 × W0 × depth.
The classical frustum formula
h/3·(A₁ + A₂ + √(A₁A₂)) does not apply. It assumes similar faces, and equal per-side offsets grow L and W by the same absolute amount, so their ratio changes with depth. On a square plan the faces are similar, which is why the fixtures insist on L0 ≠ W0 — a square fixture would pass a wrong implementation.Two shoulder rules
The profile is discontinuous where a square cut sits over a narrower stratum, soA(z) is two-valued exactly at the boundary. The two rules answer different questions and must not be merged:
- Point queries —
pitAreaAt(z), used by the pad, the bedding and the fit checks. The preceding (upper) segment owns the boundary, matching the shipped lookup, so a caller asking how wide the pit is at 1.2 m gets one answer, deterministically. - Integration —
pitVolumeBetweenis strictly segment-local. Every segment integrates using its own width at its own endpoints, including at a shared boundary, where the upper segment uses its bottom width and the lower segment uses its top width — different values, both correct.
Three depth rules
Inherited from the shipped trench, inresolveStrataDepths:
- The last stratum is the derived base remainder — its depth is
pitDepth − Σ(other strata depths), floored at 0. Its batter and overcut are applied like any other stratum. - Non-base strata are clamped to the pit. A stratum, or the part of one, sitting below the floor is never excavated and never disposed of. Raw effective depths drive the UI and the “exceeds” warning; clamped depths drive volume and cut.
- Depth the strata do not cover is still excavated at an unbattered plan widened by the calculator-level overcut. This is a separate code path from the base remainder, and it is what makes preserving an explicit empty strata list safe — a user who deletes every stratum still gets the full excavation, with no stratum-specific disposal rows.
Floor order and reinstatement
The floor order is bedding → concrete base → structures. The reinstatement layers sit above the stack base (the top of the pad, or of the bedding, or the design floor) and fill upward. Each layer’s volume is the band its typed depth occupies, clamped at the surface. The lowest layer also absorbs any height-overcut band below the design floor.overfill warning; the excess is not priced.
Each layer’s volume is a physical result. A bought-fill snapshot supplies a density range and compaction; a from-the-dig selection nets against the named excavation stratum when destination-aware partitioning is active.
The pre-1.6.0 backfill residual (excavation − displacement, clamped at zero) is replaced by the sum of all layer band volumes. A migrated pit’s one layer fills exactly the same space the residual did, so no quantity moves on migration.
Fit warnings (PitWarning)
Fit checking replaces auto-sizing. None of these blocks a save — a hole too small for its contents is the estimator’s judgement to override, not the calculator’s to refuse. Warnings are collected into the advisory panel below the modal body.
The combined check is a sum of areas against the pad area, not a packing test. Three structures whose areas sum to less than the pad can still be impossible to lay out, and this passes them. It catches the gross case and nothing subtler, which is why it warns rather than blocks — a false sense of a fit guarantee would be worse than no check.
backfill-clamped uses >=, not >: structures and layers that exactly fill the pit leave no room for reinstatement, which is the same condition to tell the estimator as overflowing. The displacement > 0 guard keeps a degenerate empty pit from warning about a 0 = 0 it has no opinion on.
Normalisation
normalizePitCalculator brings a stored config to the current shape and is idempotent:
countresolves to a positive number, defaulting to1.- Structure ids are minted only where absent or duplicated. Reordering the list changes nothing. Each structure’s
positionis normalised:{ from: "floor", distanceM: 0 }is stripped (absent is the canonical spelling for “on the floor”), as is anypositionwhosefromis not"floor"or"surface". - Empty bedding or concrete-base objects become
null; an absent concrete-base scope becomes"pit". - The strata are normalised before the fill entries, because the R3 re-point (step 5) reads them.
- Reinstatement migration (calculators 1.6.0): A config with no
layerskey has itsbackfillentry moved intolayersas onePitReinstatementLayerwith idpit-backfilland a typed depth equal to the stack base, so it fills exactly the space the pre-1.6.0 residual filled and prices to the same double. A config with neither becomeslayers: []. Thebackfillkey is deleted either way. - R3 re-point: When a reinstatement layer (including the migrated one) uses
isBackfill: truewith nobackfillFrom, and the calculator has exactly one excavation stratum, normalisation setsbackfillFromto that stratum. With zero strata or two or more, the layer is left as-is and thefill-layer-stratum-unspecifiedadvisory fires at row-build time. - Layer ids are made unique; an absent or duplicate id is re-minted, except
pit-backfillwhich is always preserved so existing links survive. - The overcut base is resolved before the strata. Legacy row widths migrate without changing quantity; absent means inherit and
0means dug neat. - An absent
existingLayerslist is seeded with one full-depth stratum; an explicit empty list stays empty. Stratum ids and current batter/overcut fields are normalised, while retired pricing fields are stripped at the shared strata boundary.
Defaults
Exported fromstralos-domain/packages/calculators/src/defaults.ts so the shape of a valid config stays the domain’s business:
createDefaultPitCalculator opens on a 2.5 × 2 × 2 m pit holding one vertical Ø1.05 m chamber 1.5 m tall, with bedding and concreteBase set to null, and one reinstatement layer with no material whose depth equals the pit depth. That layer fills the hole exactly as the old derived backfill did; choosing a material or splitting into multiple layers is up to the estimator. createDefaultPitStructure mints a box, the shape whose dimensions are all directly entered.
createDefaultReinstatementLayer(strata) — used by all three calculator arms when an Add layer button is pressed — names a sole stratum on the new layer’s backfillFrom only when that stratum is eligible for reinstatement. When the calculator’s only stratum has canUseForReinstatement === false (or derives ineligible through the legacy rule), the fresh layer opens with no material set instead. Two or more strata leave the choice to the estimator regardless of eligibility.
PIT_BACKFILL_LABEL ("Backfill") is deprecated since calculators 1.6.0. The engine no longer reads it; unnamed reinstatement layers are now labelled by their position in the stack (for example, Reinstatement layer 1), matching the trench and volume calculators.
In the modal
PitCalculatorModal uses a two-pane layout: editable geometry and layers on the left, with the section preview and material-balance ledger on the right. At narrow widths the panes stack in reading order.
View mode toggles
The modal opens in simple mode. Two toggle switches appear below the title:- Advanced Mode — when off (the default), the calculator-level Overcut side and Overcut depth strip fields, the Excavation Batter controls, and the per-layer result-line output columns are hidden. When on, all controls are visible.
- Assign to Work items — visible only when the host supplies
renderLayerAssignment. When on, each excavation and reinstatement row shows its Used by assignment control. This toggle is independent of Advanced Mode.
Overcut and per-row overcut column
The calculator-level Overcut side and Overcut depth fields, which set the base overcut width per side and the height below the design floor, are visible only in advanced mode. A stratum can override the width; an explicit zero means neat excavation. Each excavation stratum row has an Overcut column (in mm) that is visible in both modes. The value is the additional overcut width per side that this stratum adds on top of the stratum below it — the compound overcut model. Changing the base overcut, depth, or stratum order preserves each row’s increment. Each excavation stratum has a substance picker and, when the host supplies disposal rates, a Disposal material picker. Leave Onsite retains material. A named disposal item records an explicit removed destination. Both the estimate host and the activities host supply the Disposal rate group to pit calculators. Each reinstatement layer uses a single material picker for bulk bought fill, a named excavation stratum, or a non-bulk rate item. Both hosts supply the non-bulk catalogue to the pit editor. A pit layer states neither a length nor a top-face area, so the picker offers only non-bulk items priced by volume (inSituM3) or by a unit that is not geometric (count, for example each or lot). Only strata eligible for reinstatement appear in the From excavation list; strata whose substance has canUseForReinstatement === false are omitted. When two or more excavation strata exist, a from-the-dig layer with no stratum selected shows the advisory Choose which excavation layer this reinstatement layer comes from. When a stored layer names an ineligible stratum, the stratum stays visible in the row and shows the advisory “This excavated material can no longer be used for reinstatement. Choose another material for this layer.” Saving is blocked only when the stratum’s snapshot carries canUseForReinstatement: false. The calculator shows physical output chains; pricing belongs on the destination line.
Each row’s result sentence names the volume state: excavation reads <inSituM3> m³ in situ · × <bulking> bulking · <looseM3> m³ loose spoil · ...; a bought fill row reads <inSituM3> m³ compacted · × <compaction> compaction · <looseM3> m³ loose to buy · .... A from-the-dig fill row reads <inSituM3> m³ · loose, counted with its stratum. Section totals and balance ledger group headings also name the state. These labels are display-only — the engine keys inSituM3 and looseM3 are unchanged.
The base remainder’s derived depth is displayed as a read-only figure with a remainder tag. It cannot be edited directly.
A Check before saving advisory panel appears above Save when any issue exists — for example, when an excavation stratum has no substance. The panel uses role="note" and data-part="calculator-advisories". Save is not gated by advisories.
When renderLayerAssignment is provided, strata and the entries from layers[] show Used by controls. The modal has no bedding or concrete-base rows, so it offers no pit-bedding or pit-concrete-base control. The engine still emits those rows for a legacy stored bedding or pad that has not been reopened and saved. For configs that still carry a legacy backfill entry, the migrated pit-backfill row is shown. The bulk excavation row (pit-excavation) is not offered in the assignment UI: it is excluded from the layer catalogue and from the measurement catalogue’s per-layer readings.
When renderLayerAssignment is provided, the Assign to Work items toggle is shown. Turning it on reveals the Used by controls.
Measurement rows (layerKind: "measurement") are not offered to the modal’s assignment slot. The engine appends one synthetic m:o:<key> or m:l:<layerId>:<key> row per available read-out. Use nonMeasurementRows and isMeasurementRow from @stralos-co-nz/calculators to filter them by kind rather than testing the id prefix.
A new stratum is added at the top while the base remainder stays numbered 1. The excavation name column is headed Name (optional). The Name box opens empty; the generated name (for example, Existing layer 2) appears as its placeholder and is written to the config on save if the estimator leaves the box blank. The quantity control is hidden for new calculators; a legacy count above one remains effective and is shown as a read-only note.
When onRename is present, the pencil beside the title edits only the trimmed calculator name. A rename does not save geometry or run the calculator reconcile.
Advisory panel
An advisory panel appears below the modal body when any of the following conditions hold. None blocks saving.- A fit warning fires (structure depth, combined footprints, backfill clamped, or strata exceed depth). The engine’s
overcut-needs-beddingwarning needs a stored concrete base, which the modal drops on open. - An excavation stratum has no substance snapshot, so loose spoil volume and weight cannot be stated for it.
structure-depth warning for a floor-referenced structure means it stands above the ground (requiredM − availableM is the height above). From calculator-ui 0.10.2 this is a warning, not information: the structure’s row in Structures shows it in the same orange as a structure that goes through the floor (”… stands 0.30 m above ground — check this is intended. Its full volume is counted.”), and the advisory reads “Structures — a structure stands 0.30 m above ground — check this is intended (counted in full)”. Earlier versions showed it as a grey note. Nothing blocks saving, the full volume is still counted, and no quantity changes. A surface-hung structure that reaches past the floor keeps the “reaches … where it is placed” wording.
Discard confirmation
Closing the modal via Escape, the × button, Cancel, or a backdrop click raises a Discard changes? confirmation whenever there is anything unsaved. The guard covers two sources of unsaved state:- Geometry changes — any dimension, layer, or stratum edit since the modal opened.
- Host-held edits — a pending rename or a pending Used by choice. Both are held by the host until the modal’s Save is pressed, so the modal’s own geometry snapshot cannot see them. The host passes
hostDirty={true}while either is in flight; the guard ORs this into the dirty check on every close route, including the backdrop.
Generated rows
The shared engine emits physical output rows with stable type-prefixed keys, layer ids, layer kinds, display names, and the outputs that exist for each row. Pit outputs include the bulk excavation total, per-stratum disposal quantities, and one row per reinstatement layer, plus legacy bedding and concrete-base rows when a stored config still carries them. Structures affect geometry and warnings but do not emit rows. Reinstatement layer rows use the keypit-layer-<layerId> and carry the layer’s own id as layerId. A pit migrated from the pre-1.6.0 shape keeps the id pit-backfill on its first layer, so its row appears under pit-layer-pit-backfill and every existing link remains valid. Unnamed layers are labelled by position — for example, Reinstatement layer 1.
From-the-dig reinstatement rows are named Excavation — <stratum> when the stratum is unambiguous, and Excavation when no stratum is specified with two or more strata. Estimator-entered names on priced rows remain unchanged by a save.
Project and library calculator services bind these physical outputs to destination work-item or activity-component rows through their assignment registers. Pricing and rate-option selection remain on those destination rows.
Bulk dig exclusion
Thepit-excavation layer id is never written to the estimate register. Default seeding, an explicit PUT /links, and a replayed register all drop it silently. The engine still computes the bulk dig total — the stratum rows, fill draw, and totals derive from it — but no work-item component row is created for it. The excavation strata disposal rows are what an estimator prices the dig by. Layer-scoped measurements of the bulk dig (m:l:pit-excavation:*) are also excluded. Overall calculator readings (m:o:*) remain available for assignment.
Deleted row stays deleted
When an estimator deletes a component row that a calculator generated, the Estimate API removes the row’s link from the register and records the layer id in the calculator’sdecided_layer_ids. On the next calculator save, the reconcile skips any layer in decided_layer_ids and does not re-insert the row. Seeding from the default work item also skips decided layers.
Write paths
The embeddedcalculator columns on estimate work items and components are retired (migration 0058). Every embedded config was converted into a project-level calculator row before that migration deployed. The Estimate API now ignores the calculator key on write; an explicit null is accepted without error but does not delete any rows. Rows previously generated from embedded calculators survive as ordinary components.
New estimate calculators use project calculator routes. New activity calculators use /activity-calculators and its links register. Both validate the current config with the shared engine, reconcile the complete owned key set transactionally, and reject unreadable configs or pricing fields inside calculator geometry.
From calculators 1.17.0, an estimate pit’s lengthM can be linked to takeoff through inputLinks as the pit.lengthM field, a measurement total or tag subtotals in metres. The resolved value is written into config.lengthM. See Calculator input links.
Disposal material validation and disposal row binding apply to pit calculators through the shared reconcileProjectCalculator path. See Project-level calculator disposal reconciliation for the full rules, including how rows with no rate_item_id are relinked on the next save.
Quantities, units and density
count multiplies each physical output before the single two-decimal rounding step.
Bought fill carries compacted and loose cubic metres plus a minimum and maximum tonnage derived from its frozen density range. A single tonnes output is present only when both density bounds agree. Disposal outputs derive loose volume and tonnes from the stratum’s substance snapshot. Pricing conversion is applied later by the assigned work-item or activity-component line.
Measurement values are per-unit figures
Measurement rows (layer ids beginningm:o: or m:l:) always state the per-unit figure, rounded once from the unscaled quantity. Physical rows are scaled by count as before. On a calculator with count > 1, a layer measurement and that layer’s own physical row carry different values. For example, with count: 5, pit-excavation states five pits’ total excavation, while m:l:pit-excavation:inSituM3 states one pit’s excavation.
Dividing a rounded scaled figure back down to recover the per-unit value is a rounding error: on a non-terminating per-unit volume the divide inflates the figure by the rounding residual multiplied by count. Measurement rows are built from the unscaled outputs and rounded once.
Named destination partition
When any excavated stratum in the config names a destination (that is, any stratum carriesallocations), all from-the-dig layers use partitionExcavationByDestination instead of the default pro-rata partitionExcavation. The gate is the config, not the calculator arm — the trench, pit, and volume arms all apply the same rule.
Under the named-destination rule, a PitCalculatorLayer with isBackfill: true and a backfillFrom snapshot draws its reuse demand from the named stratum before any pro-rata assignment runs. The draw converts placed volume to in-situ volume through that stratum’s own substance physics (bulking and compaction). Any unnamed from-the-dig demand is then drawn pro rata over what remains.
A config that names no destination partitions exactly as before — partitionExcavation runs and every stored config is byte-identical.
backfillFrom is physical when a stratum names a destination: selecting a source stratum changes the disposal quantities.
Staleness
A pit has no SOQ-sourced input, soPitCalculatorArm carries no needsReview field. Giving it one would create a flag that can be set and never cleared. Staleness applies to the Volume calculator alone.
