Fantastica AI Assistant: technical overview
Independent Rainier Labs technology demonstration built using publicly available Fantastica Water Solutions information. This is not an official Fantastica Water Solutions service. Fantastica has not reviewed, endorsed or authorized it.
- Live demo (Fantastica's site with the assistant on top): https://fantastica.rainierlabs.io/
- About the demo: https://fantastica.rainierlabs.io/demo
- AI phone demo: (510) 340-2594
- Knowledge sources: https://fantastica.rainierlabs.io/sources
- Health: https://fantastica.rainierlabs.io/health
Purpose
Fantastica Water Solutions sells and installs water softeners, reverse osmosis (RO) systems, whole-house filtration, commercial systems, water coolers and replacement filters across the San Francisco Bay Area. Prospective customers usually arrive with a problem ("my water is hard", "the tap water tastes like chlorine") rather than a product name.
This demo shows how an AI assistant could, 24/7, on the website and on the phone:
- answer questions using only what Fantastica publishes,
- help customers understand which kind of system fits their concern,
- qualify interest, and
- turn it into a structured consultation or callback request.
Business value
Current manual path
Customer → website → form / call → wait for response → Fantastica employee qualifies lead
AI-assisted path
Customer → chat / phone → instant answer → education → qualification → consultation request → structured lead → Fantastica follows up
The assistant does the repetitive first conversation (what's the difference between RO and a whole-house filter, do you service my city, how much is this model) at any hour. Staff receive a lead that already contains the name, contact details, ZIP, concern, product interest, preferred time and an AI summary, instead of a blank form.
We have not measured conversion, response-time or revenue impact for Fantastica, so this demo makes no ROI claims.
System architecture
fantasticawatersolutions.com (WordPress + WooCommerce, public)
│ allowlisted crawler (robots.txt, rate-limited)
▼
┌──────────── Knowledge sync (server/knowledge) ──────────────┐
│ WP REST pages → text, Elementor price cards, FAQs, contacts │
│ WooCommerce Store API → product catalog + links │
│ chunk → Azure OpenAI embeddings → Postgres + pgvector │
└──────────────────────────────┬───────────────────────────────┘
▼
Postgres (Railway, pgvector) ◄── admin dashboard (/admin)
▲
┌──────────── Shared brain (server/brain) ─────────────────────┐
│ router (intent + water concern) → product / service-area │
│ lookup + hybrid retrieval (vector + full text) → Azure │
│ OpenAI answer → policy enforcement (prices, appointments, │
│ transfers) → lead extraction + qualification → notification │
└───────────────┬──────────────────────────────┬───────────────┘
│ │
Web chat (SSE): widget.js → Retell voice agent: +1 510-340-2594
/assistant iframe → /api/chat → tools /api/voice/ask, /api/voice/lead
→ webhooks /api/retell/inbound, /api/retell/webhook
One Node.js (Express, TypeScript) service hosts everything: the demo pages, chat API, voice tool endpoints, Retell webhooks, admin dashboard and the scheduled knowledge sync.
| Path | What it is |
|---|---|
/ |
Fantastica's public website in a full-page frame with the chat launcher (bottom right) and a disclaimer ribbon |
/demo |
Rainier demo landing page: examples, Try Chat, Call AI Demo, How It Works |
/assistant |
Full-page chat (also the iframe the widget opens) |
/widget.js |
Embeddable launcher |
/sources |
Every indexed page, product and sync run |
/admin |
Lead dashboard (password protected) |
/health |
Health and knowledge status (JSON) |
/readme |
This document |
Fantastica source ingestion
server/knowledge/sync.ts (run at startup when the knowledge base is empty, every SYNC_INTERVAL_HOURS, or with npm run sync:all):
- Allowlist only. The fetcher refuses any host other than
fantasticawatersolutions.com/www., blocks off-site redirects, honors robots.txt, never follows cart, checkout or account URLs, and waitsCRAWL_DELAY_MSbetween requests. - Pages come from the WordPress REST API (falls back to the page sitemap). Navigation, headers and footers are stripped so every chunk is real content.
- Published prices are extracted from the Elementor price cards on the category pages (e.g. water softeners, residential RO, whole-house filters, commercial RO, water coolers, replacement filters), including the "+ Installation Fee" note.
- FAQs, contacts and service counties are extracted from the home and contact pages.
- Services are recorded only when the site's own text supports them.
- Incremental: content hashes mean unchanged pages are not re-embedded; additions, changes, removals and failures are logged in
source_changes, and every run insource_sync_runs. - Safe failure: if the site is unreachable, existing knowledge is kept.
- Bot protection: fantasticawatersolutions.com's host (SiteGround) answers datacenter IPs, such as Railway's, with a challenge (HTTP 202) instead of the page. Every successful fetch is stored in
source_snapshots. When a live fetch is challenged, the crawler uses the last good response for that URL and records which URLs it still needs.npm run sync:push(run from a network the site allows, usingADMIN_PASSWORD) triggers a server sync, fetches exactly the requested allowlisted URLs with the same polite crawler, uploads them to/admin/api/snapshots, and repeats until nothing is missing. The server only accepts HTTP 200 responses for allowlisted Fantastica URLs. If Fantastica authorizes the crawler (or allowlists the server IP), live fetches simply take over.
WordPress / WooCommerce integration
Read-only, public endpoints only: /wp-json/wp/v2/pages, /wp-json/wc/store/v1/products and the sitemaps. No credentials, plugins or changes on Fantastica's site.
WooCommerce product prices are published as $0 (Fantastica prices on the category pages instead), so the assistant never uses WooCommerce prices. It uses the price cards and links each card to its WooCommerce product page when the model matches.
RAG (retrieval-augmented generation)
- Route: a deterministic router classifies the intent (product, price, comparison, service area, consultation, callback, transfer, …) and the water concern (hard water, chlorine taste, well water, …).
- Structured lookups: products by model or category with their published price; service area from the published county list plus a Bay Area city→county map.
- Hybrid retrieval over
knowledge_chunks: pgvector cosine similarity + Postgres full-text search, merged with reciprocal-rank fusion. - Grounded generation: the model receives only these facts and must not go beyond them.
- Policy enforcement after generation: any dollar amount not present in the published facts is replaced with "Fantastica would need to provide a quote"; appointment wording is forced to "Your preferred time has been recorded. This demonstration does not confirm an appointment."; transfer requests get the demo wording; voice output is stripped of URLs and markdown.
- Sources: up to three links (View product / Source / Learn more), always to fantasticawatersolutions.com.
If nothing relevant is found, the assistant says it couldn't verify that from Fantastica's published information.
Azure OpenAI
Uses Rainier's existing Azure OpenAI resource: gpt-4o for answers and lead extraction, text-embedding-3-large at 1,536 dimensions for embeddings. Without Azure credentials the app still runs in a retrieval-only fallback mode.
Web chatbot
- Same look and feel as the Rainier DSHS Navigator chat: gradient launcher pill, resizable panel, expand/close and new-conversation buttons, streamed answers, product cards with published prices and links. The header shows the water-droplet mark and "FANTASTICA AI Assistant".
- Speaks as part of Fantastica: answers state facts directly ("Fantastica serves Alameda County", "The S-650 is $2,511.75 + Installation Fee") and never say "the website lists…" or "according to the website". The system prompt requires this, and
speakAsSite()rewrites any leftover phrasing after generation. The demo disclaimers stay in the page header and footer. - Streams over Server-Sent Events. The conversation id lives in
sessionStorage; history is stored server-side so follow-ups ("how much is that one?") work. - Rate limited per IP (
CHAT_RATE_LIMITper minute). All model output is rendered as text, never HTML; links are allowed only to the Fantastica domain.
Installing the widget on WordPress (future, after authorization)
<script src="https://fantastica.rainierlabs.io/widget.js" data-widget="fantastica" defer></script>
- Custom HTML block: add a Custom HTML block to a page and paste the snippet (single page only).
- Footer script: Appearance → Theme File Editor / theme options "footer scripts" (or Elementor → Custom Code →
</body>) to load it site-wide. - Plugin / snippet: a header-and-footer plugin such as WPCode → add snippet → Footer.
- Google Tag Manager: Custom HTML tag with the snippet, trigger All Pages.
Then add the site's origin to WIDGET_EMBED_ORIGINS so the chat iframe may be framed there. Nothing has been installed on Fantastica's live site.
Voice agent
- Provider: Retell AI (same stack as Rainier's AFHC and DSHS demos). Number: +1 (510) 340-2594, dedicated to this demo.
- Opening: "Hi, thanks for calling Fantastica Water Solutions! Just so you know, this is an AI demo line for testing purposes. How can I help you today?" After this one-time disclosure the agent speaks as a member of the Fantastica team ("we", "our") and repeats the disclosure only if asked who it is.
- Retell handles speech, turn-taking and interruptions. Its LLM calls our tools:
fantastica_knowledge→POST /api/voice/ask, the sameAssistant.answer()as chat, with voice formatting.save_consultation_request→POST /api/voice/lead, the sameLeadServiceas chat. The caller's number is the default callback number.record_sms_consent+ Retellsend_sms: only whenRETELL_SMS_ENABLED=true.end_call.
POST /api/retell/inbound(number-level webhook) runs an abuse/cost guard before the call connects: per-caller hourly and daily limits, daily minutes, optional monthly cap, and a block list. The guard keys its counters by a hashed caller number.POST /api/retell/webhookstores call start/end, the caller number, the recording URL, the transcript (as a conversation), Retell's post-call analysis and a structured call summary, and enriches or creates the lead from the transcript.- All Retell requests are verified with the
x-retell-signatureHMAC. - No transfers: "Since this is a demo line, I can't transfer calls, but I can take a callback request for you."
npx tsx scripts/replay-retell-call.ts <call_id>(withBASE_URL) re-fetches a call from the Retell API and replays its signed webhooks. Use it to restore a call that was deleted or missed. Leads are re-linked bycall_id, not duplicated.
npm run retell:sync creates or updates the Retell LLM and agent from retell/agent.json, publishes it, and binds +15103402594 (the script refuses to touch any other number).
Shared conversation engine
Chat and voice use one Assistant (server/brain/assistant.ts), one KnowledgeService, one LeadService and one conversations table (channel = CHAT | VOICE). Only the presentation differs: markdown and cards for chat, short URL-free sentences for voice.
Lead capture
- On every chat turn, an extraction pass (Azure OpenAI, strict zod schema) pulls name, phone, email, ZIP, city, property type, concern, service and product interest, preferred contact method and time, and the consultation/callback request.
- Anti-hallucination: contact details are kept only if they literally appear in what the customer typed.
- Lead statuses: NEW, QUALIFYING, PARTIAL, QUALIFIED, CONSULTATION_REQUESTED, CALLBACK_REQUESTED, NOT_READY. Status never regresses. Staff statuses: OPEN, CONTACTED, QUALIFIED, CLOSED.
- The assistant asks only for missing fields, one or two at a time, and stops if the customer declines.
SMS
Implemented with explicit consent ("I can text that information to the number you're calling from. Would you like me to?"). Consent is stored on the lead and in sms_messages, and the text is non-marketing (acknowledgement, a Fantastica page link or a consultation summary, plus "Reply STOP to opt out").
It is off (RETELL_SMS_ENABLED=false) until the number completes A2P 10DLC registration. Web chat has no SMS.
Consultation workflow
- The customer shows buying intent (a price for their home, a water test, an installation, "can someone call me").
- The assistant offers a free consultation and collects name, best phone (or email), ZIP or city, concern, and optionally a preferred time and home vs. business.
- It records the request, says "Your preferred time has been recorded. This demonstration does not confirm an appointment.", and mentions that, because this is a demo line, the request is saved for testing (it goes to the Rainier demo dashboard, not to Fantastica).
- A notification (if configured) goes to
DEMO_LEAD_NOTIFICATION_EMAIL, a Rainier-controlled inbox. Fantastica is never emailed.
There is no scheduling integration. Scheduling would be designed around Fantastica's real process if they adopt the system.
Admin dashboard
/admin uses HTTP Basic auth (ADMIN_PASSWORD; returns 404 when unset). The header menu has two pages: Dashboard and Knowledge sources.
- Date filter: a From/To range (Pacific time) with presets: Month to date (default), Today, Last 7 days, Last 30 days, Last month and Year to date. The tiles, the results table and the insights all use this range, and it carries through every link, form and detail page.
- Insights (top concerns, services/products, ZIP codes for the range) sit at the top, above the tiles.
- Tiles: conversations, chats, calls, leads, qualified, consultation, callback and failed. Tile counts are filtered by the date range. The tiles stay at the top of every dashboard page. Clicking a tile highlights it and shows its rows in the table below (
/admin?v=<tile>&from=…&to=…). - Phone calls view (same layout as the AFHC dashboard): columns When / From / Duration / Summary & transcript, with a lead chip (name · status), a collapsible transcript and an audio player for the recording. Filters: search (number, summary, transcript, lead name), length (under 30s, 30s–2 min, over 2 min), "Has transcript", and a results count. Recordings are streamed through
GET /admin/calls/:id/recording. This same-origin proxy is admin-only, allows only https Retell/AWS hosts, supports Range requests, and fetches a fresh URL from the Retell API when the stored link has expired. - Bulk delete: select one or more rows (or select all) and click Delete selected. Deleting a conversation or call also removes its call sessions and events; leads linked to it are kept. A lead can also be deleted from its detail page.
- Knowledge sources page (
/admin/sources): sync status, pages and products, plus Refresh all knowledge sources (POST /admin/sync). The button is currently disabled (KNOWLEDGE_REFRESH_BUTTON_ENABLED = falseinserver/admin/routes.ts); the backend still works, andnpm run sync:pushremains the normal refresh path. When enabled, the page refreshes itself until the sync finishes and then shows how many pages were fetched live and how many came from saved copies. - Lead detail: every field, AI summary, call summary, full transcript, status buttons (contacted / qualified / closed) and notes.
- All changes are CSRF-protected and audited in
audit_events. Admin CSS/JS are cache-busted per deploy (?v=). - Live acceptance tests delete the conversations, calls and leads they create, so test runs don't inflate the tiles.
Request callback (web chat)
A Request callback button sits just below the chat title. It opens an inline form for name, phone or email, ZIP, best time and a message (prefilled with the last question). The form posts to POST /api/callback, which validates the fields, rate-limits requests (5 per 10 minutes per client), and records a CALLBACK_REQUESTED lead through the same LeadService as chat and voice.
Security
- Secrets only in environment variables.
.envis gitignored, andnpm run secret-scanchecks the working tree and the full git history. - Strict Content-Security-Policy (no inline scripts), HSTS, nosniff.
frame-ancestorsis restricted, and/may frame only fantasticawatersolutions.com. - Retell webhooks and tools require a valid HMAC signature.
- All SQL is parameterized. Input is validated with zod and has size limits. Rate limits apply to chat and phone.
- All user and model text is escaped or rendered as text.
- The crawler is domain-allowlisted and robots-aware.
Privacy
- Collects only what is needed for a consultation request.
- Phone calls to the demo line store the caller number and the Retell recording link so staff can review calls in the admin dashboard (admin-only, like AFHC). The abuse guard uses keyed hashes.
- Logs mask phone numbers and emails.
- No payment data. No health diagnosis, only "a water test would confirm".
- Demo data is used only to demonstrate the system.
Deployment
- Railway project
fantastica: servicefantastica(this app, Nixpacks/Node 20) pluspgvector-railway(Postgres 16 + pgvector). This database is separate from every other Rainier project. - Migrations run automatically at startup (advisory-locked). The initial knowledge sync starts automatically when the knowledge base is empty.
- Domain:
fantastica.rainierlabs.io(CNAME at the rainierlabs.io DNS provider → the Railway target). - Commands:
npm run build,npm start,npm run sync:all,npm run sync:push,npm run retell:sync,npm test,npm run test:live,npm run secret-scan.
Key environment variables (see .env.example):
DATABASE_URLAZURE_OPENAI_*RETELL_API_KEY,RETELL_AGENT_ID,RETELL_LLM_ID,RETELL_PHONE_NUMBER=+15103402594,RETELL_SMS_ENABLEDVOICE_*guard limitsADMIN_PASSWORDDEMO_LEAD_NOTIFICATION_EMAIL,RESEND_API_KEY,EMAIL_FROMPUBLIC_BASE_URL,SYNC_INTERVAL_HOURS
Source freshness
- Every document stores
retrieved_at,last_changed_atandlast_verified_at. /sourcesshows each page, product and published price, the last ten sync runs and recent changes./healthreports document, chunk, product and FAQ counts and the last sync.- Automatic refresh runs every
SYNC_INTERVAL_HOURS(default 24).
Limitations
- Knows only what Fantastica publishes. There are no published business hours, and many products have no public price ("Fantastica would need to provide a quote").
- The site's bot protection blocks the server's own crawler, so freshness depends on
npm run sync:pushbeing run (or scheduled) from an allowed network./sourcesshows when each page was last fetched. - Service area is based on the published county list. Cities outside it are reported as "couldn't verify", not "no".
- No real scheduling, CRM, transfer or delivery to Fantastica: requests stay in the demo dashboard.
- SMS is disabled until carrier registration.
- AI answers can still be imperfect. The assistant is instructed and post-processed to stay within published facts, but it isn't a substitute for Fantastica's staff or a water test.
What would change if Fantastica adopted it
- Remove the demo disclaimer after authorization.
- Embed the widget on the actual WordPress website.
- Configure official business phone forwarding (or port/forward a number to the agent).
- Configure approved transfer behavior (when and to whom).
- Send leads to Fantastica (email to their team, or their CRM).
- Connect email/SMS notifications (with A2P registration for SMS).
- Determine their actual scheduling process.
- Add scheduling integration only if useful.
- Determine whether they use a CRM.
- Integrate the CRM only if useful.
None of these integrations has been built.
Built by Rainier Labs. No GoHighLevel or other third-party CRM is used.