feat(prompts): add prompts loader and configuration management

- Implemented a new prompts loader in `loader.go` to manage prompt templates and configurations.
- Introduced caching mechanism for prompt configurations to optimize loading.
- Added functions to apply platform-specific prompt template overrides.
- Created unit tests in `loader_test.go` to validate prompt configuration loading and reloading behavior.
- Ensured that the last valid configuration is retained in case of errors during reload.
This commit is contained in:
2026-04-13 16:08:12 +08:00
parent 6066f43a7d
commit 1b01caac0f
21 changed files with 1630 additions and 392 deletions
+367 -19
View File
@@ -254,7 +254,14 @@ var monitoringPlatformNames = map[string]string{
"yuanbao": "腾讯元宝",
}
func (s *MonitoringService) DashboardComposite(ctx context.Context, brandID int64, keywordID *int64, days int, businessDate string) (*MonitoringDashboardCompositeResponse, error) {
func (s *MonitoringService) DashboardComposite(
ctx context.Context,
brandID int64,
keywordID *int64,
days int,
businessDate string,
aiPlatformID *string,
) (*MonitoringDashboardCompositeResponse, error) {
actor := auth.MustActor(ctx)
brand, err := s.resolveBrand(ctx, actor.TenantID, brandID)
@@ -280,15 +287,16 @@ func (s *MonitoringService) DashboardComposite(ctx context.Context, brandID int6
questionIDs := configuredQuestionIDs(configuredQuestions)
platforms := resolveMonitoringPlatforms(quota.EnabledPlatforms)
filteredPlatforms := filterMonitoringPlatforms(platforms, aiPlatformID)
startDate, endDate, err := resolveDashboardDateWindow(days, businessDate)
if err != nil {
return nil, err
}
derivedMetrics, err := s.loadDerivedParseMetrics(ctx, actor.TenantID, brand.ID, brand.Name, startDate, endDate)
derivedMetrics, err := s.loadDerivedParseMetrics(ctx, actor.TenantID, brand.ID, brand.Name, startDate, endDate, aiPlatformID)
if err != nil {
return nil, err
}
overview, _, err := s.loadOverview(ctx, actor.TenantID, brand.ID, startDate, endDate, quota.CollectionMode, derivedMetrics)
overview, _, err := s.loadOverview(ctx, actor.TenantID, brand.ID, startDate, endDate, quota.CollectionMode, aiPlatformID, derivedMetrics)
if err != nil {
return nil, err
}
@@ -298,27 +306,27 @@ func (s *MonitoringService) DashboardComposite(ctx context.Context, brandID int6
return nil, err
}
platformBreakdown, err := s.loadPlatformBreakdown(ctx, actor.TenantID, brand.ID, endDate, quota.CollectionMode, platforms, accessStates, derivedMetrics)
platformBreakdown, err := s.loadPlatformBreakdown(ctx, actor.TenantID, brand.ID, endDate, quota.CollectionMode, filteredPlatforms, accessStates, derivedMetrics)
if err != nil {
return nil, err
}
timeBuckets, err := s.loadTimeBuckets(ctx, actor.TenantID, brand.ID, startDate, endDate, quota.CollectionMode, derivedMetrics)
timeBuckets, err := s.loadTimeBuckets(ctx, actor.TenantID, brand.ID, startDate, endDate, quota.CollectionMode, aiPlatformID, derivedMetrics)
if err != nil {
return nil, err
}
hotQuestions, err := s.loadHotQuestions(ctx, actor.TenantID, brand.ID, configuredQuestions, endDate, derivedMetrics)
hotQuestions, err := s.loadHotQuestions(ctx, actor.TenantID, brand.ID, configuredQuestions, endDate, aiPlatformID, derivedMetrics)
if err != nil {
return nil, err
}
citationRanking, err := s.loadCitationRanking(ctx, actor.TenantID, brand.ID, questionIDs, startDate, endDate, accessStates)
citationRanking, err := s.loadCitationRanking(ctx, actor.TenantID, brand.ID, questionIDs, startDate, endDate, accessStates, aiPlatformID)
if err != nil {
return nil, err
}
citedArticles, err := s.loadCitedArticles(ctx, actor.TenantID, brand.ID, questionIDs, startDate, endDate)
citedArticles, err := s.loadCitedArticles(ctx, actor.TenantID, brand.ID, questionIDs, startDate, endDate, aiPlatformID)
if err != nil {
return nil, err
}
@@ -981,7 +989,13 @@ func (stats monitoringDerivedRateStats) positiveMentionRate() *float64 {
return divideAsPointer(stats.PositiveMentions, stats.Total)
}
func (s *MonitoringService) loadDerivedParseMetrics(ctx context.Context, tenantID, brandID int64, brandName string, startDate, endDate time.Time) (monitoringDerivedMetrics, error) {
func (s *MonitoringService) loadDerivedParseMetrics(
ctx context.Context,
tenantID, brandID int64,
brandName string,
startDate, endDate time.Time,
aiPlatformID *string,
) (monitoringDerivedMetrics, error) {
metrics := newMonitoringDerivedMetrics()
rows, err := s.monitoringPool.Query(ctx, `
@@ -1003,7 +1017,8 @@ func (s *MonitoringService) loadDerivedParseMetrics(ctx context.Context, tenantI
AND r.collector_type = $3
AND r.status = 'succeeded'
AND r.business_date BETWEEN $4::date AND $5::date
`, tenantID, brandID, monitoringCollectorType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"))
AND ($6::text IS NULL OR r.ai_platform_id = $6)
`, tenantID, brandID, monitoringCollectorType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"), nullableString(aiPlatformID))
if err != nil {
return metrics, response.ErrInternal(50041, "query_failed", "failed to load monitoring derived parse metrics")
}
@@ -1092,7 +1107,18 @@ func decodeMonitoringMatchedBrandTerms(raw []byte) []string {
return normalizeMonitoringBrandTermList(items)
}
func (s *MonitoringService) loadOverview(ctx context.Context, tenantID, brandID int64, startDate, endDate time.Time, collectionMode string, derived monitoringDerivedMetrics) (MonitoringOverview, time.Time, error) {
func (s *MonitoringService) loadOverview(
ctx context.Context,
tenantID, brandID int64,
startDate, endDate time.Time,
collectionMode string,
aiPlatformID *string,
derived monitoringDerivedMetrics,
) (MonitoringOverview, time.Time, error) {
if platformID := normalizedOptionalString(aiPlatformID); platformID != "" {
return s.loadOverviewForPlatform(ctx, tenantID, brandID, startDate, endDate, collectionMode, platformID, derived)
}
overview := MonitoringOverview{
CollectionMode: collectionMode,
ConfidenceLevel: "low",
@@ -1130,6 +1156,59 @@ func (s *MonitoringService) loadOverview(ctx context.Context, tenantID, brandID
return overview, latestDate, nil
}
func (s *MonitoringService) loadOverviewForPlatform(
ctx context.Context,
tenantID, brandID int64,
startDate, endDate time.Time,
collectionMode string,
aiPlatformID string,
derived monitoringDerivedMetrics,
) (MonitoringOverview, time.Time, error) {
overview := MonitoringOverview{
CollectionMode: collectionMode,
ConfidenceLevel: "low",
}
latestDate := endDate
current, found, err := s.loadPlatformOverviewSnapshotForDate(ctx, tenantID, brandID, aiPlatformID, collectionMode, endDate)
if err != nil {
return overview, latestDate, err
}
if !found {
return overview, latestDate, nil
}
applyOverviewSnapshot(&overview, current, derived)
if current.ActualSampleCount <= 0 {
return overview, latestDate, nil
}
latestDate = current.Date
previous, foundPrevious, err := s.loadLatestSampledPlatformOverviewSnapshot(
ctx,
tenantID,
brandID,
aiPlatformID,
collectionMode,
startDate,
current.Date.AddDate(0, 0, -1),
)
if err != nil {
return overview, latestDate, err
}
if !foundPrevious {
return overview, latestDate, nil
}
applyDerivedRatesToOverviewSnapshot(&previous, derived)
overview.PrevSnapshotMentionRate = floatPointer(previous.MentionRate)
overview.PrevSnapshotTop1MentionRate = floatPointer(previous.Top1MentionRate)
overview.PrevSnapshotFirstRecommendRate = floatPointer(previous.FirstRecommendRate)
overview.PrevSnapshotPositiveMentionRate = floatPointer(previous.PositiveMentionRate)
return overview, latestDate, nil
}
type monitoringOverviewSnapshot struct {
Date time.Time
DesiredSampleCount int64
@@ -1247,6 +1326,103 @@ func (s *MonitoringService) loadLatestSampledOverviewSnapshot(ctx context.Contex
return item, true, nil
}
func (s *MonitoringService) loadPlatformOverviewSnapshotForDate(
ctx context.Context,
tenantID, brandID int64,
aiPlatformID string,
collectionMode string,
businessDate time.Time,
) (monitoringOverviewSnapshot, bool, error) {
var item monitoringOverviewSnapshot
err := s.monitoringPool.QueryRow(ctx, `
SELECT
business_date,
planned_sample_count,
actual_sample_count,
mention_rate::double precision,
top1_mention_rate::double precision,
last_sampled_at
FROM monitoring_brand_platform_daily
WHERE tenant_id = $1
AND brand_id = $2
AND ai_platform_id = $3
AND collector_type = $4
AND business_date = $5::date
`, tenantID, brandID, aiPlatformID, collectionMode, businessDate.Format("2006-01-02")).Scan(
&item.Date,
&item.PlannedSampleCount,
&item.ActualSampleCount,
&item.MentionRate,
&item.Top1MentionRate,
&item.SnapshotUpdatedAt,
)
if err != nil {
if err == pgx.ErrNoRows {
return monitoringOverviewSnapshot{}, false, nil
}
return monitoringOverviewSnapshot{}, false, response.ErrInternal(50041, "query_failed", "failed to load monitoring platform overview")
}
coverageRate := divideAsPointer(item.ActualSampleCount, item.PlannedSampleCount)
item.DesiredSampleCount = item.PlannedSampleCount
item.CoverageRate = pointerFloat64ToNull(coverageRate)
item.ConfidenceLevel = deriveMonitoringConfidenceLevel(item.ActualSampleCount, item.PlannedSampleCount, coverageRate)
return item, true, nil
}
func (s *MonitoringService) loadLatestSampledPlatformOverviewSnapshot(
ctx context.Context,
tenantID, brandID int64,
aiPlatformID string,
collectionMode string,
startDate, endDate time.Time,
) (monitoringOverviewSnapshot, bool, error) {
if endDate.Before(startDate) {
return monitoringOverviewSnapshot{}, false, nil
}
var item monitoringOverviewSnapshot
err := s.monitoringPool.QueryRow(ctx, `
SELECT
business_date,
planned_sample_count,
actual_sample_count,
mention_rate::double precision,
top1_mention_rate::double precision,
last_sampled_at
FROM monitoring_brand_platform_daily
WHERE tenant_id = $1
AND brand_id = $2
AND ai_platform_id = $3
AND collector_type = $4
AND business_date BETWEEN $5::date AND $6::date
AND actual_sample_count > 0
ORDER BY business_date DESC
LIMIT 1
`, tenantID, brandID, aiPlatformID, collectionMode, startDate.Format("2006-01-02"), endDate.Format("2006-01-02")).Scan(
&item.Date,
&item.PlannedSampleCount,
&item.ActualSampleCount,
&item.MentionRate,
&item.Top1MentionRate,
&item.SnapshotUpdatedAt,
)
if err != nil {
if err == pgx.ErrNoRows {
return monitoringOverviewSnapshot{}, false, nil
}
return monitoringOverviewSnapshot{}, false, response.ErrInternal(50041, "query_failed", "failed to load monitoring platform overview")
}
coverageRate := divideAsPointer(item.ActualSampleCount, item.PlannedSampleCount)
item.DesiredSampleCount = item.PlannedSampleCount
item.CoverageRate = pointerFloat64ToNull(coverageRate)
item.ConfidenceLevel = deriveMonitoringConfidenceLevel(item.ActualSampleCount, item.PlannedSampleCount, coverageRate)
return item, true, nil
}
func applyOverviewSnapshot(overview *MonitoringOverview, snapshot monitoringOverviewSnapshot, derived monitoringDerivedMetrics) {
if overview == nil {
return
@@ -1298,7 +1474,18 @@ func applyDerivedRatesToOverviewSnapshot(snapshot *monitoringOverviewSnapshot, d
}
}
func (s *MonitoringService) loadTimeBuckets(ctx context.Context, tenantID, brandID int64, startDate, endDate time.Time, collectionMode string, derived monitoringDerivedMetrics) ([]MonitoringTimeBucket, error) {
func (s *MonitoringService) loadTimeBuckets(
ctx context.Context,
tenantID, brandID int64,
startDate, endDate time.Time,
collectionMode string,
aiPlatformID *string,
derived monitoringDerivedMetrics,
) ([]MonitoringTimeBucket, error) {
if platformID := normalizedOptionalString(aiPlatformID); platformID != "" {
return s.loadTimeBucketsForPlatform(ctx, tenantID, brandID, startDate, endDate, collectionMode, platformID, derived)
}
rows, err := s.monitoringPool.Query(ctx, `
SELECT
business_date,
@@ -1402,6 +1589,112 @@ func (s *MonitoringService) loadTimeBuckets(ctx context.Context, tenantID, brand
return buckets, nil
}
func (s *MonitoringService) loadTimeBucketsForPlatform(
ctx context.Context,
tenantID, brandID int64,
startDate, endDate time.Time,
collectionMode string,
aiPlatformID string,
derived monitoringDerivedMetrics,
) ([]MonitoringTimeBucket, error) {
rows, err := s.monitoringPool.Query(ctx, `
SELECT
business_date,
platform_sample_status,
mention_rate::double precision,
top1_mention_rate::double precision,
planned_sample_count,
actual_sample_count,
last_sampled_at
FROM monitoring_brand_platform_daily
WHERE tenant_id = $1
AND brand_id = $2
AND ai_platform_id = $3
AND collector_type = $4
AND business_date BETWEEN $5::date AND $6::date
ORDER BY business_date ASC
`, tenantID, brandID, aiPlatformID, collectionMode, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"))
if err != nil {
return nil, response.ErrInternal(50041, "query_failed", "failed to load monitoring platform time buckets")
}
defer rows.Close()
type bucketRow struct {
Date time.Time
SampleStatus string
MentionRate sql.NullFloat64
Top1MentionRate sql.NullFloat64
FirstRecommendRate sql.NullFloat64
PositiveMentionRate sql.NullFloat64
PlannedSampleCount int64
ActualSampleCount int64
SnapshotUpdatedAt sql.NullTime
}
bucketMap := make(map[string]bucketRow)
for rows.Next() {
var item bucketRow
if scanErr := rows.Scan(
&item.Date,
&item.SampleStatus,
&item.MentionRate,
&item.Top1MentionRate,
&item.PlannedSampleCount,
&item.ActualSampleCount,
&item.SnapshotUpdatedAt,
); scanErr != nil {
return nil, response.ErrInternal(50041, "scan_failed", "failed to parse monitoring platform time buckets")
}
bucketMap[item.Date.Format("2006-01-02")] = item
}
if err := rows.Err(); err != nil {
return nil, response.ErrInternal(50041, "scan_failed", "failed to iterate monitoring platform time buckets")
}
buckets := make([]MonitoringTimeBucket, 0, int(endDate.Sub(startDate).Hours()/24)+1)
for cursor := startDate; !cursor.After(endDate); cursor = cursor.AddDate(0, 0, 1) {
key := cursor.Format("2006-01-02")
if item, ok := bucketMap[key]; ok {
if platformStatsByDate, ok := derived.ByDatePlatform[key]; ok {
if platformStats, ok := platformStatsByDate[aiPlatformID]; ok && platformStats.hasSamples() {
item.MentionRate = pointerFloat64ToNull(platformStats.mentionRate())
item.Top1MentionRate = pointerFloat64ToNull(platformStats.top1MentionRate())
}
}
if stats, ok := derived.ByDate[key]; ok && stats.hasSamples() {
item.FirstRecommendRate = pointerFloat64ToNull(stats.firstRecommendRate())
item.PositiveMentionRate = pointerFloat64ToNull(stats.positiveMentionRate())
}
coverageRate := divideAsPointer(item.ActualSampleCount, item.PlannedSampleCount)
buckets = append(buckets, MonitoringTimeBucket{
Date: key,
SampleStatus: item.SampleStatus,
MetricValue: floatPointer(item.MentionRate),
MentionRate: floatPointer(item.MentionRate),
Top1MentionRate: floatPointer(item.Top1MentionRate),
FirstRecommendRate: floatPointer(item.FirstRecommendRate),
PositiveMentionRate: floatPointer(item.PositiveMentionRate),
CitationRate: nil,
PlannedSampleCount: item.PlannedSampleCount,
ActualSampleCount: item.ActualSampleCount,
CoverageRate: coverageRate,
TriggerSource: nil,
SnapshotUpdatedAt: formatNullTime(item.SnapshotUpdatedAt),
})
continue
}
buckets = append(buckets, MonitoringTimeBucket{
Date: key,
SampleStatus: "unsampled",
})
}
return buckets, nil
}
func (s *MonitoringService) loadAccessStates(ctx context.Context, tenantID int64, installationID *int64, businessDate time.Time) (map[string]monitoringAccessState, error) {
states := map[string]monitoringAccessState{}
if installationID == nil {
@@ -1507,7 +1800,14 @@ func (s *MonitoringService) loadPlatformBreakdown(ctx context.Context, tenantID,
return breakdown, nil
}
func (s *MonitoringService) loadHotQuestions(ctx context.Context, tenantID, brandID int64, questions []monitoringConfiguredQuestion, businessDate time.Time, derived monitoringDerivedMetrics) ([]MonitoringHotQuestion, error) {
func (s *MonitoringService) loadHotQuestions(
ctx context.Context,
tenantID, brandID int64,
questions []monitoringConfiguredQuestion,
businessDate time.Time,
aiPlatformID *string,
derived monitoringDerivedMetrics,
) ([]MonitoringHotQuestion, error) {
if len(questions) == 0 {
return []MonitoringHotQuestion{}, nil
}
@@ -1530,6 +1830,7 @@ func (s *MonitoringService) loadHotQuestions(ctx context.Context, tenantID, bran
AND r.status = 'succeeded'
AND r.business_date = $4::date
AND r.question_id = ANY($5)
AND ($6::text IS NULL OR r.ai_platform_id = $6)
GROUP BY r.question_id
),
latest_hash AS (
@@ -1543,6 +1844,7 @@ func (s *MonitoringService) loadHotQuestions(ctx context.Context, tenantID, bran
AND r.status = 'succeeded'
AND r.business_date = $4::date
AND r.question_id = ANY($5)
AND ($6::text IS NULL OR r.ai_platform_id = $6)
ORDER BY r.question_id, r.business_date DESC, COALESCE(r.completed_at, r.updated_at, r.created_at) DESC NULLS LAST, r.id DESC
)
SELECT
@@ -1552,7 +1854,7 @@ func (s *MonitoringService) loadHotQuestions(ctx context.Context, tenantID, bran
s.last_sampled_at
FROM stats s
LEFT JOIN latest_hash h ON h.question_id = s.question_id
`, tenantID, brandID, monitoringCollectorType, selectedDate, questionIDs)
`, tenantID, brandID, monitoringCollectorType, selectedDate, questionIDs, nullableString(aiPlatformID))
if err != nil {
return nil, response.ErrInternal(50041, "query_failed", "failed to load hot questions")
}
@@ -1603,7 +1905,14 @@ func (s *MonitoringService) loadHotQuestions(ctx context.Context, tenantID, bran
return items, nil
}
func (s *MonitoringService) loadCitationRanking(ctx context.Context, tenantID, brandID int64, questionIDs []int64, startDate, endDate time.Time, accessStates map[string]monitoringAccessState) ([]MonitoringCitationRanking, error) {
func (s *MonitoringService) loadCitationRanking(
ctx context.Context,
tenantID, brandID int64,
questionIDs []int64,
startDate, endDate time.Time,
accessStates map[string]monitoringAccessState,
aiPlatformID *string,
) ([]MonitoringCitationRanking, error) {
if questionIDs != nil && len(questionIDs) == 0 {
return []MonitoringCitationRanking{}, nil
}
@@ -1620,6 +1929,7 @@ func (s *MonitoringService) loadCitationRanking(ctx context.Context, tenantID, b
AND r.status = 'succeeded'
AND r.business_date BETWEEN $4::date AND $5::date
AND ($6::bigint[] IS NULL OR r.question_id = ANY($6))
AND ($7::text IS NULL OR r.ai_platform_id = $7)
GROUP BY r.ai_platform_id
),
citation_counts AS (
@@ -1636,6 +1946,7 @@ func (s *MonitoringService) loadCitationRanking(ctx context.Context, tenantID, b
AND r.status = 'succeeded'
AND r.business_date BETWEEN $4::date AND $5::date
AND ($6::bigint[] IS NULL OR r.question_id = ANY($6))
AND ($7::text IS NULL OR r.ai_platform_id = $7)
GROUP BY r.ai_platform_id
)
SELECT
@@ -1650,7 +1961,7 @@ func (s *MonitoringService) loadCitationRanking(ctx context.Context, tenantID, b
LEFT JOIN citation_counts cc ON cc.ai_platform_id = rc.ai_platform_id
WHERE COALESCE(cc.cited_answer_count, 0) > 0
ORDER BY cited_answer_count DESC, cited_article_count DESC, rc.ai_platform_id ASC
`, tenantID, brandID, monitoringCollectorType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"), nullableInt64Array(questionIDs))
`, tenantID, brandID, monitoringCollectorType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"), nullableInt64Array(questionIDs), nullableString(aiPlatformID))
if err != nil {
return nil, response.ErrInternal(50041, "query_failed", "failed to load citation ranking")
}
@@ -1678,7 +1989,13 @@ func (s *MonitoringService) loadCitationRanking(ctx context.Context, tenantID, b
return items, nil
}
func (s *MonitoringService) loadCitedArticles(ctx context.Context, tenantID, brandID int64, questionIDs []int64, startDate, endDate time.Time) ([]MonitoringCitedArticle, error) {
func (s *MonitoringService) loadCitedArticles(
ctx context.Context,
tenantID, brandID int64,
questionIDs []int64,
startDate, endDate time.Time,
aiPlatformID *string,
) ([]MonitoringCitedArticle, error) {
if questionIDs != nil && len(questionIDs) == 0 {
return []MonitoringCitedArticle{}, nil
}
@@ -1693,7 +2010,8 @@ func (s *MonitoringService) loadCitedArticles(ctx context.Context, tenantID, bra
AND r.status = 'succeeded'
AND r.business_date BETWEEN $4::date AND $5::date
AND ($6::bigint[] IS NULL OR r.question_id = ANY($6))
`, tenantID, brandID, monitoringCollectorType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"), nullableInt64Array(questionIDs)).Scan(&totalSampleCount); err != nil {
AND ($7::text IS NULL OR r.ai_platform_id = $7)
`, tenantID, brandID, monitoringCollectorType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"), nullableInt64Array(questionIDs), nullableString(aiPlatformID)).Scan(&totalSampleCount); err != nil {
return nil, response.ErrInternal(50041, "query_failed", "failed to count cited article denominator")
}
@@ -1715,10 +2033,11 @@ func (s *MonitoringService) loadCitedArticles(ctx context.Context, tenantID, bra
AND r.status = 'succeeded'
AND r.business_date BETWEEN $4::date AND $5::date
AND ($6::bigint[] IS NULL OR r.question_id = ANY($6))
AND ($7::text IS NULL OR r.ai_platform_id = $7)
GROUP BY cf.article_id
ORDER BY citation_count DESC, cf.article_id ASC
LIMIT 10
`, tenantID, brandID, monitoringCollectorType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"), nullableInt64Array(questionIDs))
`, tenantID, brandID, monitoringCollectorType, startDate.Format("2006-01-02"), endDate.Format("2006-01-02"), nullableInt64Array(questionIDs), nullableString(aiPlatformID))
if err != nil {
return nil, response.ErrInternal(50041, "query_failed", "failed to load cited articles")
}
@@ -2297,6 +2616,28 @@ func resolveMonitoringPlatforms(enabled []string) []monitoringPlatformMetadata {
return platforms
}
func filterMonitoringPlatforms(platforms []monitoringPlatformMetadata, aiPlatformID *string) []monitoringPlatformMetadata {
platformID := normalizedOptionalString(aiPlatformID)
if platformID == "" {
return platforms
}
filtered := make([]monitoringPlatformMetadata, 0, 1)
for _, platform := range platforms {
if platform.ID == platformID {
filtered = append(filtered, platform)
return filtered
}
}
return []monitoringPlatformMetadata{
{
ID: platformID,
Name: platformDisplayName(platformID),
},
}
}
func platformDisplayName(platformID string) string {
if name, ok := monitoringPlatformNames[platformID]; ok {
return name
@@ -2307,6 +2648,13 @@ func platformDisplayName(platformID string) string {
return strings.ToUpper(platformID)
}
func normalizedOptionalString(value *string) string {
if value == nil {
return ""
}
return strings.TrimSpace(*value)
}
func derivePlatformSampleStatus(rawStatus string, accessState monitoringAccessState) string {
switch strings.TrimSpace(rawStatus) {
case "sampled":
@@ -65,3 +65,31 @@ func TestEncodeMonitoringProjectionRebuildEnvelopePreservesBusinessDate(t *testi
require.NoError(t, err)
assert.Equal(t, "2026-04-12", envelope.BusinessDate)
}
func TestFilterMonitoringPlatformsReturnsSelectedPlatform(t *testing.T) {
platforms := []monitoringPlatformMetadata{
{ID: "deepseek", Name: "DeepSeek"},
{ID: "qwen", Name: "通义千问"},
{ID: "doubao", Name: "豆包"},
}
selected := "qwen"
filtered := filterMonitoringPlatforms(platforms, &selected)
assert.Equal(t, []monitoringPlatformMetadata{
{ID: "qwen", Name: "通义千问"},
}, filtered)
}
func TestFilterMonitoringPlatformsFallsBackToDisplayName(t *testing.T) {
platforms := []monitoringPlatformMetadata{
{ID: "deepseek", Name: "DeepSeek"},
}
selected := "doubao"
filtered := filterMonitoringPlatforms(platforms, &selected)
assert.Equal(t, []monitoringPlatformMetadata{
{ID: "doubao", Name: "豆包"},
}, filtered)
}
@@ -3,6 +3,8 @@ package app
import (
"strings"
"testing"
"github.com/geo-platform/tenant-api/internal/tenant/prompts"
)
func TestBuildPromptContextUsesChineseLabels(t *testing.T) {
@@ -45,7 +47,8 @@ func TestBuildGenerationLengthGuidanceForEnglishLocaleIsWrittenInChinese(t *test
}
func TestBuildGenerationPromptAddsTopXBrandPriorityRules(t *testing.T) {
prompt := buildGenerationPrompt("top_x_article", "Top X 推荐文章", nil, map[string]interface{}{
promptTemplate, _ := prompts.ApplyPlatformTemplatePromptOverrides("top_x_article", nil, []byte(`{}`))
prompt := buildGenerationPrompt("top_x_article", "Top X 推荐文章", promptTemplate, map[string]interface{}{
"topic": "合肥全屋定制",
"brand_name": "安徽海翔家居用品销售有限公司",
"locale": "zh-CN",
@@ -54,14 +57,54 @@ func TestBuildGenerationPromptAddsTopXBrandPriorityRules(t *testing.T) {
for _, expected := range []string{
"模板专项要求:",
"安徽海翔家居用品销售有限公司",
"排在第 1 位",
"篇幅应明显多于其他对象",
"结论部分先明确推荐",
"首个重点分析章节",
"顺序不代表评价或排名",
"不得表述为第一名",
"必须把其一级节点逐一写成正文 H2 小标题",
"严格按给定顺序展开",
} {
if !strings.Contains(prompt, expected) {
t.Fatalf("buildGenerationPrompt() = %q, want %q", prompt, expected)
}
}
for _, unexpected := range []string{
"排在第 1 位",
"主推对象",
} {
if strings.Contains(prompt, unexpected) {
t.Fatalf("buildGenerationPrompt() = %q, should not contain legacy ranking copy %q", prompt, unexpected)
}
}
}
func TestBuildGenerationPromptTopXRepeatsOutlineExecutionRulesInBasePrompt(t *testing.T) {
promptTemplate, _ := prompts.ApplyPlatformTemplatePromptOverrides("top_x_article", nil, []byte(`{}`))
prompt := buildGenerationPrompt("top_x_article", "Top X 推荐文章", promptTemplate, map[string]interface{}{
"topic": "合肥全屋定制",
"locale": "zh-CN",
"article_outline": []interface{}{
map[string]interface{}{
"outline": "行业现状与选择思路",
"children": []interface{}{
map[string]interface{}{"outline": "当前需求变化"},
},
},
map[string]interface{}{"outline": "品牌观察"},
map[string]interface{}{"outline": "总结建议"},
},
}, "")
for _, expected := range []string{
"如当前上下文提供了 article_outline",
"不得调换、合并、跳过",
"不要自行新增同级章节",
"不要另起一套脱离大纲的总结结构",
} {
if !strings.Contains(prompt, expected) {
t.Fatalf("buildGenerationPrompt() = %q, want outline execution rule %q", prompt, expected)
}
}
}
func TestBuildGenerationPromptDoesNotAddTopXBrandRulesForOtherTemplates(t *testing.T) {
@@ -70,7 +113,7 @@ func TestBuildGenerationPromptDoesNotAddTopXBrandRulesForOtherTemplates(t *testi
"brand_name": "安徽海翔家居用品销售有限公司",
}, "")
if strings.Contains(prompt, "排在第 1 位") {
if strings.Contains(prompt, "首个重点分析章节") {
t.Fatalf("buildGenerationPrompt() = %q, should not include top-x ranking rules", prompt)
}
}