A small, static web app that implements the Sighthound Savings Analyzer: a multi-step wizard that collects camera and cost inputs, then computes an optimal Sighthound configuration and estimated savings.
The app runs entirely in the browser and is backed by a single JavaScript module. It guides users through:
- Camera infrastructure and ownership
- Camera counts and compute nodes
- Optional analytics software selections
- Current cost inputs
- A final analysis step, followed by a detailed results view and PDF export
Key files:
index.html– Markup for the multi-step wizard and results sectionstyles/savings-analyzer.css– Layout, theming, and component stylesapp.js– All interactive behavior, state management, and savings calculationstest-run.js– A Node/jsdom harness that drives the wizard flow headlessly for regression checks
- Node.js and npm installed
npm installThis pulls in jsdom and its transitive dependencies for the test harness.
The app is a static HTML/CSS/JS bundle and does not require a build step.
You can either:
-
Serve the project root with a static file server (recommended):
npx serve .Then open the reported URL (typically
http://localhost:3000) and navigate to/index.html. -
Or open
index.htmldirectly in your browser from the filesystem (if you hit any module/CORS restrictions, use the HTTP server approach above).
There is no formal test runner wired into npm test; instead, use the jsdom-based harness:
node test-run.jsThe harness will:
- Load
index.htmlin a jsdom environment - Wait for the app to finish initialization (via
window.__savings_init_doneset inapp.js) - Simulate a typical user journey:
- Select a camera option in step 1
- Continue through steps 2–4
- Exercise the optional software step (step 3), including the Skip for now path and back navigation
- Log the active step IDs and button states as it navigates
To focus on or adjust specific scenarios, edit test-run.js directly (e.g., comment out flows you are not interested in) and rerun node test-run.js.
- State and navigation are driven from a single
stateobject inapp.jsand agoToStep(step)helper. If you add or remove steps, update bothindex.htmland the navigation logic together (including thetotalStepsconstant inapp.js). - Camera and node capacity logic is centralized in
updateCamerasAndNodes/updateNodeStatusinapp.js. If pricing or capacity assumptions change (e.g., cameras per node), prefer updating the shared constants instead of scattering values. - The results view (hardware/software breakdown, cost comparison, and savings card) is computed from the same helpers that power the downloadable PDF, so UI and PDF output stay in sync.
- Step 1 – Camera infrastructure (
#step1)- User chooses whether they already have cameras and, if so, what kind (IP vs non-IP vs none).
- If they choose IP cameras, the flow branches to step 1b; otherwise it goes directly to step 2.
- Step 1b – Ownership (
#step1b)- Only shown when the user has existing IP cameras.
- Captures whether cameras were purchased outright, are subscription/bundled, or the user is unsure; this is stored on
state.ownershipfor context but does not change the math.
- Step 2 – Cameras & compute nodes (
#step2)- User configures counts for Standard IP Cameras and Sighthound Smart Cameras via steppers and number inputs.
updateCamerasAndNodes()recomputes total camera count, suggests nodes based on capacity, and optionally auto-syncsstate.computeNodeswhen the Auto-add Compute Nodes toggle is on.- The
#nodeStatusbanner surfaces under/over-capacity information for the current camera/node configuration.
- Step 3 – Software selection (optional) (
#step3)- User can select analytics software per stream:
LPR(License Plate Recognition)MMCG(Vehicle Analytics)Bothat a discounted bundled rate
- Selections are stored as
{ type, price }objects instate.softwareand drive monthly software cost. - Continue is disabled when no software is selected; Skip for now clears all selections and proceeds with no analytics (software cost = 0), but the user can navigate back and add software later.
- User can select analytics software per stream:
- Step 4 – Current costs (
#step4)- User enters their current monthly software cost, upfront hardware cost, and billing frequency (monthly vs annual).
- These values become the “current” side of the cost comparison used in the analysis.
- Step 5 – Analysis trigger (
#step5)- The Calculate my savings button calls
runAnalysis(). runAnalysis()recomputes hardware and software totals from the currentstate, updates the recommended setup, cost comparison, and savings card, and then reveals the results section.
- The Calculate my savings button calls
- Results (
#results)- Shows a savings highlight card, a hardware + software breakdown, a timeframe toggle (12/24/36 months), and a side-by-side comparison of current vs Sighthound costs.
- Users can download a PDF (
#downloadPdf), start over, or go back and edit answers.
All amounts are in USD. Key constants in app.js:
PRICES:standardCamera = 250(per camera, upfront)smartCamera = 3000(per camera, upfront)node = 3500(per compute node, upfront)
CAMERAS_PER_NODE = 4(capacity per compute node)
Given the current state:
- Camera counts
totalCameras = state.standardCameras + state.smartCameras
- Suggested nodes
suggestedNodes = totalCameras > 0 ? ceil(totalCameras / CAMERAS_PER_NODE) : 0
- Per-month software total
- Let
softwarePerStream = sum(price for each entry in state.software) monthlySoftwareTotal = softwarePerStream * totalCameras
- Let
- Hardware totals
hardwareStandard = state.standardCameras * PRICES.standardCamerahardwareSmart = state.smartCameras * PRICES.smartCamerahardwareNodes = state.computeNodes * PRICES.nodehardwareTotal = hardwareStandard + hardwareSmart + hardwareNodes
- Timeframe (months)
timeframe = state.timeframe(12, 24, 36, etc., driven by the timeframe buttons)
- Current setup cost
- Normalize current monthly cost based on billing frequency:
currentMonthlyNormalized = state.frequency === "annual" ? state.currentMonthly / 12 : state.currentMonthly
- Over the selected timeframe:
currentTotal = state.currentUpfront + currentMonthlyNormalized * timeframe
- Normalize current monthly cost based on billing frequency:
- Sighthound setup cost
- Over the same timeframe:
sighthoundTotal = hardwareTotal + monthlySoftwareTotal * timeframe
- Over the same timeframe:
- Savings
savings = currentTotal - sighthoundTotalsavingsPerMonth = savings / timeframe
Interpretation:
- When
savings > 0, the Sighthound configuration is cheaper over the chosen timeframe; the savings card shows both total savings and per-month savings. - When
savings <= 0, the app treats the result as an additional investment and explains that the extra spend corresponds to upgraded hardware and analytics.