Reports Hub: surface business segments and expand collection linking

Objective

  • Make business segments easy to see and filter in Reports Hub exports (job-level and line-item), so finance and ops can segment revenue the same way they do in scheduling and admin tooling.
  • Expand collection linking so report builders can combine the collections they need (e.g. inspections + contacts + payments + charges) without hitting dead ends in the link picker — aligned with revenue-reconciliation and pricing-analysis workflows from product feedback.

Background

  • Product feedback from the Andy/Erin Reports Hub session (see related issues ATT-1856, ATT-1857, ATT-1859) identified revenue breakdown, categories, fees, and client identity as gaps. These two items were called out as additional needs on top of that list.
  • Business segments today: Segment data exists on services (_segmentId → business segment name) and is partially wired into exports, but it is not surfaced well for end users:
  • On inspections, businessSegmentId in attik-backend/src/config/exportFieldDefinitions.ts is pickerHidden: true and resolves from the primary service’s segment via charges (aggregationPath: '__primarySegmentId' in aggregationHelpers.ts) — usable for aggregation group-by, not obvious as a column.
  • There is no inspection-level “Business Segment” name column in the picker (unlike services segment and charges catalogSegment).
  • Charge rows expose catalogSegment (name) and a filter-only catalogBusinessSegmentId; segment appears reliably only when charges are the expanded linked collection.
  • BusinessSegment model is referenced in attik-backend/src/util/functions/dataExports/metadata.ts (getModelForEntity('businessSegments')) but business segments are not an allowed base/linked entity in ALLOWED_ENTITIES / entityFieldDefinitions.
  • Collection linking today: Joins are governed by referenceableFromBase in exportFieldDefinitions.ts, with resolvers in getRelatedIds and reverseLookupConfig (joinedReport.ts, metadata.ts). The matrix is intentionally sparse, not N×N:
  • contacts base → [] — no linked collections from a contact-base report.
  • Inspections link people, payments, charges, etc., but not contacts directly (contacts are reached via people or reverse lookup from contacts → inspections).
  • Payments link only inspections + contacts; not charges, fees, or people.
  • People base omits services and reports “until reverse lookups support mixed job ids without ambiguity” (comment in referenceableFromBase).
  • Prior ticket ATT-1489 (Done) flagged that inspections + contacts could not be re-associated when editing an older revenue reconciliation report — evidence that the link matrix blocks real user workflows.
  • Full relationship map and maintenance rules: attik-backend/src/util/functions/dataExports/RELATIONSHIPS.md (must stay in sync with referenceableFromBase, getRelatedIds, reverseLookupConfig).

Scope

Frontend (attik-frontend/src/app/tools/data-exports/)

  • Link collections UI (CreateReportForm.tsx, ReportColumnsSection.tsx, useDataExportsEntities.ts) reads referenceableFrom from GET /data-exports/entities?include=referenceable — any backend matrix expansion must be reflected here automatically; verify multi-link + column picker UX when many collections are linkable.
  • Filters (ReportFiltersSection.tsx) already has a business-segment API endpoint branch — ensure new segment columns/filters use consistent labels with admin business-segment settings.

Backend — Business segments

  • attik-backend/src/config/exportFieldDefinitions.ts — add or unhide user-facing fields, e.g. Primary Business Segment (name) on inspections and/or quotes, resolved from primary charge → service → _segmentId → segment name (mirror __primarySegmentId pipeline in aggregationHelpers.ts and charge catalogSegment in joinedReport.ts).
  • Consider exposing segment on payments-linked job context if product wants fee reports segmented by line of business.
  • Decision needed: Job-level primary segment only vs. segment per charge line (already on charges) vs. both.
  • Decision needed: Whether to add business segments as a first-class export entity (segment catalog report) or only resolved names on jobs/charges.

Backend — Collection linking (“all collections ↔ all collections”)

  • Treat “all mapped with all” as product intent — implement as expanding referenceableFromBase plus resolvers, not a single generic join engine (unless dev chooses otherwise).
  • Key gaps to close (non-exhaustive; dev to produce full matrix diff):
  • Contacts base: allow links to inspections, quotes, people, payments (via reverse paths already partially in reverseLookupConfig.contacts).
  • Inspections base: allow direct contacts link (today: people only) if product prefers contact fields without people row expansion.
  • Payments ↔ charges/fees, reports ↔ quotes, services ↔ payments, etc. — audit each ALLOWED_ENTITIES pair against user stories.
  • Each new pair requires: entry in referenceableFromBase, resolver in getRelatedIds or reverseLookupConfig, row-expansion rule in expandRowsByRelated if one-to-many, filter path in filterBuilders.ts, and RELATIONSHIPS.md update.
  • Decision needed: True full N×N vs. “complete the matrix for the 12 export entities” with documented exclusions (e.g. circular multi-hop).
  • Decision needed: Behavior when linking multiple expandable collections (charges + people + payments) — row cartesian product limits / performance caps.

Product / architecture decisions (for the implementer)

  • Decision needed: Priority order — segment surfacing (likely smaller) vs. link matrix (larger) vs. bundling with ATT-1856/1857/1859.
  • Decision needed: Backward compatibility for saved reports that reference link pairs removed or renamed.

References

Please authenticate to join the conversation.

Upvoters
Status

Triage

Board
🏠

Main App

Date

About 4 hours ago

Author

Linear

Subscribe to post

Get notified by email when there are changes.