Initial commit of Descrybe v2 without local scratch artifacts.

Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
This commit is contained in:
2026-08-09 22:47:43 +02:00
commit 8580c996c3
1285 changed files with 325780 additions and 0 deletions
+171
View File
@@ -0,0 +1,171 @@
package billing
import "strings"
// CreditsPerAIProduct is the typical wallet debit for one AI enhance
// (product_processing base + one openai_token_k pack when tokens ≤ 1000).
// Each content-language pass burns another ~CreditsPerAIProduct per product.
// Included monthly grants assume AssumedPrimaryContentLanguages only.
const CreditsPerAIProduct = 2
// AssumedPrimaryContentLanguages is how many content languages the included
// monthly grant is sized for. Extra languages → credit packs or BYOK.
const AssumedPrimaryContentLanguages = 1
// ScaleMaxProducts is the top self-serve SKU ceiling. Huge catalogs (1M+)
// belong on Enterprise (sales-led credits / BYOK), not public Scale.
const ScaleMaxProducts = 12_000
// PlanAICoverPercent is included monthly AI as a share of CreditSKUBase
// (primary language only). Paid public plans use ~50% cover so Starter stays
// lean (100 credits ≈ 50 AI products on a 100-SKU plan) and higher tiers
// scale with CreditSKUBase ≤ PlanMaxProducts — not a full-catalog AI bundle.
// Formula: credits = (CreditSKUBase × cover% / 100) × CreditsPerAIProduct.
func PlanAICoverPercent(planName string) int {
switch strings.ToLower(strings.TrimSpace(planName)) {
case "starter":
return 50 // 100 × 50% × 2 = 100
case "plus":
return 50 // 400 × 50% × 2 = 400
case "growth":
return 50 // 1,200 × 50% × 2 = 1,200
case "business":
return 50 // 4,000 × 50% × 2 = 4,000
case "scale":
return 50 // 12,000 × 50% × 2 = 12,000
case "enterprise":
return 50 // display ladder only; grant is EnterpriseUnlimitedCredits
default:
return 0
}
}
// PlanMaxProducts is the hard SKU ceiling for a public plan name.
// Slow retail ladder for small→mid shops; Scale reaches ScaleMaxProducts;
// Enterprise is unlimited (nil). Credits sized via CreditSKUBase (≤ MaxProducts).
func PlanMaxProducts(planName string) *int {
mp := func(n int) *int { return &n }
switch strings.ToLower(strings.TrimSpace(planName)) {
case "free":
return mp(50)
case "starter":
return mp(100)
case "plus":
return mp(400)
case "growth":
return mp(1_200)
case "business":
return mp(4_000)
case "scale":
return mp(ScaleMaxProducts)
default:
// Enterprise and unknown custom plans — unlimited SKU cap.
return nil
}
}
// CreditSKUBase is the catalog size used ONLY to size included monthly AI credits.
// May be smaller than PlanMaxProducts so large catalogs still get a bounded AI starter grant.
// Public paid ladder: base equals PlanMaxProducts (50% cover → half-catalog primary-lang AI).
func CreditSKUBase(planName string) int {
switch strings.ToLower(strings.TrimSpace(planName)) {
case "starter":
return 100
case "plus":
return 400
case "growth":
return 1_200
case "business":
return 4_000
case "scale":
return 12_000
default:
return 0
}
}
// MonthlyCreditsForSKUCover returns credits for coverPct% of skuBase at CreditsPerAIProduct each.
func MonthlyCreditsForSKUCover(skuBase, coverPct int) int {
if skuBase <= 0 || coverPct <= 0 {
return 0
}
if coverPct > 100 {
coverPct = 100
}
products := (skuBase * coverPct) / 100
return products * CreditsPerAIProduct * AssumedPrimaryContentLanguages
}
// MonthlyCreditsForPlan sizes the monthly grant from CreditSKUBase × PlanAICoverPercent.
// The maxProducts argument is ignored when CreditSKUBase is set (paid public ladder).
func MonthlyCreditsForPlan(planName string, maxProducts int) int {
base := CreditSKUBase(planName)
if base <= 0 {
base = maxProducts
}
return MonthlyCreditsForSKUCover(base, PlanAICoverPercent(planName))
}
// CreditPack is a one-time AI credit top-up sold via Stripe Checkout (mode=payment).
// These are additional Stripe Products with one-time Prices — not subscription add-ons.
type CreditPack struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Credits int `json:"credits"`
PriceUSD int `json:"price_usd"` // whole dollars for marketing UI
// Approx product×language passes at CreditsPerAIProduct.
AIProducts int `json:"ai_products"`
}
func packFromCredits(id, name, desc string, credits, priceUSD int) CreditPack {
return CreditPack{
ID: id,
Name: name,
Description: desc,
Credits: credits,
PriceUSD: priceUSD,
AIProducts: credits / CreditsPerAIProduct,
}
}
// DefaultCreditPacks is the public top-up ladder (credits-first sizing).
// Prices are balanced so small packs are not punitive $/credit vs larger ones,
// while staying expensive enough that packs cannot undercut plan upgrades or A1 (~€300).
func DefaultCreditPacks() []CreditPack {
return []CreditPack{
packFromCredits("tiny", "Nano pack", "Smoke tests / tiny fixes (25 credits ≈ 12 AI products)", 25, 29),
packFromCredits("small", "Starter pack", "Small top-up (65 credits ≈ 32 AI products)", 65, 59),
packFromCredits("medium", "Plus pack", "Burst top-up (200 credits ≈ 100 AI products)", 200, 149),
packFromCredits("large", "Growth pack", "Mid buffer (500 credits ≈ 250 AI products)", 500, 299),
packFromCredits("xl", "Catalog pack", "Catalog / re-run buffer (1,200 credits ≈ 600 AI products)", 1200, 599),
packFromCredits("xxl", "Business pack", "Large multi-language buffer (3,000 credits ≈ 1,500 AI products)", 3000, 1299),
packFromCredits("mega", "Scale pack", "Distributor / agency burst (8,000 credits ≈ 4,000 AI products)", 8000, 2999),
}
}
// CreditPackByID returns a pack from DefaultCreditPacks.
func CreditPackByID(id string) (CreditPack, bool) {
want := strings.ToLower(strings.TrimSpace(id))
for _, p := range DefaultCreditPacks() {
if p.ID == want {
return p, true
}
}
return CreditPack{}, false
}
// CreditPackPriceKey is the Stripe PriceIDs map key for a one-time pack (pack:<id>).
func CreditPackPriceKey(packID string) string {
return "pack:" + strings.ToLower(strings.TrimSpace(packID))
}
// CreditPackSettingsKey is the platformsettings Values key (stripe.price.pack.<id>).
func CreditPackSettingsKey(packID string) string {
return "stripe.price.pack." + strings.ToLower(strings.TrimSpace(packID))
}
// CreditPackEnvVar is the optional process-env fallback (STRIPE_PRICE_PACK_<ID>).
func CreditPackEnvVar(packID string) string {
return "STRIPE_PRICE_PACK_" + strings.ToUpper(strings.TrimSpace(packID))
}