Skip to main content
estimate_takeoff_measurements rows with type multi_line or multi_segment carry two server-side fields beyond the flat points list: parts (client-written structure) and quantity_breakdown (server-written grouping). The shapes are defined in @stralos-co-nz/base (packages/base/src/takeoffParts.ts). The calculator in @stralos-co-nz/calculators (packages/calculators/src/takeoffParts.ts) provides the one takeoffBreakdown function both api-estimate and feature-estimate must run, so the live quantity readout and the stored figure cannot drift.
Line, Area, and Count measurements carry neither parts nor quantity_breakdown. Both fields are NULL for those types.

Why parts exists

points is a flat list for every measurement type. A Multi-line’s lines are not joined — the gap from the last point of line k to the first point of line k+1 is never measured. Without parts, a reader cannot tell where one line ends and the next begins and would measure those gaps as spans. parts records the structure (line starts for a Multi-line, one tag per span for a Multi-segment) so that rule is enforced by data, not by the reader’s assumptions.

TakeoffParts

parts is one of two shapes:

TakeoffLinesParts (Multi-line)

starts[i] is the index in points where line i begins. A Multi-line with n lines has starts.length === n, with starts[0] === 0 always. Tagging (base 1.8.0): tags[i] is the UUID of the takeoff tag applied to line i, or null for an untagged line. tags.length must equal starts.length. A row stored without tags is an untagged Multi-line and is backward-compatible with all earlier readers.

TakeoffSpansParts (Multi-segment)

tags[i] applies to the span from points[i] to points[i + 1]. tags.length must equal points.length − 1.

TakeoffTagMeta

A snapshot of the tag’s label and colour at the time the measurement was last saved. Tag labels are at most 60 characters (TAKEOFF_TAG_LABEL_MAX). Colour is #rrggbb. tagMeta is keyed by the non-null ids in tags. Every id in tags that is non-null must appear in tagMeta; ids with no span or line in tags must not appear.

TakeoffBreakdown

quantity_breakdown is server-written and must never be accepted from a client. It is one of two shapes:

TakeoffLinesBreakdown

TakeoffSpansBreakdown

TakeoffTagSubtotal

byTag is in order of each tag’s first span or line. Ids are lower-cased. tag: null is the untagged bucket. quantity is computed as round2(Σ its 3 dp span lengths) for Multi-segment, and as Σ of its lines' 2 dp lineLengths for Multi-line (calculators 1.10.0). The measurement’s quantity is Σ of the 2 dp subtotals — never round2(Σ all spans) — so the breakdown column always adds up to the stored quantity.

Validation

takeoffPartsIssues(parts, type, pointCount) returns a TakeoffPartsIssue[]. An empty array means the value is acceptable. Both api-estimate and feature-estimate run it on every write, including Line, Area, and Count writes (which must have null parts).

Grouping rules

takeoffBreakdown(parts, pointCount, spanLengths) is the one function hosts run to produce quantity_breakdown and quantity. Its three invariants:
  1. A line’s gap is never measured. A Multi-line’s spans are the point pairs inside each line. The step from the last point of line k to the first of line k+1 is not a span. Use takeoffSpanEndpoints to enumerate which pairs to measure.
  2. Group by tag id, not by position. Two runs of the same tag on a Multi-segment produce one subtotal. Tag ids are compared lower-cased.
  3. (1.10.0) A tagged Multi-line groups whole lines by tag. A line’s tag covers its whole length. A tag’s subtotal is the sum of its lines’ already-rounded 2 dp lengths. Tagging does not change the measurement’s total quantity.

Point edits

takeoffPartsAfterInsert(parts, pointCount, index) returns the updated parts after a midpoint handle inserts a new point at index. For a Multi-segment, the new point splits its span and both halves inherit that span’s tag. For a Multi-line, the insert must fall strictly inside one line and never changes the line count or its tag. takeoffPartsAfterDelete(parts, pointCount, index) returns the updated parts after a point is deleted, or undefined if the delete would leave a line or the whole run with fewer than 2 points. For a Multi-segment interior delete, the merged span takes the left (incoming) span’s tag. For Multi-line, every line keeps its tag as long as the line survives.

Removed sources

takeoffRemovedSources(before, after) returns the link sources an edit removes. Hosts use this to decide whether to show a confirm-and-unlink prompt. From calculators 1.16.0, no edit of a measurement that still exists removes its total:
  • Deleting a Multi-segment span (an end delete, or an interior delete that merges two spans) or a whole Multi-line line is a geometry change. The total and every surviving tag’s subtotal recalculate through their links.
  • A tag is removed only when no span or line carries it any more, because its last span or line was deleted or retagged.
  • Drags, inserts, and retagging a non-last span or line remove nothing.
  • Untagged spans and lines are never a source, so an untagged Multi-line lists no tags.
TakeoffRemovedSources.total is therefore always false. The field stays in the type so hosts that read removed.total still compile; it is due for removal at the next major version. See Linked work-item quantity for how per-tag subtotals feed QuantityLinkV1.

Constants