fix(tencent): detect promotional token plans
Relax the Tencent catalog plan matcher so monthly promotional plans are parsed by structure instead of a hard-coded plan-name list. This keeps first-month promotional packages in the catalog and adds a regression sample plus parser test coverage.
This commit is contained in:
@@ -96,3 +96,33 @@ func TestRunTencentCatalogDryRunPrintsSummary(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseTencentCatalogExtractsPromotionalPlans(t *testing.T) {
|
||||
raw, err := os.ReadFile(filepath.Join("testdata", "tencent_token_plan_promo_sample.txt"))
|
||||
if err != nil {
|
||||
t.Fatalf("读取促销样例失败: %v", err)
|
||||
}
|
||||
|
||||
catalog, err := parseTencentCatalog(string(raw))
|
||||
if err != nil {
|
||||
t.Fatalf("parseTencentCatalog 失败: %v", err)
|
||||
}
|
||||
|
||||
if len(catalog.Plans) != 2 {
|
||||
t.Fatalf("期望 2 个套餐,实际 %d", len(catalog.Plans))
|
||||
}
|
||||
|
||||
first := catalog.Plans[0]
|
||||
if first.Series != "通用 Token Plan" {
|
||||
t.Fatalf("套餐系列错误: %q", first.Series)
|
||||
}
|
||||
if first.Tier != "首月活动版" {
|
||||
t.Fatalf("促销套餐档位错误: %q", first.Tier)
|
||||
}
|
||||
if first.Price != "19元/月" {
|
||||
t.Fatalf("促销套餐价格错误: %q", first.Price)
|
||||
}
|
||||
if first.Scene != "首购用户首月优惠,次月恢复标准价。" {
|
||||
t.Fatalf("促销套餐说明错误: %q", first.Scene)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,30 +26,30 @@ type importTencentSubscriptionConfig struct {
|
||||
}
|
||||
|
||||
type subscriptionPlanRow struct {
|
||||
ProviderName string
|
||||
ProviderCN string
|
||||
ProviderName string
|
||||
ProviderCN string
|
||||
ProviderCountry string
|
||||
OperatorName string
|
||||
OperatorCN string
|
||||
OperatorName string
|
||||
OperatorCN string
|
||||
OperatorCountry string
|
||||
OperatorType string
|
||||
PlanFamily string
|
||||
PlanCode string
|
||||
PlanName string
|
||||
Tier string
|
||||
BillingCycle string
|
||||
Currency string
|
||||
ListPrice float64
|
||||
PriceUnit string
|
||||
QuotaValue int64
|
||||
QuotaUnit string
|
||||
ContextWindow int
|
||||
PlanScope string
|
||||
ModelScope string
|
||||
SourceURL string
|
||||
PublishedAt string
|
||||
EffectiveDate string
|
||||
Notes string
|
||||
OperatorType string
|
||||
PlanFamily string
|
||||
PlanCode string
|
||||
PlanName string
|
||||
Tier string
|
||||
BillingCycle string
|
||||
Currency string
|
||||
ListPrice float64
|
||||
PriceUnit string
|
||||
QuotaValue int64
|
||||
QuotaUnit string
|
||||
ContextWindow int
|
||||
PlanScope string
|
||||
ModelScope string
|
||||
SourceURL string
|
||||
PublishedAt string
|
||||
EffectiveDate string
|
||||
Notes string
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -188,22 +188,7 @@ func extractSeriesHeading(line string) string {
|
||||
}
|
||||
|
||||
func tryParseTencentPlan(lines []string, start int, series string) (tencentPlan, int, bool) {
|
||||
if start+4 >= len(lines) {
|
||||
return tencentPlan{}, start, false
|
||||
}
|
||||
if !isTencentPlanName(lines[start]) {
|
||||
return tencentPlan{}, start, false
|
||||
}
|
||||
if !isTencentPlanTier(lines[start+1]) {
|
||||
return tencentPlan{}, start, false
|
||||
}
|
||||
if !strings.Contains(lines[start+2], "订阅月") {
|
||||
return tencentPlan{}, start, false
|
||||
}
|
||||
if !strings.Contains(lines[start+3], "Tokens") {
|
||||
return tencentPlan{}, start, false
|
||||
}
|
||||
if !strings.Contains(lines[start+4], "元/月") {
|
||||
if !looksLikeTencentPlan(lines, start) {
|
||||
return tencentPlan{}, start, false
|
||||
}
|
||||
|
||||
@@ -216,7 +201,7 @@ func tryParseTencentPlan(lines []string, start int, series string) (tencentPlan,
|
||||
}
|
||||
|
||||
nextIndex := start + 4
|
||||
if start+5 < len(lines) && !strings.HasPrefix(lines[start+5], "### ") && !isTencentPlanName(lines[start+5]) {
|
||||
if start+5 < len(lines) && !strings.HasPrefix(lines[start+5], "### ") && !looksLikeTencentPlan(lines, start+5) {
|
||||
plan.Scene = lines[start+5]
|
||||
nextIndex = start + 5
|
||||
}
|
||||
@@ -247,7 +232,7 @@ func tryParseTencentModel(lines []string, start int, series string) (tencentMode
|
||||
if strings.HasPrefix(line, "## ") || strings.HasPrefix(line, "### ") {
|
||||
break
|
||||
}
|
||||
if isTencentPlanName(line) && i+1 < len(lines) && isTencentPlanTier(lines[i+1]) {
|
||||
if looksLikeTencentPlan(lines, i) {
|
||||
break
|
||||
}
|
||||
if i+1 < len(lines) && isTencentModelID(lines[i+1]) && !isReservedTencentLine(line) {
|
||||
@@ -262,19 +247,32 @@ func tryParseTencentModel(lines []string, start int, series string) (tencentMode
|
||||
return model, nextIndex, true
|
||||
}
|
||||
|
||||
func isTencentPlanName(line string) bool {
|
||||
switch line {
|
||||
case "体验套餐", "基础套餐", "进阶套餐", "专业套餐":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func isTencentPlanTier(line string) bool {
|
||||
return strings.HasPrefix(line, "(") && strings.HasSuffix(line, ")")
|
||||
}
|
||||
|
||||
func looksLikeTencentPlan(lines []string, start int) bool {
|
||||
if start+4 >= len(lines) {
|
||||
return false
|
||||
}
|
||||
if isReservedTencentLine(lines[start]) {
|
||||
return false
|
||||
}
|
||||
if isTencentModelID(lines[start]) {
|
||||
return false
|
||||
}
|
||||
if !isTencentPlanTier(lines[start+1]) {
|
||||
return false
|
||||
}
|
||||
if !strings.Contains(lines[start+2], "订阅月") {
|
||||
return false
|
||||
}
|
||||
if !strings.Contains(lines[start+3], "Tokens") {
|
||||
return false
|
||||
}
|
||||
return strings.Contains(lines[start+4], "元/月")
|
||||
}
|
||||
|
||||
func isReservedTencentLine(line string) bool {
|
||||
if strings.HasPrefix(line, "#") {
|
||||
return true
|
||||
|
||||
27
scripts/testdata/tencent_token_plan_promo_sample.txt
vendored
Normal file
27
scripts/testdata/tencent_token_plan_promo_sample.txt
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
# Token Plan 个人版套餐概览
|
||||
最近更新时间:2026-05-14 09:00:00
|
||||
|
||||
## 通用 Token Plan 套餐
|
||||
### 套餐详情
|
||||
首月活动套餐
|
||||
(首月活动版)
|
||||
每订阅月
|
||||
3500万 Tokens
|
||||
19元/月
|
||||
首购用户首月优惠,次月恢复标准价。
|
||||
|
||||
基础套餐
|
||||
(Standard)
|
||||
每订阅月
|
||||
1亿 Tokens
|
||||
99元/月
|
||||
日常使用,高性价比。
|
||||
|
||||
### 可用模型
|
||||
GLM-5
|
||||
glm-5
|
||||
深度思考、文本生成
|
||||
|
||||
Hunyuan-T1
|
||||
hunyuan-t1
|
||||
文本生成
|
||||
Reference in New Issue
Block a user