Project Explorer
Project Explorer is PSI’s comprehensive machine project tracking and customer analytics platform. It visualizes 25+ years of project data (2000-present) across 3,084 projects, providing customer lifetime value modeling, fleet health tracking, parts analysis, and lead time benchmarking.
Quick Links
| Resource | URL |
|---|---|
| Production | https://projects.progressivesurface.com |
| Repository | ProgressiveSurface/project-explorer |
| Azure App | ps-project-explorer.azurewebsites.net |
| Portal Entry | Listed on PSI Portal |
Features
Views & Dashboards
Project Explorer provides 11 distinct views accessible from the view switcher toolbar:
| View | Purpose |
|---|---|
| Timeline | Horizontal year-by-year timeline of all projects with clickable cards |
| Table | Searchable, sortable data table (TanStack Table) with virtual scrolling |
| Grid | Card grid layout with machine type grouping |
| Equipment | Fleet view — every physical machine by serial number, with retrofit chain |
| Warranty | Active/expiring/expired warranty tracking with calendar timeline |
| Customer 360 | Parent company hierarchy, revenue breakdown, LTV, fleet health scores |
| Obsolescence | Component age tracking, EOL reference data, retrofit recommendations |
| Parts | Part frequency analysis, BOM comparison (Jaccard similarity), reuse scoring |
| Lead Time | Year trends, budget variance analysis, machine type stats, labor benchmarks |
| Outreach | Prioritized customer action queue with CSV export for sales/service outreach |
| Sales | Quote pipeline, conversion analytics, customer deep-dive, orders, trends |
UI Layout
The app uses a CSS Grid shell with three rows for maximum content area:
+------------------------------------------------------------------+
| HEADER (48px) compact: logo | search | alert-bell | stats | user |
+------------------------------------------------------------------+
| TOOLBAR (40px) view-switcher | year-range | type-filter |
+------------------------------------------------------------------+
| CONTENT (1fr = ~992px on 1080p) |
| Active view fills this with h-full flex flex-col |
+------------------------------------------------------------------+
- 88px chrome (header + toolbar) leaves ~992px content on 1080p
- All views use
h-full flex flex-colwithflex-shrink-0headers andflex-1 overflow-autocontent - No
calc(100vh-Npx)anywhere — scroll containers are flex-driven - Alert Dashboard renders as a right-side slide-over panel (not stacked)
- Project Detail Panel renders as a full-height overlay with backdrop
Customer 360 / Lifetime Value
The Customer 360 view provides deep analytics per customer:
- Parent Company Grouping: Automatically groups account locations into parent companies (e.g., all Pratt & Whitney sites)
- Revenue Breakdown: Original machines, retrofits, and Total Invoiced (from invoiceline.csv — all billing including machines, parts, and service)
- Composite Tier Scoring: Strategic / Core / Developing / Transactional based on four weighted factors
- Fleet Health Score: 0-100 score based on component age across all customer machines. Upstream component/PRIMS inputs are produced by the psi-data-pipeline and published into app-facing artifacts; scraper-owned source tables now live in
psi_ingest.dbbefore being copied into rebuildable analytics outputs. - Engagement Timeline: Order history, retrofits, warranty events
- LTV Projection: Forward-looking revenue estimate based on fleet age, parts consumption trends, and retrofit probability
- Intermediary Attribution: Revenue from distributor accounts (e.g., Ferguson, Vallen) is split and attributed to end-customer accounts
Composite Scoring System
Customer tiers are determined by a 4-factor weighted composite score:
| Factor | Weight | What It Measures |
|---|---|---|
| Revenue | 25% | Logarithmic scale of total business value (max baseline $50M) |
| Recency | 25% | Exponential decay from most recent engagement (project, invoice, or quote) |
| Aftermarket | 30% | Invoice volume per machine, order frequency, revenue trend (3yr vs prior 3yr) |
| Fleet | 20% | Machine count (log), retrofit activity, fleet health score |
Tier thresholds: Strategic >= 70, Core >= 45, Developing >= 20, Transactional < 20
Key design decisions:
- Revenue uses
max(totalOrderValue, totalInvoiced)— not summed, since they overlap - Aftermarket trend defaults to 0 with no invoice history (no free points)
- Fleet health defaults to 0 when no machines on file (no free points)
getQuoteRevenueForAccount()returns data whentotalInvoiced > 0even ifquoteCount === 0(many accounts have invoices without quotes)
Quote Revenue Integration
Real quote-to-order data from AFTEC, processed via scripts/process-quotes.mjs:
- 22,364 spare parts quotes from
spquote.csv(dgwReportFiles) - 163,306 invoice line items from
invoiceline.csvspanning 2005-2026 ($859M total invoiced) - 54,970 quote line items from
spquoteline.csv(part-level detail on open/won quotes) - 3,492 won quotes definitively linked via Invoice → Quote No field
- SPN attribution via
SPN.QUOTES.1287export (machine-to-quote linkage) - Seller name resolution via
EMPLOYEE.PUBLIC.1287(human-readable names instead of employee IDs) - Open sales orders via
VB_OPENORDLISTsubroutine call - Quote-to-SO linkage via
SOREQtable export (bidirectional quote-order mapping) - AFTEC crosswalk: CUSTOMER.1287 → ACCOUNT.1287 mapping with fuzzy name-matching fallback
- Attribution hierarchy: Machine (SPN) → Site (ShipTo) → Customer → Unmatched
- Quote intelligence: Duplicate/revision detection, behavior flags (part mining, declining engagement, growth)
Intermediary / Distributor Attribution
Some customers order through distributors (e.g., Ferguson, Vallen, Fastenal). The intermediary_mapping.json config defines 15 intermediary accounts with their end-customer mappings. Revenue from intermediary invoices is split proportionally across mapped end-customer accounts:
- Intermediary invoiced revenue is divided equally among mapped end-customers
- Each end-customer’s record is augmented with
attributedRevenueandintermediarySources - The Customer 360 view shows “attributed” amounts in blue with source distributor names
- 15 intermediaries currently mapped (Ferguson → Standard Aero/GE/Howmet, Vallen → Pratt/Honeywell, etc.)
Alert Dashboard
Slide-over notification panel (not stacked in page flow) with four alert categories:
- Warranty: Expiring warranties (30/60/90 day windows)
- Obsolescence: Machines approaching component end-of-life
- Engagement: Customers with declining activity or long gaps
- Retrofit: Machines with aging/critical components that need upgrading
Alert bell in header shows count badge with pulse animation for critical alerts. Panel slides in from the right without pushing content.
Parts Analysis
Five sub-tabs within the Parts view:
- Part Frequency: How often each part appears across all BOMs, with reuse categorization (Standard / Common / Rare)
- BOM Comparison: Cross-project Jaccard similarity to find structurally similar machines
- Duplicates: Duplicate part detection across projects
- Kits: Kit analysis for common part groupings
- Catalog: Searchable part catalog across all BOMs
Lead Time Analytics
Four sub-tabs within the Lead Time view:
- Year Trends: Average lead time by year with on-time rate and schedule slip
- Budget Variance: Budgeted vs actual hours per department across all 10 cost centers
- Machine Types: Lead time distribution by machine type with predictability scoring (coefficient of variation)
- Labor Benchmarks: Average actual hours per department per machine type (3+ projects required)
Outreach Planner
Prioritized customer action queue for sales and service teams:
- Ranks customers by engagement gap, fleet age, and revenue potential
- Filterable by action type (warranty follow-up, retrofit opportunity, re-engagement)
- CSV export for integration with CRM workflows
- Action items auto-generated from alert triggers
Sales Analytics Dashboard
Six-tab dashboard powered by quote_revenue.json data, providing end-to-end visibility into the spare parts quoting pipeline:
Pipeline Tab
- KPI cards: Open count, open value, average quote age, quotes >90 days, stale value
- Aging bucket bar chart: Visual distribution across 0-30d, 30-90d, 90-180d, 180d+ buckets
- Sortable customer pipeline table: Open quotes per customer with age, value, seller attribution
- Stale quote highlighting: Amber indicators for quotes >90 days without conversion
- CSV export: Download filtered pipeline data for CRM workflows
Conversion Tab
- Quote-to-win funnel visualization with status breakdown (open/ordered/won/closed)
- Year-over-year conversion rate trends
- Status distribution across the full quote lifecycle
Customers Tab
- Customer ranking table: Sortable by quote count, win rate, quoted/won/invoiced value
- Behavior flag badges: Part Mining (red), High Revisions (amber), Declining (amber), Growing (green)
- SPN machine attribution: Expandable rows showing quote volume per machine serial number
- Crosswalk source badges: aftec (green), name-matched (blue), unmatched (orange)
- Account Deep Dive panel: Full-screen overlay with:
- Complete quote timeline with stale highlighting and line item drilldown
- Quote-to-order conversion funnel specific to the account
- Part overlap analysis across quotes (from line items)
- Behavior flag explanations with tooltips
- Yearly revenue bar chart (quoted vs won)
- Comparable accounts with similar quote volume for benchmarking
Sellers Tab
- Seller performance ranking by won value, quote count, and win rate
- Human-readable seller names resolved from EMPLOYEE.PUBLIC.1287
Orders Tab
- KPI cards: Open order count, total value, shipped value, remaining, breakdown by type (Spare/Warranty)
- Sortable table: SO#, date, customer, PO#, type badge, value, shipped, remaining, linked quotes
- Links open sales orders back to originating quotes via SOREQ mapping
Trends Tab
- KPI cards: Current year quotes, won value, YoY quote growth, YoY won growth, invoiced total
- 3-layer bar chart: Quoted (blue), Won (green), Invoiced (gray) per year
- Summary table: Year-by-year breakdown with win rate, quote/invoice ratio, conversion velocity
- Conversion velocity metric: Average days from quote to invoice for won quotes
Quote Status Classification
Quotes are classified into four statuses:
| Status | Criteria |
|---|---|
| Won | Quote number appears in invoiceline.csv |
| Ordered | Quote linked to an open sales order via SOREQ |
| Open | Quote exists in OPEN.QUOTE.HEAD.1287 and is ⇐365 days old |
| Closed | Not in ERP or >365 days old without a win |
A stale flag is set on any non-won quote older than 90 days, enabling visual indicators without removing quotes from the pipeline.
Quote Intelligence (Behavior Flags)
Per-customer behavioral analysis to answer “why is conversion low?”:
| Flag | Trigger | Significance |
|---|---|---|
| Possible Part Mining | 10+ quotes, <5% win rate, <2% won value | Customer may be harvesting part numbers/pricing |
| High Revision Rate | >50% of quotes are revisions of earlier quotes | Win rate may be artificially deflated by counting revisions |
| Declining Engagement | Last 2 years <50% of prior 2 years’ quote volume | Customer engagement is dropping |
| Growing Account | Recent won value exceeds prior by 20%+ | Account is trending positively |
Duplicate detection groups quotes by base number (stripping alpha suffixes like 15124A, 15124B) and finds overlapping-part quotes within 60-day windows. The adjustedWinRate corrects for revision inflation.
Quality Intelligence Dashboard
Integrates with Redbook RFC data (processed via scripts/process-redbook.mjs):
- Quality alert generation (active RFCs, high-cost issues, recurring patterns)
- Department-level analysis of quality issues
- Cost trend tracking (material + labor waste)
- Links to related projects and machines
Technology Stack
| Component | Technology |
|---|---|
| Framework | React 18.3 |
| Language | TypeScript 5.6 (strict mode) |
| Build Tool | Vite 6 |
| Styling | TailwindCSS 3 |
| Table | TanStack Table 8 + React Virtual 3 |
| Authentication | Azure AD / MSAL 3.30 |
| Server | Express 4 + SQLite (better-sqlite3) |
| Testing | Vitest 4 + Testing Library + jest-dom |
| Icons | Lucide React |
| Node.js | >=24.0.0 |
Data Architecture
Data Sources
All data is loaded client-side from static files in /public/RawData/:
| File | Records | Description |
|---|---|---|
Project1287List.xml | 3,084 | Project master data from AFTEC |
Account1287List.xml | ~4,800 | Account/customer master data |
userlist.csv | ~200 | Employee ID → name mapping |
projbudget.csv | ~3,000 | Budget data (pipe-delimited, 25 columns) |
VendorList.xml | ~800 | Vendor information |
control_components.json | 1,548 projects | BOM data with component details (7.3MB), built from pipeline outputs that now source scraper-owned tables from psi_ingest.db before publish |
quote_revenue.json | 22,364 quotes | Pre-processed quote/invoice revenue data |
intermediary_mapping.json | 15 entries | Distributor → end-customer revenue attribution config |
redbook_data.json | varies | Pre-processed Redbook RFC quality data |
Pre-processing Scripts
| Script | Purpose | Data Sources |
|---|---|---|
scripts/process-quotes.mjs | Quote revenue attribution, orders, intelligence | spquote.csv, invoiceline.csv, spquoteline.csv, CUSTOMER.1287, ACCOUNT.1287, OPEN.QUOTE.HEAD.1287, SPN.QUOTES.1287, EMPLOYEE.PUBLIC.1287, SOREQ, VB_OPENORDLIST subroutine |
scripts/process-redbook.mjs | Redbook RFC quality data | Redbook API export, RFC classification data |
Data Flow
dgwReportFiles (S:\UnixShare) PSI.UniData.API
| spquote.csv | CUSTOMER.1287
| invoiceline.csv | ACCOUNT.1287
| spquoteline.csv | OPEN.QUOTE.HEAD.1287
| | SPN.QUOTES.1287
| | EMPLOYEE.PUBLIC.1287
| | SOREQ
| | VB_OPENORDLIST (subroutine)
+--------+-----------------------+
|
+--------v--------------------+
| process-quotes.mjs | (local pre-processing)
| process-redbook.mjs |
+--------+--------------------+
|
+--------v--------------------+ +----------------------+
| quote_revenue.json | | Project1287List.xml |
| redbook_data.json | | Account1287List.xml |
| intermediary_mapping.json | | projbudget.csv |
+--------+--------------------+ | control_components |
| +----------+-----------+
+-----------+--------------------+
|
+--------v--------+
| React App | (client-side parsing)
| dataService.ts |
+--------+--------+
|
+--------v--------+
| 11 View Modes |
| (Timeline, |
| Customer 360, |
| Sales...) |
+-----------------+
Pipeline note: App-facing static outputs remain the consumer contract, but scraper-owned upstream tables are now staged durably in psi_ingest.db and only then copied into rebuildable analytics artifacts/published files.
AFTEC Number Systems
Understanding the crosswalk is critical for data integration:
| ID Type | Format | Example | Source |
|---|---|---|---|
| AccountNumber | 4-digit | 1030 | ACCOUNT.1287 record ID |
| CustomerNumber | 6-digit | 100631 | CUSTOMER.1287 record ID |
| ProjectNumber | 4-digit | 2356 | PROJECT.1287 (Job number) |
| SPN | S + digits | S2586 | Serial/Part Number (machine identity) |
| ContactNumber | 5-digit | 09313 | CONTACT.1287 record ID |
| ShipToNumber | company!customer!sequence | 1!100631!1 | Ship-to site identifier |
Crosswalk: CUSTOMER.1287 field 3 → AccountNumber. ACCOUNT.1287 field 17 → CustomerNumber(s). This bidirectional mapping links 6-digit customer IDs (from quotes/invoices) to 4-digit account IDs (from projects).
Project Structure
project-explorer/
+-- .github/workflows/
| +-- azure-app-service.yml # CI/CD pipeline
+-- public/RawData/ # Static data files (9 files)
+-- scripts/
| +-- process-quotes.mjs # Quote revenue pre-processor
| +-- process-redbook.mjs # Redbook RFC quality processor
+-- server.js # Express server (SQLite + static files)
+-- vitest.config.ts # Test configuration
+-- src/
+-- App.tsx # CSS Grid shell, data loading, view routing
+-- auth.ts # Azure AD / MSAL configuration
+-- main.tsx # Entry point
+-- types/index.ts # All TypeScript interfaces (~450 types)
+-- services/
| +-- dataService.ts # Data parsing, business logic, LTV, scoring
| +-- machineStatusService.ts # Shared machine status (SQLite)
| +-- __tests__/
| +-- dataService.test.ts # 96 unit tests for pure functions
+-- test/
| +-- setup.ts # jest-dom/vitest setup
| +-- fixtures.ts # Builder functions (buildProject, buildBudget, etc.)
+-- components/
+-- Header.tsx # Compact 48px header (search + alert bell + stats)
+-- FilterBar.tsx # 40px toolbar (year range, type filters)
+-- AlertDashboard.tsx # Slide-over notification panel
+-- Timeline.tsx # Horizontal timeline view
+-- ProjectCard.tsx # Individual project card
+-- ProjectDetailPanel.tsx # Full-height overlay detail panel
+-- CumulativeGraph.tsx # Analytics graph
+-- LoadingSpinner.tsx # Loading state
+-- customers/
| +-- CustomerView.tsx # Customer 360 with LTV + scoring
+-- equipment/
| +-- EquipmentFleet.tsx # Equipment fleet view
+-- leadtime/
| +-- LeadTimeView.tsx # Lead time analytics (4 sub-tabs)
+-- obsolescence/
| +-- ObsolescenceReport.tsx
| +-- MachineHealth.tsx
| +-- index.ts
+-- outreach/
| +-- OutreachPlanner.tsx # Prioritized action queue
+-- parts/
| +-- PartsView.tsx # Part frequency + 5 sub-tabs
| +-- BomComparison.tsx
| +-- DuplicateDetection.tsx
| +-- KitAnalysis.tsx
| +-- PartCatalog.tsx
+-- quality/
| +-- QualityDashboard.tsx # Redbook RFC integration
+-- sales/
| +-- SalesView.tsx # Sales dashboard (6 tabs, ~1,300 lines)
| +-- AccountDeepDive.tsx # Customer deep-dive overlay panel
| +-- OrdersTab.tsx # Standalone orders component
| +-- QuoteLineItems.tsx # Quote line item drilldown sub-table
+-- views/
| +-- TableView.tsx # Data table
| +-- GridView.tsx # Card grid
| +-- ViewSwitcher.tsx # View switcher toolbar
+-- warranty/
+-- WarrantyTracker.tsx
~13,900 lines of TypeScript
Testing
Project Explorer uses Vitest with Testing Library, following the PSI Web App Testing standards:
| Area | Details |
|---|---|
| Framework | Vitest 4 (globals mode) |
| DOM | jsdom environment |
| Assertions | @testing-library/jest-dom/vitest |
| Fixtures | Builder pattern: buildProject(), buildUser(), buildBudget(), buildMachineComponent() |
| Coverage | V8 provider, scoped to dataService.ts with ratcheting thresholds |
| Tests | 96 unit tests covering 25+ exported pure functions |
Running Tests
npm test # Single run
npm run test:watch # Watch mode
npm run test:coverage # With V8 coverage reportCoverage Thresholds (ratchet floor)
| Metric | Threshold |
|---|---|
| Statements | 19% |
| Branches | 21% |
| Functions | 23% |
| Lines | 20% |
Coverage is scoped to dataService.ts (~2,600 lines). Thresholds increase as more tests are added.
Production Configuration
| Setting | Value |
|---|---|
| Azure App Service | ps-project-explorer |
| Resource Group | PS-WEBAPPS |
| Runtime | Node 24 (Linux) |
| Custom Domain | projects.progressivesurface.com |
| Private Endpoint IP | 10.160.140.10 |
| Private Endpoint Subnet | PS-ProdData |
| VNet Integration | PS-VNMAIN / PS-WebApps |
| Managed Identity | e5791f95-8e1d-4151-9f47-2e0456c19e39 |
| Entra App ID | 971a34d6-004c-40c0-86d9-f610d83a26ca |
| SSL | Wildcard cert from ps-certificates-kv |
Network Architecture
Internet (blocked)
|
X (Public network access: Enabled, Default action: Deny)
|
+------+----------------------------------------+
| Azure App Service: ps-project-explorer |
| projects.progressivesurface.com |
| |
| VNet Integration: PS-VNMAIN / PS-WebApps |
+------+----------------------------------------+
|
| Private Endpoint (10.160.140.10)
| PS-ProdData subnet
|
+------+----------------------------------------+
| PSI Internal Network |
| |
| Users access via: |
| - Onsite workstations |
| - VPN connection |
| - PSI Portal (portal.progressivesurface.com) |
+-----------------------------------------------+
Deployment
CI/CD Pipeline
GitHub Actions workflow on [self-hosted, psi-internal] runner, triggers on push to master:
- Checkout code
- Setup Node.js 24
npm ci— install all dependenciesnpm run build— TypeScript check + Vite production buildnpm ci --omit=dev— reinstall production deps onlyzip -r deploy.zip dist server.js package.json node_modules publicaz login --identity— managed identity auth- Configure app settings (
WEBSITE_RUN_FROM_PACKAGE=1, disable Oryx build) az webapp deployment source config-zip— deploy zip package (10 min timeout)
Pre-processing (manual, before deploy)
Quote revenue and quality data must be regenerated when new source data is available:
# Quote revenue — requires S:\UnixShare and API access
node scripts/process-quotes.mjs
# Output: public/RawData/quote_revenue.json (~2 minutes)
# Redbook quality data
node scripts/process-redbook.mjs
# Output: public/RawData/redbook_data.jsonLocal Development
# Clone repository
git clone https://progressivesurface.ghe.com/ProgressiveSurface/project-explorer.git
cd project-explorer
# Install dependencies
npm install
# Start dev server (frontend only)
npm run dev
# Opens at http://localhost:5173
# Start with Express server (machine status API)
npm run build && npm start
# Serves at http://localhost:8080
# Build for production
npm run build
# Run tests
npm test
# Run linter
npm run lintKey Design Patterns
CSS Grid App Shell
The root layout uses h-screen grid grid-rows-[48px_40px_1fr] overflow-hidden. Header and toolbar are fixed-height rows; content fills remaining space. All views use h-full flex flex-col with flex-shrink-0 for stat cards/tabs and flex-1 overflow-auto for scrollable content. No calc(100vh-Npx) anywhere.
Overlay Panels
Both the Alert Dashboard and Project Detail Panel render as fixed overlays (not in page flow). Alert panel slides from the right at z-50; detail panel overlays with semi-transparent backdrop. Content height is constant regardless of panel state.
Retrofit Detection
Projects with 5-digit numbers starting with 9 are automatically flagged as retrofits and linked to parent machines via serial number extraction from descriptions.
Client-Side Data Processing
All XML/CSV parsing happens in the browser via dataService.ts. No server-side database is required for core project data — only the machine status feature uses SQLite on the Express server.
Parent Company Grouping
The Customer 360 view uses name-based heuristics to group individual account locations into parent companies (e.g., “Pratt & Whitney - Columbus” and “Pratt & Whitney - East Hartford” roll up to “Pratt & Whitney”).
Quote Revenue Attribution Hierarchy
- Machine: Quote linked to SPN(s) via
SPN.QUOTES.1287export → attribute to those specific machines - Machine (inferred): Single-machine customer → auto-attribute all quotes
- Site: Group by ShipTo number when multiple machines exist
- Customer: AFTEC crosswalk (CUSTOMER.1287 → ACCOUNT.1287), with fuzzy name-matching fallback
- Unmatched: No crosswalk or name match → visible in unmatched bucket for manual review
Revenue Data Sources
Two overlapping data sources for revenue:
- Project orderTotal (from
Project1287List.xml): The order value at time of order for each machine/retrofit - totalInvoiced (from
invoiceline.csvviaquote_revenue.json): All billing — machines, parts, service, everything
These overlap but don’t perfectly align (different systems, timing, scope). The app shows both without trying to subtract one from the other. Scoring uses max(orderValue, invoiced) to avoid double-counting.
TypeScript Strict Mode
noUnusedLocals and noUnusedParameters are enforced. All data types are defined in src/types/index.ts (~500 lines of type definitions including QuoteStatus, QuoteLineItem, OpenOrder, QuotePatterns, BehaviorFlags).
Quote Revenue Pipeline Details
The process-quotes.mjs script is the bridge between AFTEC ERP data and the Project Explorer UI:
Input Sources
| Source | Type | Records | Key Fields |
|---|---|---|---|
spquote.csv | dgwReportFiles | 22,364 | Quote No, Date, Amount, Customer No, Ship info |
invoiceline.csv | dgwReportFiles | 163,306 | Customer No, SO No, Invoice Date, Ext Price, Quote No |
spquoteline.csv | dgwReportFiles | 54,970 | Quote No, Part No, Qty, Price (line items) |
CUSTOMER.1287 | API export | 912 | Field 3 = AccountNumber (crosswalk) |
ACCOUNT.1287 | API export | 4,851 | Field 17 = CustomerNumbers, Field 20 = SPNs |
OPEN.QUOTE.HEAD.1287 | API export | 31,128 | Open quote IDs (status determination) |
SPN.QUOTES.1287 | API export | varies | SPN → quote number linkage (machine attribution) |
EMPLOYEE.PUBLIC.1287 | API export | varies | Employee ID → first/last name (seller resolution) |
SOREQ | API export | varies | SO line → quote number bidirectional mapping |
VB_OPENORDLIST | Subroutine call | varies | All open sales orders with SO#, value, shipped, type |
Processing Pipeline Steps
- Load CSV files — spquote, invoiceline, spquoteline from dgwReportFiles
- Export ERP tables — CUSTOMER, ACCOUNT, OPEN.QUOTE.HEAD via paginated API
- SPN attribution — Fetch SPN.QUOTES.1287, build quote-to-machine mapping
- Seller resolution — Fetch EMPLOYEE.PUBLIC.1287, map employee IDs to names
- Open orders — Call VB_OPENORDLIST subroutine for active sales orders
- SOREQ linkage — Export SOREQ table for quote-to-SO bidirectional mapping
- Process quotes — Status determination, crosswalk, line items, stale flags
- Fuzzy matching — Name-based fallback for unmatched customers (strip INC/LLC/CORP, case-fold)
- Quote intelligence — Duplicate/revision detection, behavior flag computation per customer
- Output — Write
quote_revenue.jsonwith all enrichment data
Status Determination Logic
For each quote in spquote.csv:
1. If Quote No appears in invoiceline.csv -> status: 'won'
2. If Quote No linked to open SO via SOREQ -> status: 'ordered'
3. If Quote No in OPEN.QUOTE.HEAD.1287 AND <=365 days old -> status: 'open'
4. Otherwise -> status: 'closed'
Additional flags:
- stale: true when non-won quote is >90 days old
- ageDays: days since quote date
- lineItems: part-level detail stored for open and won quotes (abbreviated keys)
Output Stats (as of February 2026)
| Metric | Value |
|---|---|
| Total quotes processed | 22,364 |
| Won (invoiced) | 3,492 |
| Open (active in ERP, ⇐365d) | varies |
| Ordered (linked to open SO) | varies |
| Closed (expired/lost) | varies |
| Won quote value | $18.3M |
| Total invoiced revenue | $859M |
| Crosswalk match rate | 96.5%+ (improved with fuzzy matching) |
Server Component
server.js provides:
- Static file serving: Serves the Vite build output (
dist/) andpublic/data files - Machine Status API: SQLite database for shared machine status tracking
GET /api/machine-status/:serialNumber— get machine statusPOST /api/machine-status— update machine status- Falls back gracefully if
better-sqlite3native module is unavailable
- Service Model API: Stores customer service model preferences
POST /api/service-model— save service model selection per account
Implementation Phases
Project Explorer was built incrementally across 5 major phases:
| Phase | Feature | Key Deliverables |
|---|---|---|
| 1A | Shared Machine Status | SQLite + Express API + localStorage cache |
| 1B | Customer 360 | Revenue breakdown, contacts, engagement timeline |
| 1C | Alert Dashboard | Warranty, obsolescence, engagement, retrofit alerts |
| 2A | Full Fleet Components | Manufacturer EOL reference data |
| 2B | Customer LTV Model | projectedCustomerLTV function |
| 2C | Service Spectrum | Own / service-contract / managed / pay-per-part models |
| 3A | Part Frequency Dashboard | PartsView with reuse categorization |
| 3B | BOM Comparison | Cross-project Jaccard similarity by machine type |
| 3C | Standard vs Custom BOM | Green/yellow/red reuse color coding |
| 4A | Lead Time Analytics | Year trends, budget variance, machine type stats |
| 4B | Labor Benchmarks | Avg hours per dept per machine type |
| 5A | Part Proliferation Engine | Duplicate detection, kit analysis, searchable catalog |
| 5B | Outreach Planner | Prioritized action queue with CSV export |
| 5C | Quality Intelligence | Redbook RFC integration, quality alerts |
| 6A | Sales Analytics Dashboard | Pipeline, conversion, customers, sellers, orders, trends |
| 6A.1 | Pipeline Data Quality | SPN attribution, seller names, stale flags, fuzzy matching |
| 6A.2 | Quote Enrichment | Line items, open orders, SOREQ linkage, quote intelligence |
| 6A.3 | Conversion Intelligence | Behavior flags, duplicate detection, account deep-dive |
Related Documentation
- psi-portal - PSI Portal (links to Project Explorer)
- psiall-architecture - .NET architecture and UniData subroutines
- deploy-to-azure - Azure deployment guide
- webapp-testing - Web app testing standards
- quotes - AFTEC quote subroutines
- sales-orders - AFTEC sales order subroutines
- data-brain - Master data reference
- terminology - PSI terminology glossary
Last updated: February 2026