Internal architecture · Proposal artifact
The system, end to end.
Most freelancers will pitch you a website. This is the system that website is the surface of: NWS auto-publish, source-tagged URL attribution, a 30-field GHL schema, a 15-stage pipeline, a lead-scoring engine, a Whisper-and-Sonnet call-recording loop, and the owner dashboard that ties them together. Read this in 10 minutes, then we'll get on a call to walk it.
Diagram
Every node, every protocol.
The split that makes this defensible: GHL owns marketing automation + contact + pipeline; AccuLynx owns production (measurements, supplements, crews); CF Pages + Workers own routing, attribution, storm intelligence, and recordings.
┌──────────────────────────────────────────┐
│ ATTRIBUTION SOURCES │
│ - Outbound AI call (VAPI/Bland/Synthflow│
│ - Google/Meta Ads UTMs │
│ - Door knock QR code │
│ - Yard sign QR (storm-area canvass) │
│ - Referral link (?ref={leadId}) │
│ - Organic SEO │
└─────────────────┬────────────────────────┘
▼ HTTPS GET (cookie + LS write)
┌──────────────────────────────┐ ┌──────────────────────────────────────────┐
│ NWS / IEM Storm Feed │ │ PUBLIC SITE (Astro 5 + Tailwind v4) │
│ cron */15min CF Worker │ │ CF Pages linecrewroofing.com │
│ → poll lsr.geojson?wfo=FWD │ │ - / (homepage) │
│ → filter HAIL ≥ 1.5″ │ │ - /storms/{slug} (auto-generated) │
│ → write to KV: storm:events │ │ - /should-i-file (calculator) │
│ → notify owner Telegram │ │ - /verify-a-roofer (skeptic pre-read) │
│ → draft Astro MDX page │ │ - /h?c={callId} (handoff personalized) │
│ (PR to GitHub auto) │ │ - /api/lead (POST form intake) │
└──────────────────────────────┘ └─────────┬────────────────────┬───────────┘
▼ ▼
POST JSON ┌────────────────────────┐
│ CF Pages Function: │
│ /api/lead │
│ - Turnstile verify │
│ - rate-limit (KV) │
│ - score lead (0-100) │
│ - parse first-touch │
│ - fan out 3 ways: │
│ 1) GHL upsert │
│ 2) Sheets mirror │
│ 3) Slack/Telegram │
│ - return 200 fast │
└─┬────┬──────┬──────────┘
▼ ▼ ▼
┌───────────────┐ ┌─────────┐ ┌─────────────┐
│ GoHighLevel │ │ Google │ │ Owner pings │
│ v2 API │ │ Sheets │ │ via Telegram│
│ - upsert │ │ mirror │ │ if score≥80 │
│ - 30 fields │ │ (safety │ └─────────────┘
│ - tag storm │ │ net) │
│ - assign stage│ └─────────┘
└───────┬───────┘
▼ workflow on stage change
┌─────────────────────────────────┐
│ GHL WORKFLOW ENGINE │
│ - SMS via LeadConnector │
│ - Email via Resend │
│ - Cal.com booking sync │
│ - VAPI inbound webhook │
│ - Stale-opportunity nudges │
│ - 15-stage pipeline routing │
└─────────────────────────────────┘
│
(post-walk) ▼
┌─────────────────────────────────┐
│ HANDOFF TO PRODUCTION │
│ AccuLynx job creation │
│ CompanyCam photo sync │
│ Hourly cron: AccuLynx → GHL │
└─────────────────────────────────┘
│
▼
┌─────────────────────────────────┐
│ OWNER DASHBOARD (/admin) │
│ CF Access auth │
│ Reads Supabase + GHL API │
│ Funnel · Sources · Capacity │
│ Recordings · Hot queue · AR │
└─────────────────────────────────┘ Source attribution
Every URL carries its origin.
Every entry point gets its own URL signature. The page reads the params, personalizes the copy, and writes them to the GHL contact when the form submits - so every lead's true origin is recoverable, not guessed.
// Sample URL: AI-call follow-up SMS link
https://linecrewroofing.com/storms/plano-2026-04-26-hail
?s=ai_call // source
&c=plano-2026-04-26-hail // campaign / storm
&ag=ai-emma // agent that called
&n=willow-bend // neighborhood
&fn=TWFyaWE= // first name (base64)
&ph=KzEyMTQ1NTUwMTIx // phone E.164 (base64)
// On page load, attr.js writes to localStorage + cookie + form hidden fields
> localStorage.lc_ft = { params: {...}, landed_at, landing_url, referrer, user_agent }
GHL custom fields schema
30 fields. 12 ship Phase 1.
Every field has a defined source, type, and downstream use. Phase 1 (12 fields) ships with the demo and covers attribution, scoring, compliance. Phase 2 (the rest) ships during the build engagement and adds production sync + carrier intelligence + call summaries.
| Field name | Type | Description | Phase |
|---|---|---|---|
| lead_source | dropdown | ai_call · sms · doorhanger · ad_meta · ad_google · referral · organic · qr | P1 |
| lead_source_detail | text | utm_source + utm_campaign concatenated | P1 |
| first_touch_url | url | Full landing URL with query params, for forensics + path replay | P1 |
| first_touch_at | datetime | ISO timestamp of first site visit, for velocity calculation | P1 |
| first_touch_campaign | text | Campaign / storm event ID at first touch | P1 |
| storm_event_id | text | Slug, e.g. "plano-2026-04-26-hail" - joins to storm record + dashboard | P1 |
| outreach_agent | text | AI caller ID (vapi_b2x9) or human crew lead - leaderboard | P1 |
| address_zip | text | 5-digit zip - crew routing, storm match, density score | P1 |
| lead_score | number | 0-100 weighted score, computed at form-submit | P1 |
| lead_score_breakdown | text JSON | Audit trail of how the score was reached | P1 |
| consent_sms | boolean | TCPA-compliant SMS consent, required for automated messaging | P1 |
| consent_calls | boolean | TCPA-compliant call consent | P1 |
| neighborhood_first_touch | text | willow-bend, prestonwood, etc. Drives copy + crew assignment | P2 |
| storm_hail_size_in | number | Hail size in inches at landing-page event | P2 |
| storm_event_date | date | Date of NWS-confirmed storm | P2 |
| roof_age_estimate | number | Years; pulled from form OR DCAD/TAD lookup | P2 |
| insurance_carrier | dropdown | State Farm · Allstate · USAA · Farmers · Liberty Mutual · TX Farm Bureau · Other | P2 |
| claim_status | dropdown | none · planning · filed · adjuster_scheduled · approved · denied · supplementing · paid | P2 |
| claim_number | text | Carrier claim number, set after filing | P2 |
| quoted_amount | currency | Synced from AccuLynx hourly cron | P2 |
| signed_amount | currency | Final contract value | P2 |
| install_scheduled_date | date | Synced from AccuLynx | P2 |
| install_completed_date | date | Triggers review-request automation | P2 |
| crew_assigned | text | Lead crew member, drives SMS personalization | P2 |
| referral_source_lead_id | text | GHL contactId of referrer - for attribution + payout | P2 |
| last_call_recording_url | url | R2 signed URL to most recent call audio | P2 |
| last_call_summary | text | 4-line LLM summary of most recent call | P2 |
| last_call_sentiment | number | -1.0 to 1.0, gates hot-lead routing | P2 |
| time_in_current_stage_hrs | number | For SLA escalation rules | P2 |
| tcpa_optout | boolean | Suppresses all automated messaging when STOP received | P1 |
Pipeline stages
15 stages, each with an SLA.
Pipeline name in GHL: Storm Response - DFW. Workflows hook on the Pipeline Stage Changed trigger. Stale-opportunity SLAs auto-escalate.
- 1 New Lead SLA: 5 min SMS auto-reply within 60s; owner SMS direct if score ≥80
- 2 Tried-to-Reach SLA: 48 hr 3-touch cadence: SMS +30min, email +2h, SMS +24h; drop to nurture if no response
- 3 Qualified - Walk Pending SLA: 24 hr Cal.com link sent; manual call task if no booking in 6h
- 4 Roof Walk Scheduled SLA: until time Reminder SMS at -24h, -2h, -15min; weather check for reschedule
- 5 Walk Done - Quoting SLA: 48 hr AccuLynx job created; quote due nudge if 48h slip
- 6 Quoted SLA: 14 days Email/SMS quote follow-ups at +1d, +3d, +7d
- 7 Claim Filed SLA: 14-30 days Insurance liaison playbook; weekly check-in
- 8 Claim Approved SLA: 7 days Send contract via PandaDoc; deposit invoice
- 9 Contract Signed SLA: 7 days Stripe deposit invoice; install scheduling task
- 10 Install Scheduled SLA: until install Day-before reminder; crew dispatch SMS; weather check
- 11 Install Complete SLA: 3 days Final invoice; thank-you SMS + photo recap
- 12 Final Invoice Sent SLA: 30 days Payment-due drip; AR follow-up workflow
- 13 Paid + Review Requested SLA: 30 days Google review SMS, NPS, referral request with ?ref={contactId}
- 14 Customer / Referral Network SLA: ongoing Quarterly check-in; reactivation on new storm in zip
- 15 Drip Nurture (parking lot) SLA: - Monthly value-add email; reactivate on new storm in zip
Lead scoring
0-100 score at form-submit, drives routing.
score = clamp(0..100, +25 if storm_event_id present AND event in last 21 days +15 if hail_size_in >= 1.75 in lead's zip in last 30 days +10 if address_zip in DFW priority list +10 if roof_age_estimate >= 12 +10 if insurance_carrier in [State Farm, Allstate, USAA, Farmers] +15 if lead_source = ai_call AND VAPI sentiment >= 0.6 +10 if referral_source_lead_id is set + 5 if consent_sms AND consent_calls + 5 if form_completion_seconds < 90 // human, not bot, not abandoner -20 if address_zip not served (out of DFW) -10 if hours_since_first_touch > 168 ) Routing: >=80 → owner SMS direct + ring nearest crew + tag "hot" 50-79 → standard pipeline + automated nurture <50 → drip nurture only, no owner ping
Call recording → CRM loop
Every call summarized in 4 lines, automatically.
After every inbound or outbound call, the recording goes through Whisper for transcription and Sonnet for a 4-line summary that lands in the GHL contact's notes. Prep time before callbacks drops from 4 minutes to 20 seconds. Compounds across hundreds of calls per month.
- 1. Twilio recordingStatusCallback fires when the call ends.
- 2. CF Worker validates Twilio HMAC signature, fetches the WAV via signed URL, streams into Cloudflare R2.
- 3. Cloudflare Workers AI: whisper-large-v3-turbo transcribes (~11k neurons per minute of audio).
- 4. Anthropic Sonnet 4.6 summarizes: 4 lines + sentiment_score (-1..1) + flags ["mentions_competitor", "asked_for_callback", "ready_to_book"].
- 5. PATCH the GHL contact: append note, set last_call_summary, last_call_sentiment, last_call_recording_url.
- 6. If sentiment < 0.3 OR ["complaint", "competitor_chosen"] flagged → Telegram alert to owner with playback link.
Average end-to-end latency: 18 seconds post-call. Cost at 500 calls/month × 4min/call ≈ $0 (within Cloudflare's $5/mo Workers AI tier) + ~$2 of Anthropic API.
NWS storm-event auto-publish
From NWS confirmation to live landing page in ~30 minutes.
Most DFW competitors react to storms a day later. This pipeline drafts the storm landing page within minutes of the NWS Local Storm Report being published - owner reviews on phone, taps Approve, page is live before the chaser convoy hits the affected zips.
[CF Cron Trigger every 15 min] ↓ [fetch NWS LSR feed for FWD WFO + DFW counties] https://mesonet.agron.iastate.edu/cgi-bin/request/gis/lsr.py ?wfo=FWD&recent=3600&fmt=json ↓ [filter: typetext=HAIL, magf>=1.5, county IN (Collin, Dallas, Denton, Tarrant, Rockwall)] ↓ [match: county+date against existing src/content/storms/*.mdx] ↓ [if no match: draft new storm record] - generate slug: {city}-{date}-hail - extract neighborhoods from polygon overlap with US Census TIGER ZCTA - draft copy via Claude API (LSR text + city + hail size) - POST to D1: storms_drafts table, status=pending_review ↓ [Telegram alert to owner: "⚡ New hail event in Plano, 2.25in. Approve to publish?"] ↓ [Owner taps "Publish" → GitHub commit + CF Pages deploy → live in 90s] ↓ [Bonus: GHL workflow auto-fires AI-call list filtered to ZIPs in storm polygon]
Phased delivery
Three phases. Each shippable independently.
Phase 1 - Demo (now)
- · Astro site core, all 12 pages
- · /api/lead Pages Function (Turnstile + GHL upsert + Sheets mirror)
- · 12 GHL Phase-1 custom fields + 5 pipeline stages
- · Filing Calculator, Verify-a-Roofer, Priority List, /vault, /who-called, /h, /admin mock
- · Source-tag URL JS + first-touch attribution
- · One sample storm landing page (Plano 2026-04-26-hail) demonstrating the pattern
Target: ~6-8 hours of build. Ships before the proposal call.
Phase 2 - Build (week 2-4)
- · Full 30-field GHL schema + 15-stage pipeline + every workflow
- · Twilio call-recording → Whisper → Sonnet pipeline live
- · VAPI/Bland/Synthflow handoff fully integrated
- · Lead-scoring engine + routing logic deployed
- · Owner dashboard polished (all 9 widgets, real data)
- · AccuLynx bidirectional sync (one-way push GHL→prod, hourly pull prod→GHL)
- · TCPA consent capture + audit log
Target: 80-120 hours engagement.
Phase 3 - Compounding (month 2+)
- · NWS auto-publish wiring (the showpiece)
- · Drone-footage hosting via Cloudflare Stream
- · Real Pre-Storm Photo Vault (Nearmap or EagleView paid imagery)
- · Did-You-Get-Called live data via Robokiller/Truecaller commercial agreement
- · Carrier-adaptive form (6 conditional experiences)
- · Adjuster Co-Pilot homeowner portal
- · Anniversary drone re-flyover automation
- · Internal Adjuster Heatmap (per-adjuster approval rate intelligence)
Compounding moat. Quote per feature.
Compliance baked in
Texas-specific legal layer, on every form and contract flow.
Most freelancers don't know these laws exist. The site handles each correctly - both as a credibility signal and as a competitive moat.
-
HB 2102 deductible disclosure
Effective 2019. Every quote PDF + LP form footer states the homeowner must pay full deductible. Class B misdemeanor to waive.
-
Ch.601 / Ch.27 (RCLA) 3-day cancellation
Door-signed contracts get a printable Notice of Cancellation in 10pt boldface per BC §601.052.
-
Public-adjuster prohibition (TX § 4102.163)
Site copy never says "we negotiate with your insurance." Instead: "we document, you stay in control of the claim."
-
TCPA SMS consent
Form has separate unchecked checkbox; consent record stored with timestamp + IP + user-agent.
-
STOP/HELP handling
GHL native, plus tcpa_optout flag suppresses all future automated messaging across all storm events.
-
Door-to-door permits (Plano, Frisco, McKinney)
Doorhangers list permit number. Crew member name visible on storm-LP for permit cross-check.
-
TDI Fraud Unit alignment
Site links to FraudUnit@tdi.texas.gov from the Verify-a-Roofer page. We send homeowners to authority.
-
RCAT voluntary cert reframe
Homepage explains TX has no state license. Honest framing outperforms vague "licensed and insured."