Triage
2Under consideration
Capacity-based pricing modifier — surge pricing when schedule fills
Objective Allow companies to configure a pricing modifier that automatically increases service fees as daily schedule capacity fills toward a configurable threshold — applying demand-based pricing without manual intervention. Give brands a revenue lever that is already familiar from other industries (airlines, rideshare) and requires no staff action during the booking flow. Background Sean Garvey (SW/SE Region) proposed this concept in the June 22 regional call: as a day's available inspection slots fill toward 80–90% capacity, prices should increase automatically by a configurable amount — similar to airline seat pricing. Catherine Lemoine validated the idea and noted it should function as a pricing modifier, meaning it affects the client-facing charge that lands on the confirmed workorder. This is a distinct feature from day-of-week pricing (ATT-1304), which applies a static modifier based on calendar day. Capacity-based pricing is dynamic — the modifier fires based on real-time schedule fill rate, not a predetermined schedule rule. Payroll is out of scope for this issue. Because the modifier increases the client-facing service price, the existing commission and bonus mechanisms in payroll will naturally flow through the higher amount where applicable. This feature is related to ATT-1691 (configurable capacity calculation rules), which defines how slot availability is calculated across the company. That issue does not block this one, but the two are closely related and engineering should assess the dependency when picking this up. Product Decisions Locked Pricing modifier only — Capacity-based pricing applies to the client-facing service price only. Payroll is not directly modified by this trigger; inspector commissions and bonuses flow through existing mechanisms on the higher base price. Opt-in per company/service — Feature must be off by default and configurable per company and/or service. Capacity scope — Capacity is measured against total slots across the company for a given day. Tiered thresholds — Companies configure multiple tiers, each with its own capacity threshold percentage and modifier amount (e.g. +$25 at 70% capacity, +$50 at 90%). Not limited to a single threshold. Modifier amount type — Both flat dollar amounts and percentage-based increases are supported, consistent with the existing modifier system. Open Capacity data timing — At the moment a booking is priced, the system needs a real-time slot fill rate for the selected inspection date. Engineering needs to determine where that calculation lives and whether it's available server-side during price calculation without introducing meaningful latency or race conditions. Scope Backend src/routes/payroll.ts — Existing modifier loading pattern; capacity modifier will follow the same modifierSchema approach and needs a new modType for capacityThreshold. Engineering will need to identify where real-time company-wide slot fill rate is computed for a given day and whether that value can be passed into the pricing calculation at booking time. Frontend src/util/functions/schedulingHelpers/calculateServicePrices.ts — Core pricing engine; capacity modifier evaluation needs to be injected here alongside other modifier
Add column configuration to default prebuilt worklists
Objective Let teams choose which columns appear on Attik’s six default (prebuilt) worklists in Tools → Work — the same column flexibility custom worklists already have — without changing what records each list includes. Reduce the workaround of recreating a default queue as a custom worklist just to get the right columns (called out explicitly for Open Quotes, including the on hold status view). Improve scan speed for operational queues (quote follow-up, on-hold triage, unpaid jobs, etc.) by surfacing the fields each team cares about on the row. Background Custom worklists (Mongo worklist documents) support saved column configuration via visibleColumns, riGroupFields, and crGroupFields on worklistSchema, configured in NewWorklistDrawer.tsx and rendered through WorklistResolver.tsx with WorklistTableHeader, SingleJobsBar, SingleQuoteBar, and SingleEventBar. Default prebuilt worklists are not Mongo documents. They are hardcoded in WorklistMenu.tsx (DEFAULT_WORKLISTS: quotes, published, agreements, unconfirmed, paid, invoices) and routed in [[...type]]/page.tsx to dedicated task-check components (QuoteList, IsPublished, AgreementsNeeded, UnconfirmedOrders, JobsNotPaid, InvoicesDue). Those default components do not persist or expose column configuration. Row components (SingleJobsBar, SingleQuoteBar) already accept visibleColumns, but defaults never pass them — they fall back to catalog defaults in columnConfig.ts via getEffectiveVisibleColumns. A WorklistColumnsButton.tsx component exists for in-view column toggling on custom worklists but is not wired to any page today; column changes on custom worklists are currently set at create/edit time in NewWorklistDrawer.tsx. On the June 16 implementation call, users said default quote and on-hold quote views still need column selection similar to custom worklists, while keeping built-in list criteria locked. “On hold” is not a separate default worklist route — it is the Open Quotes worklist (/work/quotes) filtered to status=on_hold via HardcodedFiltersBar / WorklistFiltersBar. ATT-1639 (Done) shipped customizable columns for custom worklists only; this work closes the gap for prebuilt defaults. No attachments, linked documents, or inline media on this ticket. Product Decisions Locked Criteria stay locked — Default worklist inclusion logic (API params, status filters, date windows, grouping rules) must not become user-editable. Only which columns display on each row is in scope. Prefer configuring defaults over duplicating — Teams should be able to tune columns on the built-in lists instead of being forced to recreate equivalent custom worklists. Primary user pain (June 16 call) — Open Quotes (all status tabs, especially on hold) is the highest-priority default surface; other
Planned
373Committed and queued
Allow for copying reports when re-ordering a workorder
Users can already copy (reorder) a job so the new one has the same contacts, property, and services. For BINSERs and revisits they also need the original report and defect items to come over so the new job starts from the same findings. They are asking for the copy/reorder flow to optionally include reports and line items so inspectors or client care don’t have to re-enter them. Why it matters: Speeds up BINSERs and revisits and reduces mistakes when reusing the original report.
Allow Equipment Fields to be variables
Users need to track radon monitors (serial number, calibration date) and see when calibration is due—for example a list or worklist of monitors due within 30 days or email action based on calibration date—so the team avoids missed calibrations.
Inspectors need to see prior jobs at the property, ability to build a BINSER from someone else’s job
Inspectors want to know if the company has already done an inspection at the same property—surfaced on the job or in the app (and optionally as a notification)—so they can pull prior reports and avoid duplicate work. When a BINSER or revisit is assigned to an inspector who didn’t do the original inspection, they need a way to find that original job and build the BINSER from it instead of client care having to do it. This needs to be able to be completed by inspector or office staff and ideally inspectors can do it from the mobile app. Why it matters: Prevents duplicate inspections, speeds up BINSERs and revisits.
Require client contact in online scheduler
Require a client contact in online scheduler flows even when a buyer agent is the person booking. The transcript showed that buyer agents can skip entering the client because the client field is optional in additional contacts, which leaves orders without the expected client contact.
Allow "add all" findings from a certain section for revisits
The goal is to easily carry over previous findings into a new inspection report, specifically for revisits, which would save time and effort.
Region Level Birthdays/Anniversaries Widget
Users want birthdays and anniversaries (for employees and optionally agents) in one place at the regional level so everyone gets the same treatment and no one is missed. Today this is managed per brand or in spreadsheets. They are asking for a regional list or dashboard of upcoming birthdays and anniversaries. Why it matters: Fair, consistent recognition and less manual tracking across brands.
Select Multiple Inspectors In Scheduler
In the scheduler, over a certain size home based on inspector/scheduling settings, make it so more than 1 inspector could be selected.
Run Rules on Job assignments or reschedules
Prevent moving jobs from one inspector to another where the agent doesn’t like the inspector or to an inexperienced inspector by running rules on moves and alerting the user. This would also mean adding inspector dislikes along with the preferences on contact profiles.
Bug: Clearing the Reasoning Description in Online Service Filters does not persist after save
Objective Restore expected behavior when users clear the Reasoning Description (and optionally the Question for Children) on an Online Service Filter option: the saved value should persist as blank after save and refresh. Users expect that clearing an optional field and saving will result in that field remaining empty; currently the previous text reappears. Background In Settings → Scheduling → Online Service Filter, the Edit/Add Filter Option modal includes a "Reasoning Description" textarea. When a user clears this field, saves, and refreshes, the old text is still present; the clear does not persist. The save request succeeds and shows a success toast; the failure is that the backend never receives a value that means "clear this field," so the stored value is left unchanged. The same pattern may affect the optional "Question for Children" field if users clear it—same payload shape applies. Scope Frontend attik-frontend/src/app/tools/settings/scheduling/online-service-filter/ServiceFilterOptionModal.tsx — The modal holds description and question in local state and, on save, sends them as description.trim() || undefined and question.trim() || undefined. When the user clears a field, that becomes undefined and is omitted from the JSON body. The fix must ensure that when the user intends to clear a field, the backend receives an explicit value that means "clear" (e.g. empty string) so the stored value can be updated. The same consideration applies for both create and update payloads. Backend attik-backend/src/routes/serviceFilter.ts — The PUT /options/:id handler updates option.description and option.question only when the corresponding key is present in the request body (if (description !== undefined) option.description = description;). When the frontend omits the key (because it sent undefined), the backend does not change the field. No backend change is strictly required if the frontend starts sending an explicit empty string for "cleared" optional fields; the existing logic will then set the stored value to empty. If the team prefers the backend to treat a missing key as "clear," that would be a separate decision and would need to be applied consistently for optional string fields on this endpoint. attik-backend/src/models/serviceFilterOptionSchema.ts — The description and question fields are optional (required: false). Storing an empty string is valid and already supported. Decision Whether to fix only on the frontend (send empty string when user clears) or also change backend semantics for missing keys is a dev decision; the minimal fix is frontend-only. References Modal and save payload: attik-frontend/src/app/tools/settings/scheduling/online-service-filter/ServiceFilterOptionModal.tsx (handleSave, update and create payloads) Update endpoint: attik-backend/src/routes/serviceFilter.ts (PUT /options/:id) Option model: attik-backend/src/models/serviceFilterOptionSchema.ts
Additional Staff Type
Users want the Office area (under Staff) split into two tabs or categories: one for actual brand specific office staff and one for LaunchPad level team members or team members from other brands who need access. With a third designation this can make is easier for brand level managers to mange their team’s users.
Next up
2Actively being built
Add PostHog funnel and session analytics for report page
Objective Instrument the client report page and related repair-list / quote flows with PostHog so product and engineering can see behavior, drop-off, and whether tutorials help. Support a core funnel: report page → repair list → prepare/submit quote. Enable event tracking, path analysis after report landing, and sampled session replays for qualitative review. Establish a baseline now, knowing events will need manual updates as the report UX evolves. Background There is no PostHog (or similar product analytics) integration in the Attik frontend/backend repos today — this is net-new instrumentation on top of the PostHog platform work. The team wants analytics focused on the report experience, especially the path into repair list and quote actions. Examples of behaviors to capture: Opening or closing tutorials (ReportExplainerModal) Repair-related clicks (sidebar, sections, categorization) Building a repair list without saving Where users abandon the flow Product questions to answer: Are tutorials helping? Where are users dropping off? Is the current flow too complicated? Expectation: instrumentation is somewhat manual and will require follow-up tickets as UI changes; still ship a useful v1 now. Scope Frontend — Report landing (client portal) Primary route: attik-frontend/src/app/client/reports/[slug]/page.tsx — report view with ReportSectionsWithAIAssist, ReportSidebar, ClientReportWrapper, lock gating via LockedReportScreen / resolveReportPageData. Repair list entry from report UI: ReportSections.tsx (repair-list feature/permission gates), RepairListSidebar.tsx (navigation to /reports/{slug}/repair-list/{id}). Frontend — Repair list and quote flow Repair list pages: attik-frontend/src/app/client/reports/[slug]/repair-list/[id]/ (page.tsx, layout.tsx, RepairListProvider.tsx, RepairListHeader.tsx). Categorization / request flow: CategorizationPageClient.tsx, repair-list/[id]/request/page.tsx, request/[categoryId]/page.tsx, RequestFlowDrawer.tsx, MultiCategoryRequestClient.tsx (quote submit / “Did you submit the quote request?” steps). Save / share actions: attik-frontend/src/app/client/reports/actions/saveRepairList.ts, ShareRepairListModal.tsx (request-quote-share, request-list-send API calls). Frontend — Tutorials / explainer ReportExplainerModal.tsx — mounted from RepairListProvider.tsx; gated by jobPortalFeatures and role permissions including repair-list. Frontend — Analytics integration (to add) Decision needed: PostHog SDK placement (e.g. client portal root layout vs report-only subtree) and identity properties (company, inspection/report id, contact role, access_token portal session). Decision needed: Event naming convention and whether to use PostHog autocapture vs explicit capture() only on client components (many report surfaces are client components or dynamic imports with ssr: false). Funnel steps shou
Monorepo
In Progress
1Actively being built
Code Review
3Recently shipped
Client Care Dashboard: Close attribution toggle (Scheduled By vs Quote Creator)
Objective Add a toggle on the Employee Performance scheduler stats table (Client Care Dashboard) so users can switch close-rate attribution between: Scheduled By (default / current behavior) — all inspection-backed close components are credited to inspection.scheduledBy. Quote Creator — for inspections linked to a quote (Quote._finalInspectionId), credit close components to quote.createdBy; if no linked quote, fall back to scheduledBy. This lets CC leadership compare “who booked the job” vs “who originated the quote/lead” without changing revenue, upsell, cancel, or other non-close columns unless product decides otherwise. Current behavior (as of investigation) Backend: GET /cc-dashboard/scheduler-metrics in apps/backend/src/routes/ccDashboard.ts Main inspection aggregation groups by (scheduledBy, _companyId) for revenue, created, confirmed, upsell, cancel, add-on, etc. Open quotes in the close-rate denominator already use quote.createdBy → user mapping (Steps 5–5b). Result: hybrid attribution today — confirmed inspections → scheduledBy; orphan open quotes → createdBy. Frontend: EmployeeTable.tsx renders the table inside DashboardBlock; filters live in the controller slot (Online Scheduler checkbox + scheduler multi-select). Data fetched from CCDashboardClient.tsx via cc-dashboard/scheduler-metrics. Data model: cc_inspection_base view exposes scheduledBy but not quote linkage (createCcDashboardViews.ts). Quote link: Quote._finalInspectionId ↔ inspection _id; quote originator: Quote.createdBy. Proposed implementation 1. API — new query param Add closeAttribution=scheduledBy|quoteCreator (default scheduledBy) to GET /cc-dashboard/scheduler-metrics. When closeAttribution=quoteCreator, re-attribute close-related fields only: closeRate ordersCreated (denominator basis) ordersConfirmed (numerator) Team average + per-company breakdown for those fields Keep on scheduledBy (unless product expands scope): revenue, averageFee, upsellRate, oneCallCloseRate, selectedAddOnRate, cancel columns, # Core Confirmed. 2. Backend aggregation strategy Recommended approach (no view migration required): Keep existing scheduledBy aggregation for non-close metrics. Add a parallel close-only aggregation when closeAttribution=quoteCreator: $match same date/status filters as main query. $lookup quotes on { localField: '_id', foreignField: '_finalInspectionId', as: 'quote' }. $addFields: closeAttributionEmp: { $ifNull: [{ $arrayElemAt: ['$quote.createdBy', 0] }, '$scheduledBy'] }. $group by (closeAttributionEmp, _companyId) summing only created (non-cancelled), confirmed, and cancelled counts needed for close math. Merge close buckets into existing mergedMap by resolved userId
Bug: Required Info fields export empty from Reports Hub
Objective Fix Required Info field columns selected in Reports Hub exporting with no data in the downloaded file. Ensure requiredInfoValues are correctly projected, resolved, and written to export rows for all export entity types that support Required Info columns. Background Catherine Lemoine (SE/Residential) reported that selecting Required Info fields in Reports Hub and running an export produces a file where those columns are blank or missing. The export completes without error — the fields appear to be recognized by the UI but arrive empty in the CSV/XLSX output. The export pipeline in attik-backend has a distinct path for Required Info columns: they are identified by a REQUIRED_INFO_VALUE_PREFIX on the column key, the backend adds requiredInfoValues to the MongoDB projection, and transformData resolves each cell by matching _requiredInfoId within the document's requiredInfoValues array. Entity scope gap (hypothesis A): buildSingleEntityProjection only adds requiredInfoValues to the projection when targetEntity is inspections or quotes. If the export is run on any other entity, the field is silently absent from all fetched documents, producing empty cells with no error. ID match failure (hypothesis B): If the column key prefix-stripping or _requiredInfoId comparison fails (e.g. ObjectId string vs. string type mismatch), entry is undefined and rawValue defaults to ''. Missing requiredInfoValues on records (hypothesis C): Jobs predating when Required Info was introduced may not have the array populated. The export entity Catherine was running is unknown — this is an open repro variable. Product Decisions Locked Required Info columns are currently supported only on inspections and quotes entity exports — this is intentional per buildSingleEntityProjection. Open Entity scope — If Catherine was running a non-inspections/quotes export, is the correct fix to silently drop RI columns with a UI warning, block RI column selection for unsupported entities, or expand RI support to additional entities? Repro confirmation — Engineering needs to confirm which export entity and which specific Required Info fields Catherine had selected before fixing. Scope Backend (attik-backend / attik monorepo) src/routes/dataExport.ts (apps/backend/src/routes/dataExport.ts) — route handler; buildSingleEntityProjection controls whether requiredInfoValues is added to the projection; getSingleEntityHeaderData calls getRequiredInfoLabels src/util/functions/dataExports/documentResolution.ts — transformData function; the Required Info cell resolution loop at column.startsWith(REQUIRED_INFO_VALUE_PREFIX); the _requiredInfoId match against doc.requiredInfoValues src/util/functions/dataExports/filterBuilders.ts — defines REQUIRED_INFO_VALUE_PREFIX and SPECIAL_RULE_PATHS Frontend (attik-frontend / attik monorepo) src/app/tools/data-exports/CreateReportForm.tsx (apps/frontend/src/app/tool
Imported ISN contacts have contact preferences disabled
Contacts imported from ISN appear to have both email and SMS contact preferences toggled off, which is preventing agents from receiving reports. The immediate workaround is to update them manually, but the historical import likely needs a code fix and there may also need to be a programmatic way to enable these preferences.
Done
538Recently shipped
Foundation in Address type
Sample Reports
Account executives use sample reports (e.g. for USPs or demos) and today have to create fake orders and fill full reports to get one. They want a dedicated place—a sample report library—where they can view or download sample reports without creating a test order every time they need a sample report
Inspector Capacity Widget
The widget currently treats any time slot blocked by an event (rather than an inspection) as “booked,” so capacity is not what the brands are expecting; it should count only scheduled inspections (orders) as booked so schedulers see accurate capacity.
Solar Inspections - API for Reports
Users need when a solar service is added for a job to automatically create the order in their solar partner’s system, and when the report is published for that job to trigger the appropriate response to the client. They are asking for this connection so the solar workflow is seamless in Attik. Why it matters: Required for brands that offer solar and for a smooth client experience.
Change the quote status 'Rejected' to something friendlier
When a quote is rejected (e.g. after several call attempts with no answer), the client sees “rejected” in the portal and often thinks they can’t book anymore. Users want a friendlier status that can better support an automated reminder email so the client can come back and reschedule using the same quote—ideally with a link that takes them to a prepopulated booking flow instead of starting over. Why it matters: Reduces confusion and lost bookings when clients just needed more time.
Inspection and revenue counts by contact roles
Schedulers and growth need to see who their top agents are by revenue and by inspection count—for VIP handling, follow-ups, and reporting. They also rely on automatic tags based on rules (e.g. inspections in the last 365 days, top producer by money and count) for slot holds, new-agent and lost-agent outreach, and lists. Users are asking for both: visible agent stats (revenue and count) and tag automations that match the rules they use today so they don’t lose that workflow in Attik. Why it matters: Drives how schedulers prioritize calls and how growth runs campaigns and thank-yous. Can this be done through a HubSpot integration, where Hubspot does the calculating and adds a tag to the contact profile?
Treat primary and CC email as the same contact so we don’t get duplicates
Users have reported that when an agent (or contact) in Spectora uses one email as primary and another as CC—or the other way around—Attik creates two separate contact records for the same person. That leads to duplicate contacts, split history, and confusion. They want the system to recognize primary and CC as the same identity so one contact is kept and duplicates are not created. Why it matters: Cleaner contact data and correct attribution for agents and clients.
Referral Source
Need to take whats in Spectora or push to spectora
Discount Feature
Major Modifier Updates
Use Conditional component in place of the compare modifier. Run script to convert these. This will add multi-select in a single condition. Make it so inspectors can be selected. Inspectors have more or less time etc. Modifier that adjust the number of equipment items needed. Modifiers grouped and reordered. Miles from company address modifier needed