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.
225 lines
7.5 KiB
Go
225 lines
7.5 KiB
Go
//go:build llm_script
|
||
|
||
package main
|
||
|
||
import (
|
||
"fmt"
|
||
"regexp"
|
||
"strings"
|
||
)
|
||
|
||
const (
|
||
defaultCTYunCodingPlanURL = "https://www.ctyun.cn/document/11061839/11092368"
|
||
defaultCTYunTokenPlanURL = "https://www.ctyun.cn/act/AI/zhuanxiang"
|
||
)
|
||
|
||
func parseCTYunSubscriptionCatalog(codingRaw string, tokenRaw string) ([]subscriptionImportRecord, error) {
|
||
publishedAt, known := publishedAtFromText(firstNonEmptyText(codingRaw, tokenRaw))
|
||
codingRecords, err := parseCTYunCodingPlan(codingRaw, publishedAt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
tokenRecords, err := parseCTYunTokenPlan(tokenRaw, publishedAt)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
records := append(codingRecords, tokenRecords...)
|
||
for i := range records {
|
||
records[i].PublishedAtKnown = known
|
||
}
|
||
return records, nil
|
||
}
|
||
|
||
func parseCTYunCodingPlan(raw string, publishedAt string) ([]subscriptionImportRecord, error) {
|
||
if !strings.Contains(raw, "GLM Lite") || !strings.Contains(raw, "GLM Max") {
|
||
return nil, fmt.Errorf("ctyun coding plan tiers not found")
|
||
}
|
||
pricePattern := regexp.MustCompile(`包月价格\s+(\d+)元/月\s+(\d+)元/月\s+(\d+)元/月`)
|
||
priceMatch := pricePattern.FindStringSubmatch(raw)
|
||
if len(priceMatch) != 4 {
|
||
return nil, fmt.Errorf("ctyun coding plan monthly prices not found")
|
||
}
|
||
limitPattern := regexp.MustCompile(`每月最多约([\d,]+)次prompts`)
|
||
limitMatches := limitPattern.FindAllStringSubmatch(raw, -1)
|
||
if len(limitMatches) < 3 {
|
||
return nil, fmt.Errorf("ctyun coding plan monthly limits not found")
|
||
}
|
||
modelScope := extractCTYunCodingModels(raw)
|
||
|
||
records := []subscriptionImportRecord{
|
||
{
|
||
ProviderName: "Telecom",
|
||
ProviderNameCn: "中国电信",
|
||
ProviderCountry: "CN",
|
||
ProviderWebsite: "https://www.ctyun.cn",
|
||
OperatorName: "CTYun",
|
||
OperatorNameCn: "天翼云",
|
||
OperatorCountry: "CN",
|
||
OperatorWebsite: "https://www.ctyun.cn",
|
||
OperatorType: "cloud",
|
||
PlanFamily: "coding_plan",
|
||
PlanCode: "ctyun-coding-plan-lite-monthly",
|
||
PlanName: "天翼云 Coding Plan Lite(月付)",
|
||
Tier: "Lite",
|
||
BillingCycle: "monthly",
|
||
Currency: "CNY",
|
||
ListPrice: mustParseSubscriptionPrice(priceMatch[1]),
|
||
PriceUnit: "CNY/month",
|
||
QuotaValue: mustParseSubscriptionInt64(limitMatches[0][1]),
|
||
QuotaUnit: "prompts/month",
|
||
PlanScope: "Coding Plan",
|
||
ModelScope: modelScope,
|
||
SourceURL: defaultCTYunCodingPlanURL,
|
||
PublishedAt: publishedAt,
|
||
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
|
||
Notes: "每 5 小时约 80 次 prompts;每周约 400 次 prompts。",
|
||
},
|
||
{
|
||
ProviderName: "Telecom",
|
||
ProviderNameCn: "中国电信",
|
||
ProviderCountry: "CN",
|
||
ProviderWebsite: "https://www.ctyun.cn",
|
||
OperatorName: "CTYun",
|
||
OperatorNameCn: "天翼云",
|
||
OperatorCountry: "CN",
|
||
OperatorWebsite: "https://www.ctyun.cn",
|
||
OperatorType: "cloud",
|
||
PlanFamily: "coding_plan",
|
||
PlanCode: "ctyun-coding-plan-pro-monthly",
|
||
PlanName: "天翼云 Coding Plan Pro(月付)",
|
||
Tier: "Pro",
|
||
BillingCycle: "monthly",
|
||
Currency: "CNY",
|
||
ListPrice: mustParseSubscriptionPrice(priceMatch[2]),
|
||
PriceUnit: "CNY/month",
|
||
QuotaValue: mustParseSubscriptionInt64(limitMatches[1][1]),
|
||
QuotaUnit: "prompts/month",
|
||
PlanScope: "Coding Plan",
|
||
ModelScope: modelScope,
|
||
SourceURL: defaultCTYunCodingPlanURL,
|
||
PublishedAt: publishedAt,
|
||
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
|
||
Notes: "每 5 小时约 400 次 prompts;每周约 2,000 次 prompts。",
|
||
},
|
||
{
|
||
ProviderName: "Telecom",
|
||
ProviderNameCn: "中国电信",
|
||
ProviderCountry: "CN",
|
||
ProviderWebsite: "https://www.ctyun.cn",
|
||
OperatorName: "CTYun",
|
||
OperatorNameCn: "天翼云",
|
||
OperatorCountry: "CN",
|
||
OperatorWebsite: "https://www.ctyun.cn",
|
||
OperatorType: "cloud",
|
||
PlanFamily: "coding_plan",
|
||
PlanCode: "ctyun-coding-plan-max-monthly",
|
||
PlanName: "天翼云 Coding Plan Max(月付)",
|
||
Tier: "Max",
|
||
BillingCycle: "monthly",
|
||
Currency: "CNY",
|
||
ListPrice: mustParseSubscriptionPrice(priceMatch[3]),
|
||
PriceUnit: "CNY/month",
|
||
QuotaValue: mustParseSubscriptionInt64(limitMatches[2][1]),
|
||
QuotaUnit: "prompts/month",
|
||
PlanScope: "Coding Plan",
|
||
ModelScope: modelScope,
|
||
SourceURL: defaultCTYunCodingPlanURL,
|
||
PublishedAt: publishedAt,
|
||
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
|
||
Notes: "每 5 小时约 1,600 次 prompts;每周约 8,000 次 prompts。",
|
||
},
|
||
}
|
||
return records, nil
|
||
}
|
||
|
||
func parseCTYunTokenPlan(raw string, publishedAt string) ([]subscriptionImportRecord, error) {
|
||
pattern := regexp.MustCompile(`Token Plan ([^\n]+?)(\d+(?:\.\d+)?亿|\d+万)Tokens包[\s\S]*?支持模型:([^\n]+)[\s\S]*?(\d+\s*\.\s*\d+)\s*元/个`)
|
||
matches := pattern.FindAllStringSubmatch(raw, -1)
|
||
if len(matches) != 6 {
|
||
return nil, fmt.Errorf("unexpected ctyun token plan count: %d", len(matches))
|
||
}
|
||
|
||
codeByTier := map[string]string{
|
||
"Lite": "lite",
|
||
"Pro": "pro",
|
||
"Max": "max",
|
||
"轻享包": "starter",
|
||
"畅享包": "plus",
|
||
"尊享包": "vip",
|
||
}
|
||
|
||
records := make([]subscriptionImportRecord, 0, len(matches))
|
||
for _, match := range matches {
|
||
rawTier := strings.TrimSpace(match[1])
|
||
tierCode := codeByTier[rawTier]
|
||
quotaValue := parseChineseTokenQuota(match[2])
|
||
price := mustParseSubscriptionPrice(strings.ReplaceAll(match[4], " ", ""))
|
||
planName := "天翼云 Token Plan " + rawTier
|
||
if rawTier == "Lite" || rawTier == "Pro" || rawTier == "Max" {
|
||
planName = "天翼云 Token Plan " + rawTier
|
||
}
|
||
records = append(records, subscriptionImportRecord{
|
||
ProviderName: "Telecom",
|
||
ProviderNameCn: "中国电信",
|
||
ProviderCountry: "CN",
|
||
ProviderWebsite: "https://www.ctyun.cn",
|
||
OperatorName: "CTYun",
|
||
OperatorNameCn: "天翼云",
|
||
OperatorCountry: "CN",
|
||
OperatorWebsite: "https://www.ctyun.cn",
|
||
OperatorType: "cloud",
|
||
PlanFamily: "token_plan",
|
||
PlanCode: "ctyun-token-plan-" + tierCode,
|
||
PlanName: planName,
|
||
Tier: rawTier,
|
||
BillingCycle: "monthly",
|
||
Currency: "CNY",
|
||
ListPrice: price,
|
||
PriceUnit: "CNY/pack",
|
||
QuotaValue: quotaValue,
|
||
QuotaUnit: "tokens/pack",
|
||
PlanScope: "Token Plan",
|
||
ModelScope: []string{strings.TrimSpace(match[3])},
|
||
SourceURL: defaultCTYunTokenPlanURL,
|
||
PublishedAt: publishedAt,
|
||
EffectiveDate: effectiveDateFromPublishedAt(publishedAt),
|
||
Notes: "天翼云大模型 AI 专项活动页套餐。",
|
||
})
|
||
}
|
||
return records, nil
|
||
}
|
||
|
||
func parseChineseTokenQuota(raw string) int64 {
|
||
cleaned := strings.TrimSpace(strings.TrimSuffix(raw, "Tokens包"))
|
||
cleaned = strings.ReplaceAll(cleaned, " ", "")
|
||
switch {
|
||
case strings.Contains(cleaned, "亿"):
|
||
return parseDecimalMultiplier(strings.TrimSuffix(cleaned, "亿"), 100000000)
|
||
case strings.Contains(cleaned, "万"):
|
||
return parseDecimalMultiplier(strings.TrimSuffix(cleaned, "万"), 10000)
|
||
default:
|
||
return mustParseSubscriptionInt64(cleaned)
|
||
}
|
||
}
|
||
|
||
func extractCTYunCodingModels(raw string) []string {
|
||
lines := strings.Split(raw, "\n")
|
||
models := make([]string, 0, 8)
|
||
capturing := false
|
||
for _, line := range lines {
|
||
line = strings.TrimSpace(line)
|
||
switch {
|
||
case line == "支持模型":
|
||
capturing = true
|
||
continue
|
||
case line == "用量限制":
|
||
return models
|
||
case !capturing || line == "":
|
||
continue
|
||
default:
|
||
models = append(models, line)
|
||
}
|
||
}
|
||
return models
|
||
}
|