feat(imports): add real pricing and subscription collectors

Add plan catalog and subscription schema support, seed baselines, and real importers for core domestic subscriptions plus stable official pricing sources.

This commit also hardens the shared fetch layers so the importers can support live collection and database writes instead of relying on manual placeholders alone.
This commit is contained in:
phamnazage-jpg
2026-05-15 22:32:57 +08:00
parent dd58c18fe3
commit 958245537a
91 changed files with 10474 additions and 1 deletions

View File

@@ -0,0 +1,188 @@
//go:build llm_script
package main
import (
"fmt"
"regexp"
"strings"
)
const defaultMinimaxTokenPlanURL = "https://platform.minimax.io/docs/guides/pricing-token-plan"
type minimaxPlanSpec struct {
billingCycle string
priceUnit string
blockPattern string
modelScope []string
tiers []string
planCodes []string
planNames []string
quotaUnit string
}
func parseMinimaxTokenPlans(raw string) ([]subscriptionImportRecord, error) {
publishedAt, known := publishedAtFromText(raw)
normalized := normalizeMinimaxTokenPlanText(raw)
specs := []minimaxPlanSpec{
{
billingCycle: "monthly",
priceUnit: "USD/month",
blockPattern: `Monthly.*?Starter\s+Plus\s+Max\s+Price\s+\$([\d,]+)\s*/month\s+\$([\d,]+)\s*/month\s+\$([\d,]+)\s*/month\s+M2\.7\s+([\d,]+)\s+requests/5hrs\s+([\d,]+)\s+requests/5hrs\s+([\d,]+)\s+requests/5hrs`,
modelScope: []string{"MiniMax-M2.7"},
tiers: []string{"Starter", "Plus", "Max"},
planCodes: []string{"minimax-token-plan-starter", "minimax-token-plan-plus", "minimax-token-plan-max"},
planNames: []string{"MiniMax Token Plan Starter", "MiniMax Token Plan Plus", "MiniMax Token Plan Max"},
quotaUnit: "requests/5hrs",
},
{
billingCycle: "monthly",
priceUnit: "USD/month",
blockPattern: `Monthly.*?Highspeed Plans\s+Plus-Highspeed\s+Max-Highspeed\s+Ultra-Highspeed\s+Price\s+\$([\d,]+)\s*/month\s+\$([\d,]+)\s*/month\s+\$([\d,]+)\s*/month\s+M2\.7-highspeed\s+([\d,]+)\s+requests/5hrs\s+([\d,]+)\s+requests/5hrs\s+([\d,]+)\s+requests/5hrs`,
modelScope: []string{"MiniMax-M2.7-Highspeed"},
tiers: []string{"Plus-Highspeed", "Max-Highspeed", "Ultra-Highspeed"},
planCodes: []string{"minimax-token-plan-plus-highspeed", "minimax-token-plan-max-highspeed", "minimax-token-plan-ultra-highspeed"},
planNames: []string{"MiniMax Token Plan Plus-Highspeed", "MiniMax Token Plan Max-Highspeed", "MiniMax Token Plan Ultra-Highspeed"},
quotaUnit: "requests/5hrs",
},
{
billingCycle: "yearly",
priceUnit: "USD/year",
blockPattern: `Yearly.*?Starter\s+Plus\s+Max\s+Price\s+\$([\d,]+)\s*/year\s+\$([\d,]+)\s*/year\s+\$([\d,]+)\s*/year\s+M2\.7\s+([\d,]+)\s+requests/5hrs\s+([\d,]+)\s+requests/5hrs\s+([\d,]+)\s+requests/5hrs`,
modelScope: []string{"MiniMax-M2.7"},
tiers: []string{"Starter-Yearly", "Plus-Yearly", "Max-Yearly"},
planCodes: []string{"minimax-token-plan-starter-yearly", "minimax-token-plan-plus-yearly", "minimax-token-plan-max-yearly"},
planNames: []string{"MiniMax Token Plan Starter Yearly", "MiniMax Token Plan Plus Yearly", "MiniMax Token Plan Max Yearly"},
quotaUnit: "requests/5hrs",
},
{
billingCycle: "yearly",
priceUnit: "USD/year",
blockPattern: `Yearly.*?Highspeed Plans\s+Plus-Highspeed\s+Max-Highspeed\s+Ultra-Highspeed\s+Price\s+\$([\d,]+)\s*/year\s+\$([\d,]+)\s*/year\s+\$([\d,]+)\s*/year\s+M2\.7-highspeed\s+([\d,]+)\s+requests/5hrs\s+([\d,]+)\s+requests/5hrs\s+([\d,]+)\s+requests/5hrs`,
modelScope: []string{"MiniMax-M2.7-Highspeed"},
tiers: []string{"Plus-Highspeed-Yearly", "Max-Highspeed-Yearly", "Ultra-Highspeed-Yearly"},
planCodes: []string{"minimax-token-plan-plus-highspeed-yearly", "minimax-token-plan-max-highspeed-yearly", "minimax-token-plan-ultra-highspeed-yearly"},
planNames: []string{"MiniMax Token Plan Plus-Highspeed Yearly", "MiniMax Token Plan Max-Highspeed Yearly", "MiniMax Token Plan Ultra-Highspeed Yearly"},
quotaUnit: "requests/5hrs",
},
}
var records []subscriptionImportRecord
for _, spec := range specs {
parsed, err := parseMinimaxPlanBlock(normalized, spec, publishedAt)
if err != nil {
return nil, err
}
records = append(records, parsed...)
}
standardNotes := extractMinimaxNotes(normalized, []string{
"Speech 2.8",
"image-01",
"Hailuo-2.3-Fast",
"Hailuo-2.3",
"Music-2.6",
})
for i := range records {
records[i].PublishedAtKnown = known
if strings.Contains(records[i].PlanCode, "highspeed") {
records[i].Notes = joinNonEmptyNotes(records[i].Notes, "高速版覆盖 MiniMax-M2.7-Highspeed。")
continue
}
records[i].Notes = joinNonEmptyNotes(records[i].Notes, standardNotes)
}
return records, nil
}
func parseMinimaxPlanBlock(raw string, spec minimaxPlanSpec, publishedAt string) ([]subscriptionImportRecord, error) {
match := regexp.MustCompile(spec.blockPattern).FindStringSubmatch(raw)
if len(match) != 7 {
return nil, fmt.Errorf("unexpected minimax %s block", spec.billingCycle)
}
records := make([]subscriptionImportRecord, 0, 3)
for i := range spec.tiers {
records = append(records, subscriptionImportRecord{
ProviderName: "MiniMax",
ProviderNameCn: "MiniMax",
ProviderCountry: "CN",
ProviderWebsite: "https://platform.minimax.io",
OperatorName: "MiniMax",
OperatorNameCn: "MiniMax",
OperatorCountry: "CN",
OperatorWebsite: "https://platform.minimax.io/docs/guides/pricing-overview",
OperatorType: "official",
PlanFamily: "token_plan",
PlanCode: spec.planCodes[i],
PlanName: spec.planNames[i],
Tier: spec.tiers[i],
BillingCycle: spec.billingCycle,
Currency: "USD",
ListPrice: mustParseSubscriptionPrice(match[i+1]),
PriceUnit: spec.priceUnit,
QuotaValue: mustParseSubscriptionInt64(match[i+4]),
QuotaUnit: spec.quotaUnit,
PlanScope: "Token Plan",
ModelScope: append([]string(nil), spec.modelScope...),
SourceURL: defaultMinimaxTokenPlanURL,
PublishedAt: publishedAt,
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
PublishedAtKnown: true,
})
}
return records, nil
}
func normalizeMinimaxTokenPlanText(raw string) string {
replacer := strings.NewReplacer(
"High-Speed", "Highspeed",
"High Speed", "Highspeed",
"Max-High-Speed", "Max-Highspeed",
"Ultra-High-Speed", "Ultra-Highspeed",
"Plus `Cost-Effective`", "Plus",
"Max `Extra Large`", "Max",
"Starter `Save $20`", "Starter",
"Plus `Save $40`", "Plus",
"Max `Save $100`", "Max",
"Plus-Highspeed `Save $80`", "Plus-Highspeed",
"Max-Highspeed `Save $160`", "Max-Highspeed",
"Ultra-Highspeed `Save $300`", "Ultra-Highspeed",
"Subscribe Now Standard Plans:", "",
"Subscribe Now", "",
"Standard Plans:", "",
"Highspeed Plans:", "Highspeed Plans",
)
normalized := replacer.Replace(raw)
normalized = strings.ReplaceAll(normalized, "\r\n", "\n")
normalized = strings.ReplaceAll(normalized, "\r", "\n")
normalized = strings.ReplaceAll(normalized, "|", " ")
normalized = strings.ReplaceAll(normalized, "---", " ")
normalized = regexp.MustCompile(`\s+`).ReplaceAllString(normalized, " ")
return strings.TrimSpace(normalized)
}
func extractMinimaxNotes(raw string, markers []string) string {
var hits []string
for _, marker := range markers {
if strings.Contains(raw, marker) {
hits = append(hits, marker)
}
}
if len(hits) == 0 {
return ""
}
return "附带配额包含 " + strings.Join(hits, " / ") + "。"
}
func joinNonEmptyNotes(parts ...string) string {
filtered := make([]string, 0, len(parts))
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
filtered = append(filtered, part)
}
return strings.Join(filtered, " ")
}