From 8a55860e86b73076ac1a9f8f982dcf258d73f917 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:58:48 +0000 Subject: [PATCH 1/2] Initial plan From 9d11d741f4e6c0f29529d7dafc93980eea955aeb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Mar 2026 20:12:28 +0000 Subject: [PATCH 2/2] fix(ui): update plan descriptions to reflect per-user pricing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Rewrite taglines and feature lists for all four tiers with concrete, use-case-driven copy (freelancers, knowledge workers, power users — no team or enterprise framing) - Rename 'Business' display name to 'Power' (plan_id stays 'business' for backwards DB compatibility) - Replace 'enterprise' language in pricing page hero with 'per person, per month' copy - Swap fa-building icon for fa-bolt on the Power tier - Fix support level for Power tier to 'Priority' (was 'Dedicated') - Update docs/SubscriptionTiers.md with new names, table, and intended-use-case section - Add test_business_tier_display_name_is_power assertion Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com> --- app/utils/subscription.py | 32 ++++++++++++++++------------ docs/SubscriptionTiers.md | 13 ++++++++++- frontend/templates/pricing.html | 4 ++-- frontend/templates/subscription.html | 2 +- tests/test_subscription.py | 12 ++++++++--- 5 files changed, 42 insertions(+), 21 deletions(-) diff --git a/app/utils/subscription.py b/app/utils/subscription.py index 6f7ea841..40dd8123 100644 --- a/app/utils/subscription.py +++ b/app/utils/subscription.py @@ -1,11 +1,12 @@ """ Subscription tier definitions and enforcement utilities for DocuElevate SaaS. +All plans are priced per user per month (or per year with ~20 % discount). Four tiers (prices ex-VAT; German customers +19 % MwSt): - free $0/mo — 50 lifetime docs, 150 lifetime OCR pages, 1 dest - starter $2.99/mo — 50/mo, 300 OCR pp/mo, 2 dests, 1 mailbox - professional $5.99/mo — 150/mo, 750 OCR pp/mo, 5 dests, 3 mailboxes - - business $7.99/mo — 300/mo, 1500 OCR pp/mo, 10 dests, unlimited mailboxes + - power $7.99/mo — 300/mo, 1500 OCR pp/mo, 10 dests, unlimited mailboxes Limits use 0 to represent "unlimited". All paid tiers include a 30-day free trial (trial_days field). @@ -14,14 +15,14 @@ All paid tiers include a 30-day free trial (trial_days field). Infrastructure: CX32 (app+Redis €7.59) + CX22 (worker €3.79) + BX21 (storage €7.22) ≈ $24/mo At 100 users infra share ≈ $0.24/user/mo. - Starter : OCR $0.45 + AI $0.012 + infra $0.24 + Stripe $0.34 = $1.04 → 65 % gross margin + Starter : OCR $0.45 + AI $0.012 + infra $0.24 + Stripe $0.34 = $1.04 → 65 % gross margin Professional: OCR $1.13 + AI $0.035 + infra $0.24 + Stripe $0.42 = $1.82 → 70 % gross margin - Business : OCR $2.25 + AI $0.069 + infra $0.24 + Stripe $0.48 = $3.04 → 62 % gross margin + Power : OCR $2.25 + AI $0.069 + infra $0.24 + Stripe $0.48 = $3.04 → 62 % gross margin -After ~30 % German corporate tax: Starter 45 %, Professional 49 %, Business 43 %. +After ~30 % German corporate tax: Starter 45 %, Professional 49 %, Power 43 %. At average usage (~40 % of quota) margins improve to 55-65 % after tax. -⚠ If GPT-4o (not mini) is configured, Business AI cost at max rises to ~$1.92/user, +⚠ If GPT-4o (not mini) is configured, Power AI cost at max rises to ~$1.92/user, reducing after-tax margin to ~33 %. Recommend GPT-4o mini as default in production. """ @@ -46,7 +47,7 @@ TIER_DEFAULTS: dict[str, dict[str, Any]] = { "free": { "id": "free", "name": "Free", - "tagline": "Explore DocuElevate at no cost", + "tagline": "Try DocuElevate free — no credit card needed", "price_monthly": 0, "price_yearly": 0, "trial_days": 0, @@ -75,7 +76,8 @@ TIER_DEFAULTS: dict[str, dict[str, Any]] = { "starter": { "id": "starter", "name": "Starter", - "tagline": "Perfect for individuals getting started", + # Use case: freelancer sending ~50 invoices, contracts, or scanned receipts a month + "tagline": "Perfect for freelancers and side-project owners", "price_monthly": 2.99, "price_yearly": 28.99, # ≈ 80 % of monthly × 12 — save ~19 % (≈ 2½ months free) "trial_days": 30, @@ -89,7 +91,7 @@ TIER_DEFAULTS: dict[str, dict[str, Any]] = { "max_mailboxes": 1, "api_access": True, "features": [ - "50 documents / month", + "50 documents / month — invoices, contracts, receipts", "2 storage destinations", "300 OCR pages / month", "25 MB max file size", @@ -104,7 +106,8 @@ TIER_DEFAULTS: dict[str, dict[str, Any]] = { "professional": { "id": "professional", "name": "Professional", - "tagline": "For growing teams that need more power", + # Use case: consultant or knowledge worker handling ~150 docs/month across multiple platforms + "tagline": "For knowledge workers managing documents daily", "price_monthly": 5.99, "price_yearly": 57.99, # ≈ 80 % of monthly × 12 — save ~19 % "trial_days": 30, @@ -118,7 +121,7 @@ TIER_DEFAULTS: dict[str, dict[str, Any]] = { "max_mailboxes": 3, "api_access": True, "features": [ - "150 documents / month", + "150 documents / month — reports, contracts, invoices", "5 storage destinations", "750 OCR pages / month", "100 MB max file size", @@ -133,8 +136,9 @@ TIER_DEFAULTS: dict[str, dict[str, Any]] = { }, "business": { "id": "business", - "name": "Business", - "tagline": "High-volume processing for organisations", + "name": "Power", + # Use case: power user — real estate agent, bookkeeper, or researcher processing ~10 docs/day + "tagline": "For power users with high-volume document workflows", "price_monthly": 7.99, "price_yearly": 76.99, # ≈ 80 % of monthly × 12 — save ~20 % "trial_days": 30, @@ -148,7 +152,7 @@ TIER_DEFAULTS: dict[str, dict[str, Any]] = { "max_mailboxes": 0, # unlimited mailboxes "api_access": True, "features": [ - "300 documents / month", + "300 documents / month — ~10 documents per day", "10 storage destinations", "1,500 OCR pages / month", "Unlimited file size", @@ -156,7 +160,7 @@ TIER_DEFAULTS: dict[str, dict[str, Any]] = { "Unlimited email ingestion mailboxes", "All ingestion methods", "Webhooks & full API access", - "Dedicated support", + "Priority support", ], "cta": "Start free trial", "badge": "Best Value", diff --git a/docs/SubscriptionTiers.md b/docs/SubscriptionTiers.md index d013fff7..f73b443d 100644 --- a/docs/SubscriptionTiers.md +++ b/docs/SubscriptionTiers.md @@ -2,6 +2,8 @@ DocuElevate uses database-backed subscription plans that are fully configurable by admins via the **Plan Designer** at `/admin/plans`. Four default tiers are seeded automatically on first startup. +All plans are priced **per user, per month** (or per year with ~20 % discount). There are no team, business, or enterprise tiers — every plan is a single-user subscription. + ## Default Plans | Plan | Monthly | Yearly | Docs/Month | Lifetime Docs | OCR Pages/Mo | Max File | Mailboxes | Destinations | @@ -9,12 +11,21 @@ DocuElevate uses database-backed subscription plans that are fully configurable | **Free** | $0 | $0 | — | 50 total | 150 total | 5 MB | 0 | 1 | | **Starter** | $2.99 | $28.99 | 50 | — | 300 | 25 MB | 1 | 2 | | **Professional** | $5.99 | $57.99 | 150 | — | 750 | 100 MB | 3 | 5 | -| **Business** | $7.99 | $76.99 | 300 | — | 1,500 | Unlimited | Unlimited | 10 | +| **Power** | $7.99 | $76.99 | 300 | — | 1,500 | Unlimited | Unlimited | 10 | > Prices ex-VAT. German customers add 19% MwSt. All paid plans include a **30-day free trial**. +### Intended Use Cases + +- **Free** — Try DocuElevate with no commitment. Good for one-off experiments or evaluating the service. +- **Starter** — Freelancers and side-project owners sending ~50 invoices, contracts, or scanned receipts a month. +- **Professional** — Knowledge workers (consultants, paralegals, accountants) handling ~150 multi-page documents a month across several cloud destinations. +- **Power** — Power users with heavy daily workloads: real estate agents, bookkeepers, or researchers processing ~10 documents a day (≈ 300/month) with no file-size restrictions. + +> The **plan_id** in the database remains `"business"` for the Power tier to preserve backwards compatibility. The display name shown to users is "Power". + ## How Plans Are Stored Plans are stored in the `subscription_plans` database table. On application startup, `seed_default_plans()` is called automatically — if the table is empty, the four built-in defaults are inserted. If plans already exist, the seed is a no-op. diff --git a/frontend/templates/pricing.html b/frontend/templates/pricing.html index 17ff6e06..b6cae447 100644 --- a/frontend/templates/pricing.html +++ b/frontend/templates/pricing.html @@ -14,7 +14,7 @@ Choose the plan that's right for you

- From free exploration to unlimited enterprise processing — scale as your document workflows grow. + One price per person, per month — from casual exploration to power-user workflows. No team plans, no per-seat tiers.

@@ -318,7 +318,7 @@ Community Email Priority email - Dedicated + Priority diff --git a/frontend/templates/subscription.html b/frontend/templates/subscription.html index ef9a476b..ce3f89d6 100644 --- a/frontend/templates/subscription.html +++ b/frontend/templates/subscription.html @@ -42,7 +42,7 @@ {% if tier_id == 'free' %}fa-seedling text-gray-500 {% elif tier_id == 'starter' %}fa-rocket text-blue-600 {% elif tier_id == 'professional' %}fa-star text-indigo-600 - {% else %}fa-building text-purple-600{% endif %} + {% else %}fa-bolt text-purple-600{% endif %} text-2xl" aria-hidden="true">

Current Plan

diff --git a/tests/test_subscription.py b/tests/test_subscription.py index 2f3a66c1..599137e8 100644 --- a/tests/test_subscription.py +++ b/tests/test_subscription.py @@ -107,7 +107,7 @@ def test_free_tier_has_no_mailboxes(): @pytest.mark.unit def test_business_tier_has_highest_limits(): - """Business tier must have the highest limits of all paid tiers.""" + """Power tier (plan_id 'business') must have the highest limits of all paid tiers.""" t = TIERS["business"] # lifetime: no hard cap (0 = unlimited) assert t["lifetime_file_limit"] == 0 @@ -121,9 +121,15 @@ def test_business_tier_has_highest_limits(): assert t["max_file_size_mb"] == 0 +@pytest.mark.unit +def test_business_tier_display_name_is_power(): + """The 'business' plan_id must display as 'Power'.""" + assert TIERS["business"]["name"] == "Power" + + @pytest.mark.unit def test_mailbox_limits_increase_by_tier(): - """Mailbox limits must increase across tiers: free=0, starter=1, professional=3, business=0(inf).""" + """Mailbox limits must increase across tiers: free=0, starter=1, professional=3, power/business=0(inf).""" assert TIERS["free"]["max_mailboxes"] == 0 assert TIERS["starter"]["max_mailboxes"] == 1 assert TIERS["professional"]["max_mailboxes"] == 3 @@ -144,7 +150,7 @@ def test_free_tier_has_no_trial(): @pytest.mark.unit def test_pricing_order(): - """Paid tier prices must increase in order: starter < professional < business.""" + """Paid tier prices must increase in order: starter < professional < power.""" assert TIERS["starter"]["price_monthly"] < TIERS["professional"]["price_monthly"] assert TIERS["professional"]["price_monthly"] < TIERS["business"]["price_monthly"]