Merge pull request #502 from christianlouis/copilot/update-plan-descriptions
fix(ui): rename "Business" → "Power" and rewrite plan copy for per-user pricing
This commit is contained in:
+18
-14
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
Choose the plan that's right for you
|
||||
</h1>
|
||||
<p class="text-indigo-100 text-lg max-w-2xl mx-auto">
|
||||
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.
|
||||
</p>
|
||||
|
||||
<!-- Annual / Monthly toggle (cosmetic — actual billing handled separately) -->
|
||||
@@ -318,7 +318,7 @@
|
||||
<td class="px-4 py-3 text-center text-xs text-gray-500">Community</td>
|
||||
<td class="px-4 py-3 text-center text-xs text-gray-700">Email</td>
|
||||
<td class="px-4 py-3 text-center text-xs text-gray-700 font-medium">Priority email</td>
|
||||
<td class="px-4 py-3 text-center text-xs text-indigo-700 font-bold">Dedicated</td>
|
||||
<td class="px-4 py-3 text-center text-xs text-indigo-700 font-bold">Priority</td>
|
||||
</tr>
|
||||
|
||||
</tbody>
|
||||
|
||||
@@ -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"></i>
|
||||
</div>
|
||||
<p class="text-xs font-semibold text-gray-400 uppercase tracking-widest">Current Plan</p>
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
Reference in New Issue
Block a user