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:
188
scripts/minimax_subscription_lib.go
Normal file
188
scripts/minimax_subscription_lib.go
Normal 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, " ")
|
||||
}
|
||||
Reference in New Issue
Block a user