Add retail catalog add-ons to /web_design estimator.
CI / test (pull_request) Successful in 12s
Unit Tests / test (pull_request) Successful in 12s

Closes #31. Storefront, POS sync, event tickets, and shipping now appear
in the package table with the same auto-select deps as the Django template.
This commit is contained in:
2026-09-06 06:23:42 -05:00
parent 85369914c8
commit ff2b0952ec
4 changed files with 128 additions and 7 deletions
@@ -361,8 +361,10 @@
'Sites are brand-tailored; clients own the site and data. Public site and client portal are ' + 'Sites are brand-tailored; clients own the site and data. Public site and client portal are ' +
'always included, along with three-instance hosting, UTM/leads, SEO/accessibility/LLM readiness, ' + 'always included, along with three-instance hosting, UTM/leads, SEO/accessibility/LLM readiness, ' +
'and Grafana metrics/alerts. Pass optional feature ids to add Email/SMS, direct mail, blog, ' + 'and Grafana metrics/alerts. Pass optional feature ids to add Email/SMS, direct mail, blog, ' +
'Stripe payments, social consolidation, or AI social generator. Dependencies ' + 'Stripe payments, social, AI social, e-commerce, POS sync, event ticketing, or shipping. ' +
'(payments→email_sms, ai_social→social) are auto-selected. Call with an empty features array ' + 'Dependencies (payments→email_sms, ai_social→social, ecommerce→email_sms+payments, ' +
'pos_sync/shipping→ecommerce, event_ticketing→email_sms+payments) are auto-selected. ' +
'Call with an empty features array ' +
'to list catalog pricing and included-with-every-site benefits.', 'to list catalog pricing and included-with-every-site benefits.',
inputSchema: { inputSchema: {
type: 'object', type: 'object',
@@ -371,7 +373,8 @@
type: 'array', type: 'array',
description: description:
'Optional feature ids to include beyond the required base. Valid ids: ' + 'Optional feature ids to include beyond the required base. Valid ids: ' +
'public_site, client_portal, email_sms, direct_mail, blog, payments, social, ai_social.', 'public_site, client_portal, email_sms, direct_mail, blog, payments, social, ai_social, ' +
'ecommerce, pos_sync, event_ticketing, shipping.',
items: { type: 'string' }, items: { type: 'string' },
}, },
}, },
@@ -2,7 +2,7 @@
{% load static %} {% load static %}
{% block title %}Web Design & Hosting - AI ML Operations, LLC{% endblock %} {% block title %}Web Design & Hosting - AI ML Operations, LLC{% endblock %}
{% block meta_description %}Custom brand-tailored websites with interactive package pricing. You own the site and data. Public site + client portal with UTM/leads included; add Email/SMS, blog, Stripe, social, and more. Three-instance hosting, SEO, accessibility, LLM integration, and Grafana alerts on every build.{% endblock %} {% block meta_description %}Custom brand-tailored websites with interactive package pricing. You own the site and data. Public site + client portal with UTM/leads included; add Email/SMS, blog, Stripe, e-commerce, POS sync, event tickets, shipping, social, and more. Three-instance hosting, SEO, accessibility, LLM integration, and Grafana alerts on every build.{% endblock %}
{% block og_title %}Web Design & Hosting - AI ML Operations, LLC{% endblock %} {% block og_title %}Web Design & Hosting - AI ML Operations, LLC{% endblock %}
{% block og_description %}Build a custom site package with live pricing. You own the site and data. Base site + portal included; three-instance hosting, UTM/leads, SEO, accessibility, LLM readiness, and Grafana metrics on every project.{% endblock %} {% block og_description %}Build a custom site package with live pricing. You own the site and data. Base site + portal included; three-instance hosting, UTM/leads, SEO, accessibility, LLM readiness, and Grafana metrics on every project.{% endblock %}
{% block twitter_title %}Web Design & Hosting - AI ML Operations, LLC{% endblock %} {% block twitter_title %}Web Design & Hosting - AI ML Operations, LLC{% endblock %}
@@ -78,7 +78,7 @@
<img src="{% static 'public/img/web_design/card-5.jpg' %}" alt="Growth Add-ons" <img src="{% static 'public/img/web_design/card-5.jpg' %}" alt="Growth Add-ons"
style="width: 100%; border-radius: 8px; margin-bottom: 1rem;"> style="width: 100%; border-radius: 8px; margin-bottom: 1rem;">
<span class="card-title">Growth Add-ons</span> <span class="card-title">Growth Add-ons</span>
<p class="card-text">Email/SMS, direct mail, blog, Stripe payments, social, and AI social drafts.</p> <p class="card-text">Email/SMS, direct mail, blog, Stripe, e-commerce, POS sync, event tickets, shipping, social, and AI social drafts.</p>
</div> </div>
</div> </div>
</div> </div>
@@ -90,7 +90,7 @@
<h2 class="section-title">Build Your Package</h2> <h2 class="section-title">Build Your Package</h2>
<p class="pricing-intro"> <p class="pricing-intro">
Public site and client portal (with UTM) are always included. Toggle add-ons to see one-time Public site and client portal (with UTM) are always included. Toggle add-ons to see one-time
build and monthly totals. Stripe / SMS / postage usage billed separately. build and monthly totals. Stripe / SMS / postage / EasyPost usage billed separately.
</p> </p>
<div class="pricing-estimator" id="pricingEstimator" <div class="pricing-estimator" id="pricingEstimator"
+78
View File
@@ -246,6 +246,10 @@ class AgenticBrowsingSeoTests(TestCase):
self.assertContains(response, "## Web design & hosting") self.assertContains(response, "## Web design & hosting")
self.assertContains(response, "estimate_web_design_cost") self.assertContains(response, "estimate_web_design_cost")
self.assertContains(response, "email_sms") self.assertContains(response, "email_sms")
self.assertContains(response, "ecommerce")
self.assertContains(response, "pos_sync")
self.assertContains(response, "event_ticketing")
self.assertContains(response, "shipping")
self.assertContains(response, "three instances") self.assertContains(response, "three instances")
self.assertContains(response, "Tailored to your brand") self.assertContains(response, "Tailored to your brand")
self.assertContains(response, "You own the site") self.assertContains(response, "You own the site")
@@ -340,6 +344,8 @@ class WebMcpTests(TestCase):
self.assertContains(response, "data-web-design-pricing=") self.assertContains(response, "data-web-design-pricing=")
self.assertContains(response, "email_sms") self.assertContains(response, "email_sms")
self.assertContains(response, "client_portal") self.assertContains(response, "client_portal")
self.assertContains(response, "ecommerce")
self.assertContains(response, "event_ticketing")
class WebDesignPricingTests(TestCase): class WebDesignPricingTests(TestCase):
@@ -363,6 +369,10 @@ class WebDesignPricingTests(TestCase):
self.assertContains(response, "aimlTrackWhenReady") self.assertContains(response, "aimlTrackWhenReady")
self.assertContains(response, "data-tianji-event-build=") self.assertContains(response, "data-tianji-event-build=")
self.assertContains(response, "data-tianji-event-features=") self.assertContains(response, "data-tianji-event-features=")
self.assertContains(response, "E-commerce storefront")
self.assertContains(response, "POS API sync")
self.assertContains(response, "Event ticketing")
self.assertContains(response, "Shipping automation")
def test_base_estimate_includes_required_features_only(self): def test_base_estimate_includes_required_features_only(self):
from .web_design_pricing import estimate_web_design_cost from .web_design_pricing import estimate_web_design_cost
@@ -392,6 +402,74 @@ class WebDesignPricingTests(TestCase):
self.assertEqual(estimate["one_time_build"], 1400) self.assertEqual(estimate["one_time_build"], 1400)
self.assertEqual(estimate["monthly"], 80) self.assertEqual(estimate["monthly"], 80)
def test_ecommerce_auto_selects_email_sms_and_payments(self):
from .web_design_pricing import estimate_web_design_cost
estimate = estimate_web_design_cost(["ecommerce"])
selected_ids = {item["id"] for item in estimate["selected"]}
self.assertEqual(
selected_ids,
{"public_site", "client_portal", "email_sms", "payments", "ecommerce"},
)
self.assertEqual(estimate["one_time_build"], 2300)
self.assertEqual(estimate["monthly"], 100)
def test_pos_sync_pulls_ecommerce_chain(self):
from .web_design_pricing import estimate_web_design_cost
estimate = estimate_web_design_cost(["pos_sync"])
selected_ids = {item["id"] for item in estimate["selected"]}
self.assertEqual(
selected_ids,
{
"public_site",
"client_portal",
"email_sms",
"payments",
"ecommerce",
"pos_sync",
},
)
self.assertEqual(estimate["one_time_build"], 2800)
self.assertEqual(estimate["monthly"], 120)
def test_event_ticketing_auto_selects_email_sms_and_payments(self):
from .web_design_pricing import estimate_web_design_cost
estimate = estimate_web_design_cost(["event_ticketing"])
selected_ids = {item["id"] for item in estimate["selected"]}
self.assertEqual(
selected_ids,
{
"public_site",
"client_portal",
"email_sms",
"payments",
"event_ticketing",
},
)
self.assertEqual(estimate["one_time_build"], 2000)
self.assertEqual(estimate["monthly"], 80)
def test_shipping_pulls_ecommerce_chain(self):
from .web_design_pricing import estimate_web_design_cost
estimate = estimate_web_design_cost(["shipping"])
selected_ids = {item["id"] for item in estimate["selected"]}
self.assertEqual(
selected_ids,
{
"public_site",
"client_portal",
"email_sms",
"payments",
"ecommerce",
"shipping",
},
)
self.assertEqual(estimate["one_time_build"], 2700)
self.assertEqual(estimate["monthly"], 115)
class TrafficClassificationTests(TestCase): class TrafficClassificationTests(TestCase):
def test_classifies_common_agents(self): def test_classifies_common_agents(self):
+41 -1
View File
@@ -80,6 +80,46 @@ WEB_DESIGN_FEATURES = (
"requires": ("social",), "requires": ("social",),
"requires_note": "Requires Social consolidation — auto-selected.", "requires_note": "Requires Social consolidation — auto-selected.",
}, },
{
"id": "ecommerce",
"name": "E-commerce storefront",
"description": "Catalog, cart, inventory dashboard, Stripe checkout. Requires Email & SMS and Payments.",
"build": 700,
"monthly": 30,
"required": False,
"requires": ("email_sms", "payments"),
"requires_note": "Requires Email & SMS and Payments (Stripe) — auto-selected.",
},
{
"id": "pos_sync",
"name": "POS API sync",
"description": "Keep in-store POS inventory in sync with the online catalog.",
"build": 500,
"monthly": 20,
"required": False,
"requires": ("ecommerce",),
"requires_note": "Requires E-commerce storefront — auto-selected.",
},
{
"id": "event_ticketing",
"name": "Event ticketing",
"description": "Events, capacity limits, digital tickets. Requires Email & SMS and Payments.",
"build": 400,
"monthly": 10,
"required": False,
"requires": ("email_sms", "payments"),
"requires_note": "Requires Email & SMS and Payments (Stripe) — auto-selected.",
},
{
"id": "shipping",
"name": "Shipping automation",
"description": "EasyPost labels + Pirate Ship CSV export. Requires E-commerce storefront.",
"build": 400,
"monthly": 15,
"required": False,
"requires": ("ecommerce",),
"requires_note": "Requires E-commerce storefront — auto-selected.",
},
) )
WEB_DESIGN_INCLUDED = ( WEB_DESIGN_INCLUDED = (
@@ -111,7 +151,7 @@ WEB_DESIGN_INCLUDED = (
WEB_DESIGN_PRICING_DISCLAIMER = ( WEB_DESIGN_PRICING_DISCLAIMER = (
"Draft catalog pricing. Not a formal quote. " "Draft catalog pricing. Not a formal quote. "
"Third-party usage (Stripe fees, SMS, postage) not included." "Third-party usage (Stripe fees, SMS, postage, EasyPost labels) not included."
) )