Files
llm-intelligence/scripts/ctyun_subscription_lib.go
phamnazage-jpg 958245537a 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.
2026-05-15 22:32:57 +08:00

225 lines
7.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//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
}