- Add QBO OAuth2 client with token refresh (lib/services/qbo-client.ts) - Add QBO sync service for invoices, payments, deposits, purchases, journal entries, reports (lib/services/qbo-sync-service.ts) - Add QBO types (lib/types/qbo.ts) - Add API routes: /api/qbo/auth, /api/qbo/sync, /api/qbo/disconnect - Add /admin/qbo status and sync management page - Add legal pages: /legal/eula, /legal/privacy (Intuit app assessment) - Add QBO nav link under Admin - Fix reports: remove invalid summarize_column_by, add accounting_method from Preferences API, add showrows=all&showcols=all - Add CashFlow report type alongside P&L and BalanceSheet - Add NoReportData check to skip empty report months - Add intuit_tid capture in error messages - Add redirect: follow for cluster routing - Migration 051: qbo_tokens, qbo_invoices, qbo_payments, qbo_deposits, qbo_transactions, qbo_reports tables Also includes earlier work: - Ping flap suppression pipeline step - Ticket digest reports with LLM analysis - Zabbix WAN monitor and gap analysis - Kiosk is_deleted filter fixes - Datto RMM ping target enrichment - Entity sync soft-delete detection
21 KiB
Lifecycle Insights Feature Replacement — Pulse Design Doc
Purpose: Document how Lifecycle Insights (LCI) works across the four target features — Warranty, Assessments, Budget Forecast, and QBR/Business Reviews — so we can build equivalent functionality in Pulse, using Autotask (and Datto RMM) as the primary data source.
Reference client: Hynes Industries (321 assets, multiple locations) Source tool: app.lifecycleinsights.io Date captured: 2026-03-16
1. Asset List & Warranty
What LCI Shows
Summary banner (top of Asset List page):
- Total Assets: 321
- EOL Status breakdown:
- 104 Good Standing
- 60 Approaching EOL
- 6 Past EOL
- 151 Unknown
- OS Status breakdown:
- 177 Supported
- 27 Not Supported
- 117 Unknown
- Warranty Status breakdown:
- 91 Under Warranty
- 20 Approaching Warranty Expiration (within ~90 days)
- 97 Expired Warranty
- 113 Unknown
Per-asset table columns:
| Column | Example |
|---|---|
| Device Name | YNGHYNLT030 |
| Status | Active / Retired / Hidden |
| Last User (RMM) | HYNES\shopfloor |
| Location | Youngstown (Main) / Painesville (Branch) / Kokomo (Branch) |
| Type | Workstation / Hypervisor Host / Network Device |
| Make | Dell / Lenovo |
| Model | Latitude 5591 / ThinkSystem SR630 |
| Serial Number | 2KNXWT2 |
| Operating System | Microsoft Windows 10 Pro 10.0.19044 |
| Purchase Date | 04-23-2019 |
| Warranty Expiration | 04-24-2023 |
| End of Life | 04-23-2024 |
| Replacement Cost | $3,000 |
| Age (years) | 6.9 |
Table features:
- Sortable columns (click header)
- Filter buttons: Show Only NON Hidden / Duplicate / Hidden / All Assets
- Duplicate detection badge
- Pagination (321 rows, 25/50/75/100/200 per page)
- Search box
Warranty status logic (inferred):
- Under Warranty: warranty expiration date > today
- Approaching: warranty expiration within ~90 days
- Expired: warranty expiration date < today
- Unknown: no warranty expiration date on record
EOL logic (inferred):
- EOL date is OS/hardware specific (LCI maintains a lookup table — shown on dashboard)
- Good Standing: EOL date > today + threshold (~1 year)
- Approaching EOL: EOL date within ~1 year
- Past EOL: EOL date < today
- Unknown: no EOL date derivable
Data Sources
- Device inventory: comes from RMM (Datto RMM in Hynes's case) — device name, make, model, serial, OS, last user, location
- Purchase/warranty dates: can come from RMM or be manually entered
- EOL dates: LCI maintains a global EOL lookup table by OS/software version (visible on dashboard — e.g., "Windows 10 Pro: 10-14-2025")
- Replacement cost: manually set per asset type, or inherited from category defaults
- Locations: mapped from RMM site groupings
Pulse Implementation Notes
Data model (new migration needed):
-- assets table (core)
asset_id, autotask_id, datto_rmm_uid,
device_name, status, last_user,
location_id, asset_type,
make, model, serial_number,
os_name, os_version,
purchase_date, warranty_expiry_date, eol_date,
replacement_cost, age_years,
company_id (FK → companies),
is_hidden, is_duplicate,
created_at, updated_at, last_synced_at
-- eol_reference table (global)
software_name, eol_date
-- asset_locations table
location_id, company_id, location_name
Warranty status computed field:
function warrantyStatus(expiryDate: Date | null): 'under' | 'approaching' | 'expired' | 'unknown' {
if (!expiryDate) return 'unknown';
const daysUntil = differenceInDays(expiryDate, new Date());
if (daysUntil < 0) return 'expired';
if (daysUntil <= 90) return 'approaching';
return 'under';
}
Autotask as source: Autotask has a Configuration Items module that maps to devices. Datto RMM syncs device details into Autotask CIs. Fields available via Autotask API: warrantyExpirationDate, purchaseOrderNumber, purchaseDate, installDate, serialNumber, referenceNumber, manufacturer, product (model), location, deviceType.
2. Budget Forecast
What LCI Shows
Purpose: Project technology spend for the next 6–8 quarters, broken into categories.
Views:
- Detail View — collapsible sections per time period, category breakdown
- Spreadsheet View — bar chart + grid with rows per category, columns per quarter
- Budget Board — (not fully captured; likely a Kanban/card layout)
Filters available:
- Location (site filter)
- Asset Type
- Asset Status
- "Generate Budget Forecast as of" date (defaults to today; can run future projections)
Time buckets:
- Overdue (past-due items)
- Recommendations Not Scheduled (approved recommendations with no date)
- Then rolling quarters: e.g., Jan-Mar 2026, Apr-Jun 2026, Jul-Sep 2026, etc. (6–8 quarters shown)
Categories:
| Category | What it represents |
|---|---|
| Workstation | Assets reaching EOL scheduled for replacement |
| Hypervisor Host | Server/hypervisor assets reaching EOL |
| Network Device | Network hardware replacements |
| Contracts/Subscriptions | Contract renewals (from Autotask contracts) |
| Recommendations | Open recommendations with cost + target date |
Hynes Industries example data (Spreadsheet View):
| Category | Overdue | Not Sched | Apr-Jun 26 | Jul-Sep 26 | Oct-Dec 26 | Jan-Mar 27 | Apr-Jun 27 | Total |
|---|---|---|---|---|---|---|---|---|
| Hypervisor Host | $22,500 | — | — | — | — | — | — | $22,500 |
| Workstation | — | — | — | — | — | $18,000 | $21,000 | $39,000 |
| Contracts/Subscriptions | — | — | $37,231 | $43,590 | $164,414 | $27,737 | $37,231 | $313,549 |
| Recommendations | $254,754 | $107,000 | $31,245 | $15,045 | $29,795 | — | — | $437,839 |
| TOTAL | $277,254 | $107,000 | $68,476 | $58,635 | $194,209 | $45,737 | $58,231 | $812,888 |
How Each Category is Populated
Workstation / Hypervisor / Network Device:
- Source: assets with EOL date falling within the quarter
- Cost:
replacement_costper asset - Scheduled quarter = quarter containing the asset's
eol_date
Contracts/Subscriptions:
- Source: Autotask Contracts with
end_dateor renewal dates - Cost: contract value
- Quarter = quarter of contract end/renewal date
Recommendations:
- Source: open Recommendations (see Recommendations module)
- Cost:
estimated_coston the recommendation - Quarter = recommendation's
planned_date(or "Not Scheduled" if no date) - "Overdue" = planned_date < today
Pulse Implementation Notes
Budget Forecast is computed at query time — no separate stored table needed beyond assets + contracts + recommendations with dates and costs.
-- Pseudo-query for budget forecast
SELECT
quarter_bucket,
category,
COUNT(*) AS quantity,
SUM(cost) AS budget_cost
FROM (
-- EOL assets
SELECT date_trunc('quarter', eol_date) AS quarter_bucket,
asset_type AS category,
replacement_cost AS cost
FROM assets WHERE company_id = $1 AND eol_date IS NOT NULL AND status = 'active'
UNION ALL
-- Contracts
SELECT date_trunc('quarter', end_date), 'Contracts/Subscriptions', contract_value
FROM contracts WHERE company_id = $1
UNION ALL
-- Recommendations
SELECT date_trunc('quarter', COALESCE(planned_date, NULL)), 'Recommendations', estimated_cost
FROM recommendations WHERE company_id = $1 AND status NOT IN ('Completed', 'Declined')
) combined
GROUP BY quarter_bucket, category
ORDER BY quarter_bucket;
API route: GET /api/budget-forecast/[companyId]?asOf=DATE&location=X&assetType=Y
3. Assessments
What LCI Shows
Structure (from Assessments Menu page):
- Assessment Events — each time you run an assessment for a client = one "event"
- Assessment Templates — define what you're measuring (items + categories)
- Assessment Items — individual questions/checklist items
- Event Types — classify events (e.g., baseline, annual review, gap assessment)
- Analytics by Customer — score history, trends, category breakdowns
- Analytics All Customers — cross-client comparison
Wulf Consulting uses: "Wulf Consulting: Default Assessment" template
Hynes Industries history (from dashboard):
- Latest: completed 01-29-2025 (411 days ago) by Jake Hammel
- Score type: "baseline assessment with a score of --" (baseline = no numeric score, just captures state)
- Assessment Event Score History chart shows one data point at 2025-01
Data Quality Scores (dashboard, related to assessments):
- EOL: 53/100
- Warranty: 65/100
- Make: 92/100
- Serial: 83/100
- Model: 95/100
- Overall: 67/100
Assessment Data Model (inferred)
AssessmentTemplate
├── id, name, company_id (MSP-level)
├── items[] → AssessmentItem
│ ├── id, category, question_text, weight, scoring_type
│ └── possible_scores: [0,1,2,3,4,5] or pass/fail
└── event_type_config
AssessmentEvent
├── id, template_id, client_id (company)
├── event_type (baseline / annual / gap)
├── completed_date, completed_by (technician)
├── overall_score (numeric or null for baseline)
└── responses[] → AssessmentResponse
├── item_id, score, notes
└── linked_recommendation_id (optional)
AssessmentEventScore (for history/chart)
├── event_id, category, score, max_score
Key behaviors:
- Templates are MSP-defined (not per-client)
- One client can have many events over time (score history chart)
- Assessment items can generate Recommendations (linked)
- Scores tracked by category (enables radar/bar chart per category)
- "Baseline" events don't produce a numeric score — they capture current state only
Pulse Implementation Notes
Migration: Create assessment_templates, assessment_items, assessment_events, assessment_responses tables.
Admin UI: Template builder (drag-drop items into categories), Event runner (per-client form), Analytics view (score history chart per client).
Autotask link: Assessment items that result in action → create Autotask ticket or recommendation.
4. Business Reviews / QBR
What LCI Shows — FULLY CAPTURED
Page: Business Review Meetings for Hynes Industries
Meeting table columns:
| Column | Values |
|---|---|
| Meeting Date | e.g., 03-03-2026 |
| Meeting Type | Business Review / Virtual Meeting / (blank) |
| Primary Contact | Ryan Day |
| Account Manager | Jake Hammel |
| Status | Completed / Scheduled / Auto-Scheduled / Preparation / Skipped/Cxl |
| Notes | Rich text notes (Pre-Meeting / During Meeting / Post-Meeting sections) |
| Recommendations | (linked recommendations column) |
| Reports / Artifacts | (PDF download column) |
| Delete | delete action |
Status types explained:
- Completed — meeting happened and notes are finalized
- Scheduled — manually scheduled upcoming meeting
- Auto-Scheduled — system automatically created this slot based on cadence
- Preparation — notes are being drafted before meeting
- Skipped/Cxl — meeting was skipped or cancelled
Cadence for Hynes: Bi-weekly (every 2 weeks), Ryan Day as primary contact, Jake Hammel as account manager. Running since September 2023.
Notes structure (rich text, per meeting):
Pre-Meeting:
[prep notes, project status, talking points]
Meeting / During Meeting:
[live notes taken during call]
Post-Meeting:
[follow-up actions, decisions made]
Real example notes (from Hynes 09-19-2023):
"Follow up with Ryan on 2024 CAPX Budget... Updated Ryan that moving forward new workstations won't have the BitLocker Password Prompt... Amy Young - Executive Admin - Get her to view details on the Hynes conference room calendars..."
Scale: Hynes has ~56 business review records from 09-2023 to 03-2026 (bi-weekly cadence = ~2/month × 30 months). The "47 overdue" from the dashboard is because many Auto-Scheduled meetings were never formally completed.
Filters on page:
- "Show Meetings with Date in Past" toggle
Actions:
- ADD NEW — create a new meeting record
- Each row has: delete, reports/artifacts (PDF generation)
Business Reviews Data Model (confirmed)
business_reviews
id, company_id,
meeting_date DATE,
meeting_type VARCHAR, -- 'Business Review', 'Virtual Meeting', etc.
primary_contact_id INT, -- FK → contacts
account_manager_id INT, -- FK → users (MSP staff)
status VARCHAR, -- Completed/Scheduled/Auto-Scheduled/Preparation/Skipped-Cxl
notes_pre_meeting TEXT,
notes_during_meeting TEXT,
notes_post_meeting TEXT,
created_at, updated_at
business_review_artifacts -- linked PDFs / reports
id, business_review_id, artifact_type, file_url, created_at
business_review_recommendations -- join table
business_review_id, recommendation_id
Key behaviors:
- System auto-generates meeting slots based on cadence (configurable per client)
- Each meeting has pre/during/post note sections
- Meetings can link to specific recommendations (show progress at meeting)
- PDF reports can be attached or generated per meeting
- "47 overdue" = count of Auto-Scheduled meetings with date in past and no Completed status
- Agenda links (from dashboard widget) are separate from meeting notes — persistent links per client
Pulse Implementation Notes
QBR = structured meeting log with recurring cadence + notes + linked recommendations.
- Auto-generate meeting slots via a cron job based on
meeting_cadence_daysper company - The "Overdue" count =
SELECT COUNT(*) FROM business_reviews WHERE company_id = $1 AND meeting_date < NOW() AND status != 'Completed' - PDF generation: render current EOL health + budget + recommendations at time of export
- Agenda links widget: simple per-client URL bookmarks (
business_review_linkstable)
5. Recommendations — FULLY CAPTURED
What LCI Shows
Page: Recommendations for Hynes Industries — 26 total records
Table columns:
| Column | Notes |
|---|---|
| Name | Free-text project/recommendation name |
| Location | Youngstown (Main) / Painesville (Branch) / Kokomo (Branch) / blank (all sites) |
| Start | Target start date (optional) |
| End | Target end date (optional) |
| Status | New / Proposed / Planned / In Progress / On Hold / Completed / Declined |
| Tags | (free-form tags) |
| Priority | Low / Medium / High |
| Description | Rich text description of scope |
| Labor Cost | $ |
| Materials Cost | $ |
| Replacement Cost | $ |
| Recurring Cost | Monthly $X → shown as "Monthly / $X" |
| Total Cost | Sum of all cost components |
| Delete | delete action |
Filters:
- Search box
- Include Completed toggle
- Include Declined toggle
- "SHOW AUTOTASK OPPS" button — links to Autotask opportunities
Views: List (default) — table view
Hynes Industries full recommendation list (real data):
| Name | Status | Priority | Total Cost |
|---|---|---|---|
| 2024 - Fish Bowl Renovation | Completed | Medium | $11,000 |
| 2024 - Pepwave Licensing Renewal | Completed | Medium | $0 |
| 2024 - Planned Multiple Switch Lifecycle | Completed | Medium | $24,000 |
| 2024 - Planned Workstation Lifecycle | In Progress | Medium | $9,800 |
| 2024 - VLAN Cleanup | Completed | Medium | $10,000 |
| 2024: O365 Backup | Completed | Medium | $3,560 (monthly) |
| 2024: Password Management | Completed | Medium | $8,340 (monthly) |
| 2024: SEIM Tool | Completed | High | $20,020 (monthly) |
| 2024: ZTNA - Phase 1 | Completed | High | $16,350 (monthly) |
| 2025 - Painesville Zoom Room | Declined | Low | $11,000 |
| 2025 - Planned Infrastructure Lifecycle | Completed | High | $6,830 (monthly) |
| 2025 - Planned Workstation Lifecycle | Completed | High | $3,000 |
| 2025 - PV Planned Multiple Switch Lifecycle | In Progress | High | $5,000 |
| 2025 - YNG Planned Multiple Switch Lifecycle | Completed | High | $2,900 |
| 2025: Windows 10 to 11 Upgrade | Completed | High | $40,400 |
| 2026 - Exterior Cameras - Partial Build | On Hold | Low | $57,000 |
| 2026 - PEN Test | On Hold | High | $20,000 |
| 2026 - Pepwave Licensing Renewal | Planned | High | $350 |
| 2026 - Planned Host/SAN Lifecycle | Proposed | High | $120,000 |
| 2026 - Planned Multiple Switch Lifecycle | Proposed | High | $31,000 |
| 2026 - Planned Workstation Budget | Proposed | Medium | $86,600 |
| 2026 - Zebra Printers Lifecycle | Proposed | High | $80,534 (monthly) |
| 2026: ZTNA: Phase 2 | On Hold | High | $30,000 |
| Auvik SaaS | Declined | Medium | $1,760 (monthly) |
| Physical Switch & IDF Cleanup | On Hold | Medium | $0 |
| Receptionist Hardware | Completed | Medium | $1,800 |
How recommendations feed Budget Forecast:
- Status
Proposed,Planned,In Progress,On Hold→ included in budget forecast startdate (orenddate) → determines which quarter they appear in- No date → "Not Scheduled" bucket
- Past date → "Overdue" bucket
Completed/Declined→ excluded from forecast
Recommendations Data Model
recommendations
id, company_id,
name VARCHAR,
location_id INT (nullable),
start_date DATE (nullable),
end_date DATE (nullable),
status VARCHAR, -- New/Proposed/Planned/In Progress/On Hold/Completed/Declined
tags TEXT[],
priority VARCHAR, -- Low/Medium/High
description TEXT,
labor_cost DECIMAL,
materials_cost DECIMAL,
replacement_cost DECIMAL,
recurring_cost DECIMAL, -- monthly amount
recurring_period VARCHAR, -- 'Monthly'
total_cost DECIMAL, -- computed or stored
autotask_opportunity_id INT (nullable),
created_at, updated_at
recommendation_assets -- assets linked to this recommendation
recommendation_id, asset_id
Pulse Implementation Notes
- Recommendations are Pulse-native (not pulled from Autotask) but can push/link to Autotask Opportunities
total_cost = labor_cost + materials_cost + replacement_cost + (recurring_cost × months_in_period)- Budget Forecast query filters:
status NOT IN ('Completed', 'Declined') - Dashboard widget shows count + total cost grouped by status
- The
SHOW AUTOTASK OPPSbutton = link to Autotask opportunities filtered to this company
6. Dashboard Summary
The LCI dashboard aggregates all four features for a client into one view:
| Widget | Data |
|---|---|
| CSAT Score | From Crewhu integration (external) |
| Budget Forecast chart | 6-quarter bar chart |
| EOL Health | % OK / Past EOL / Near EOL donut |
| Data Quality Score | Per-field completeness scores |
| Microsoft License Summary | From M365 / CSP data |
| EOL Replacement Cost | Bar chart by status |
| Latest Assessment | Score + date |
| Recommendation Status | Count + cost per status |
| Assessment Score History | Line/bar chart |
| Software/OS EOL Table | Global reference table |
| Tech Debt by Asset Type | Stacked bar |
| Missing Data Fields | Counts of missing fields |
| Total Technology Debt | $ value of overdue/EOL assets |
| Headlines | Alerts: overdue contracts, overdue QBRs, latest assessment age |
6. Integrations & Data Flow
Datto RMM ──────────────────┐
▼
Autotask CIs ───────────► LCI Asset Sync
│
Autotask Contracts ──────────┤
│
Autotask Recommendations ────┤
(or LCI-native) │
▼
LCI PostgreSQL
│
┌────────┼────────┐
▼ ▼ ▼
Budget Assessments QBR
Forecast Events Reports
For Pulse:
- Assets: Autotask Configuration Items API →
assetstable - Contracts: Autotask Contracts API →
contractstable (already partially done) - Recommendations: Either Autotask Opportunities/Quotes or Pulse-native table
- CSAT: Not currently integrated (Crewhoo is external) — can be skipped initially
- EOL Reference: Static lookup table (maintained manually or seeded from LCI's public list)
- Warranty dates: Come from Autotask CI
warrantyExpirationDatefield
7. Build Priority Order
- Asset List + Warranty — highest value, data already in Autotask CIs
- Budget Forecast — derived from assets + contracts (both already syncing)
- QBR / Business Reviews — report generation + scheduling
- Assessments — most complex, requires template builder + event runner
9. Pages Still to Explore
Fully captured: Asset List, Budget Forecast (detail + spreadsheet), Recommendations (all 26), Business Reviews (all ~56 meetings), Dashboard.
Still needed for complete picture:
app.lifecycleinsights.io/assessmentevents— see an actual scored assessment event (items, categories, scores per item). The Hynes event from 01-29-2025 was a baseline with no numeric score.app.lifecycleinsights.io/assessmentanalytics— analytics/trends view per clientapp.lifecycleinsights.io/managetemplates— what a template looks like with items + categories- MDS client — verify data model generalizes (different asset mix, different contact structure)
These can be explored via Claude_in_Chrome (already connected to local Chrome where you're logged in) without needing another MFA login.