Roof material engine — formulas, API & integration

This document is for your development team and stakeholders who need to understand how measurements flow through the system, how quantities are derived, and how to integrate the API into a larger CRM, database, or quoting product.

Core idea: The browser collects roof measurements and product choices. The server (logic/calculator.js) turns that into a structured materials object and optional pricing. Reports (PDF, Word, Excel) are views of that same data—no duplicate business logic in the exporters.

1. System architecture

Browser (index.html)
    POST /api/calculate     → JSON { materials, pricing, input }
    POST /api/proposal      → PDF stream
    POST /api/proposal/docx → Word (.docx) file
    POST /api/proposal/xlsx → Excel workbook (material order + pricing sheet)

server.js
    → validation (utils/validation.js)
    → calculateRoofEstimate (logic/calculator.js)
    → calculatePricing (logic/pricing.js)
    → PDF / Word / Excel generators (utils/pdf.js, word.js, excel.js)

2. Request body (what you send)

All POST endpoints use the same JSON shape (after validation):

{
  "roof": {
    "area_sqft": 2500,
    "pitch": 6,
    "complexity": "low" | "medium" | "high"
  },
  "lengths": {
    "ridge_lf": 120,
    "hip_lf": 80,
    "valley_lf": 60,
    "eave_lf": 140,
    "rake_lf": 100
  },
  "product": {
    "shingle_style": "3tab" | "architectural" | "designer",
    "manufacturer": "GAF" | "Owens Corning" | ... | "Other",
    "manufacturer_custom": "string (required context when manufacturer is Other)"
  },
  "pricing_margin_percent": 20
}

product and pricing_margin_percent are optional in the raw request; the server normalizes defaults (architectural, manufacturer GAF, margin 20). Shingle style and manufacturer appear on exports for ordering context—they do not change bundle math today (you can later tie SKUs or waste rules to style in calculator.js).

3. API reference

Method & path Response Purpose
GET /api/health JSON Load balancer / uptime check.
POST /api/calculate JSON Primary integration: materials + pricing + echoed normalized input.
POST /api/proposal PDF binary Tabular estimate PDF.
POST /api/proposal/docx Word binary Same data as PDF in editable Word format.
POST /api/proposal/xlsx Excel binary “Roofing material order” sheet + “Pricing” sheet (aligned to contractor order workflows).

Error format (robust handling)

Failed requests return JSON (never a partial file) with a stable shape:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR" | "INVALID_JSON" | "NOT_FOUND" | "INTERNAL_ERROR",
    "message": "Human-readable summary",
    "details": ["Optional bullet list", "..."]
  }
}

Binary export endpoints use the same error JSON on 4xx/5xx so clients can branch on Content-Type: application/json.

4. Core formulas (logic/calculator.js)

4.1 Pitch-adjusted surface area

Plan area is expanded by pitch so shingle coverage matches slope:

pitchMultiplier = sqrt((pitch² + 12²) / 12²)
surfaceArea = area_sqft × pitchMultiplier

Example: 2,500 sq ft plan at 6/12 → multiplier ≈ 1.118 → ~2,795 sq ft surface.

4.2 Waste factor

Base waste 10%, increased by linear features and capped at 25%:

waste = 0.10
       + (valley_lf / 100) × 0.05
       + (hip_lf / 100) × 0.03
       + (ridge_lf / 100) × 0.01
if complexity === "medium" → +0.02
if complexity === "high"   → +0.04
waste = min(waste, 0.25)

This drives shingles and underlayment (both use adjusted area).

4.3 Shingles (squares & bundles)

adjustedArea = surfaceArea × (1 + waste)
rawSquares = adjustedArea / 100
squares = ceil(rawSquares)          // contractor-style full squares
bundles = squares × 3               // CONSTANTS.bundlesPerSquare

4.4 Starter

lf = eave_lf + rake_lf
bundles = ceil(lf / starterCoverage)   // 100 LF per bundle (constant)

4.5 Ridge / hip caps

lf = ridge_lf + hip_lf
bundles = ceil(lf / ridgeCapCoverage)  // 20 LF per bundle (constant)

4.6 Underlayment

adjusted = surfaceArea × (1 + waste)
rolls = ceil(adjusted / underlaymentEffectiveCoverage)  // 360 sq ft/roll effective

4.7 Drip edge

lf = eave_lf + rake_lf
pieces = ceil(lf / dripEdgeLength)   // 10 LF sticks

4.8 Valley membrane

sqft = valley_lf × 3   // 3' width assumption
rolls = ceil(sqft / 200)

4.9 Nails

count = squares × nailsPerSquare   // 320 per square (constant)

4.10 Vents

units = ceil(plan_area_sqft / 300)

4.11 Flashing (order estimate)

lf = eave_lf + rake_lf
rolls = ceil(lf / 75)

Flashing is a roll-count proxy for combined step/apron/roll goods—tune constants or replace with SKU logic as needed.

5. Constants (single source of truth)

Defined in export const CONSTANTS inside constants/estimation.js:

KeyValueRole
bundlesPerSquare3Shingle bundles per square
starterCoverage100 LFStarter per bundle
ridgeCapCoverage20 LFRidge/hip cap per bundle
underlaymentEffectiveCoverage360 sq ftAfter overlaps
dripEdgeLength10 LFStick length
nailsPerSquare320Nail estimate per square

6. Pricing module (logic/pricing.js)

calculatePricing(materials, marginPercent) multiplies each line by placeholder unit costs in MATERIAL_PRICES, sums a subtotal, applies margin, returns breakdown, subtotal, marginAmount, total. Swap these numbers for database- driven or API-driven supplier pricing without changing the calculator.

7. Integration checklist for your devs

  1. Validate user input client-side for UX; rely on server validation for correctness.
  2. Store the normalized input + materials JSON in your database for audit/reorders.
  3. Replace MATERIAL_PRICES with CRM or supplier lookups (keep the calculator pure).
  4. Map Excel columns to your internal SKU table—the workbook is structured for procurement.
  5. Extend formulas by editing only calculator.js; exporters read the same output object.

8. Files map

FileResponsibility
server.jsRoutes, validation pipeline, error handling
utils/validation.jsRequest schema + normalization
logic/calculator.jsAll quantity logic
logic/pricing.jsIllustrative dollars
utils/reportHelpers.jsShared rows/labels for Word & Excel
utils/pdf.jsPDF layout
utils/word.jsWord (.docx) layout
utils/excel.jsRoofing material order workbook
public/index.htmlUI + API consumer example
public/guide.htmlThis documentation