fix media supply citation attribution
Desktop Client Build / Resolve Build Metadata (push) Successful in 26s
Frontend CI / Frontend (push) Successful in 2m53s
Backend CI / Backend (push) Successful in 16m37s
Desktop Client Build / Build Desktop Client (push) Successful in 23m40s
Desktop Client Build / Publish Client Artifacts to NAS (push) Successful in 28s

This commit is contained in:
2026-06-25 23:53:35 +08:00
parent 89ef95b65d
commit 6780963c28
16 changed files with 1033 additions and 457 deletions
+3 -1
View File
@@ -46,7 +46,9 @@ func main() {
if mediaSupplyService == nil {
mediaSupplyService = tenantapp.NewMediaSupplyService(app.DB, app.Redis, app.Logger, app.ConfigStore).WithObjectStorage(app.ObjectStorage)
}
tenantapp.NewMediaSupplyWorker(mediaSupplyService, app.DB, app.Logger).Start(workerCtx)
tenantapp.NewMediaSupplyWorker(mediaSupplyService, app.DB, app.Logger).
WithMonitoringPool(app.MonitoringDB).
Start(workerCtx)
knowledgeCleanupService := tenantapp.NewKnowledgeService(
app.DB,
app.RetrievalProvider,
@@ -17,9 +17,10 @@ import (
)
type MediaSupplyWorker struct {
service *MediaSupplyService
pool *pgxpool.Pool
logger *zap.Logger
service *MediaSupplyService
pool *pgxpool.Pool
monitoringPool *pgxpool.Pool
logger *zap.Logger
}
type mediaSupplySyncJob struct {
@@ -59,6 +60,13 @@ func NewMediaSupplyWorker(service *MediaSupplyService, pool *pgxpool.Pool, logge
return &MediaSupplyWorker{service: service, pool: pool, logger: logger}
}
func (w *MediaSupplyWorker) WithMonitoringPool(monitoringPool *pgxpool.Pool) *MediaSupplyWorker {
if w != nil {
w.monitoringPool = monitoringPool
}
return w
}
func (w *MediaSupplyWorker) Start(ctx context.Context) {
if w == nil || w.service == nil || w.pool == nil {
return
@@ -917,7 +925,165 @@ func (w *MediaSupplyWorker) updateOrderBacklinks(ctx context.Context, order medi
`, order.ID, "published", remaining, firstMatchedMeijiequanOrderCode(matches), firstMatchedMeijiequanOrderID(matches)); err != nil {
return 0, err
}
return updatedLinks, tx.Commit(ctx)
if err := tx.Commit(ctx); err != nil {
return 0, err
}
w.refreshMonitoringAttributionForMediaSupplyBacklinks(ctx, order.ID)
return updatedLinks, nil
}
func (w *MediaSupplyWorker) refreshMonitoringAttributionForMediaSupplyBacklinks(ctx context.Context, orderID int64) {
if w == nil || w.monitoringPool == nil || w.pool == nil || orderID <= 0 {
return
}
rows, err := w.pool.Query(ctx, `
SELECT
o.tenant_id,
i.id,
i.external_article_url
FROM media_supply_order_items i
JOIN media_supply_orders o ON o.id = i.order_id
WHERE o.id = $1
AND o.deleted_at IS NULL
AND NULLIF(TRIM(COALESCE(i.external_article_url, '')), '') IS NOT NULL
`, orderID)
if err != nil {
w.logWarn("media supply monitoring attribution backlink lookup failed", zap.Int64("order_id", orderID), zap.Error(err))
return
}
defer rows.Close()
type backlinkSource struct {
tenantID int64
itemID int64
url string
}
sources := make([]backlinkSource, 0)
for rows.Next() {
var source backlinkSource
if scanErr := rows.Scan(&source.tenantID, &source.itemID, &source.url); scanErr != nil {
w.logWarn("media supply monitoring attribution backlink scan failed", zap.Int64("order_id", orderID), zap.Error(scanErr))
return
}
source.url = strings.TrimSpace(source.url)
if source.tenantID <= 0 || source.itemID <= 0 || source.url == "" {
continue
}
sources = append(sources, source)
}
if err := rows.Err(); err != nil {
w.logWarn("media supply monitoring attribution backlink iterate failed", zap.Int64("order_id", orderID), zap.Error(err))
return
}
for _, source := range sources {
if _, err := w.refreshMonitoringAttributionForMediaSupplyBacklink(ctx, source.tenantID, source.itemID, source.url); err != nil {
w.logWarn(
"media supply monitoring attribution refresh failed",
zap.Int64("tenant_id", source.tenantID),
zap.Int64("order_id", orderID),
zap.Int64("item_id", source.itemID),
zap.Error(err),
)
}
}
}
func (w *MediaSupplyWorker) refreshMonitoringAttributionForMediaSupplyBacklink(ctx context.Context, tenantID, itemID int64, rawURL string) (int64, error) {
matchKeys := monitoringCitationCandidateKeys(rawURL, normalizeCitationURL(rawURL))
if len(matchKeys) == 0 {
return 0, nil
}
normalizedURL := normalizeCitationURL(rawURL)
host, registrableDomain, _, _ := deriveCitationURLParts(rawURL, normalizedURL)
candidateURLs := uniqueTrimmedStrings(rawURL, normalizedURL)
rows, err := w.monitoringPool.Query(ctx, `
SELECT id, cited_url, normalized_url
FROM monitoring_citation_facts
WHERE tenant_id = $1
AND (
(content_source_type IS NULL AND content_source_id IS NULL)
OR content_source_type = ANY($5::text[])
)
AND (
cited_url = ANY($2::text[])
OR normalized_url = ANY($2::text[])
OR NULLIF(LOWER(TRIM(host)), '') = NULLIF($3, '')
OR NULLIF(LOWER(TRIM(registrable_domain)), '') = NULLIF($4, '')
OR NULLIF(LOWER(TRIM(site_key)), '') = NULLIF($4, '')
)
`, tenantID, candidateURLs, strings.ToLower(strings.TrimSpace(host)), strings.ToLower(strings.TrimSpace(registrableDomain)), []string{
monitoringContentSourcePublishedArticle,
monitoringContentSourceManualMark,
})
if err != nil {
return 0, err
}
defer rows.Close()
matchKeySet := make(map[string]struct{}, len(matchKeys))
for _, key := range matchKeys {
matchKeySet[strings.TrimSpace(key)] = struct{}{}
}
matchedIDs := make([]int64, 0)
for rows.Next() {
var id int64
var citedURL string
var factNormalizedURL string
if scanErr := rows.Scan(&id, &citedURL, &factNormalizedURL); scanErr != nil {
return 0, scanErr
}
for _, factKey := range monitoringCitationCandidateKeys(citedURL, factNormalizedURL) {
if _, ok := matchKeySet[strings.TrimSpace(factKey)]; ok {
matchedIDs = append(matchedIDs, id)
break
}
}
}
if err := rows.Err(); err != nil {
return 0, err
}
if len(matchedIDs) == 0 {
return 0, nil
}
tag, err := w.monitoringPool.Exec(ctx, `
UPDATE monitoring_citation_facts
SET content_source_type = $3,
content_source_id = $2
WHERE tenant_id = $1
AND id = ANY($4::bigint[])
AND (
(content_source_type IS NULL AND content_source_id IS NULL)
OR content_source_type = ANY($5::text[])
)
`, tenantID, itemID, monitoringContentSourceMediaSupplyOrderItem, matchedIDs, []string{
monitoringContentSourcePublishedArticle,
monitoringContentSourceManualMark,
})
if err != nil {
return 0, err
}
return tag.RowsAffected(), nil
}
func uniqueTrimmedStrings(values ...string) []string {
result := make([]string, 0, len(values))
seen := make(map[string]struct{}, len(values))
for _, value := range values {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
continue
}
if _, exists := seen[trimmed]; exists {
continue
}
seen[trimmed] = struct{}{}
result = append(result, trimmed)
}
return result
}
func (w *MediaSupplyWorker) claimSyncJob(ctx context.Context) (*mediaSupplySyncJob, bool, error) {
@@ -2819,10 +2819,10 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
normalized_url,
article_id,
publish_record_id,
'published_article' AS source_type,
article_id AS source_id,
1 AS source_priority,
registrable_domain,
'published_article' AS source_type,
article_id AS source_id,
2 AS source_priority,
registrable_domain,
site_key,
host,
last_path_segment,
@@ -2837,10 +2837,10 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
normalized_url,
NULL AS article_id,
NULL AS publish_record_id,
'manual_mark' AS source_type,
id AS source_id,
2 AS source_priority,
registrable_domain,
'manual_mark' AS source_type,
id AS source_id,
3 AS source_priority,
registrable_domain,
site_key,
host,
last_path_segment,
@@ -2855,10 +2855,10 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
normalized_url,
article_id,
publish_record_id,
source_type,
source_id,
source_priority,
registrable_domain,
source_type,
source_id,
source_priority,
registrable_domain,
site_key,
host,
last_path_segment,
@@ -2916,6 +2916,36 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
return nil, response.ErrInternal(50041, "alias_scan_failed", "failed to iterate article aliases")
}
mediaAliases, err := loadMonitoringMediaSupplyPublishedAliases(ctx, s.businessPool, tenantID)
if err != nil {
return nil, err
}
for _, alias := range mediaAliases {
candidate := monitoringAliasCandidate{
NormalizedURL: alias.NormalizedURL,
SourceType: strings.TrimSpace(alias.SourceType),
SourcePriority: alias.SourcePriority,
TitleKey: normalizeMonitoringAliasTitle(alias.ArticleTitle),
MatchKeys: monitoringCitationCandidateKeys(alias.OriginalURL, alias.NormalizedURL),
}
if alias.ArticleID > 0 {
value := alias.ArticleID
candidate.ArticleID = &value
}
if alias.SourceID > 0 {
value := alias.SourceID
candidate.SourceID = &value
}
if lastPathSegment := monitoringAliasLastPathSegment(alias.NormalizedURL); lastPathSegment != nil {
candidate.LastPathSegment = strings.TrimSpace(*lastPathSegment)
}
host, registrableDomain, _, _ := deriveCitationURLParts(alias.OriginalURL, alias.NormalizedURL)
candidate.Host = strings.ToLower(strings.TrimSpace(host))
candidate.RegistrableDomain = strings.ToLower(strings.TrimSpace(registrableDomain))
candidate.SiteKey = candidate.RegistrableDomain
candidates = append(candidates, candidate)
}
for _, input := range lookupInputs {
bestScore := -1
bestAmbiguous := false
@@ -583,6 +583,38 @@ func TestBuildMonitoringCitationFactResolvesContentForQwen(t *testing.T) {
}
}
func TestBuildMonitoringCitationFactResolvesMediaSupplyContentSource(t *testing.T) {
aliasArticleID := int64(42)
sourceID := int64(9001)
normalizedURL := "https://example.com/submitted/article"
input := MonitoringSourceItem{
URL: "https://example.com/submitted/article?utm_source=chat",
NormalizedURL: &normalizedURL,
}
aliasMap := map[string]monitoringAliasResolution{
normalizedURL: {
ArticleID: &aliasArticleID,
NormalizedURLKey: normalizedURL,
SourceType: monitoringContentSourceMediaSupplyOrderItem,
SourceID: &sourceID,
},
}
fact, ok := buildMonitoringCitationFact(input, aliasMap, true)
if !ok {
t.Fatal("buildMonitoringCitationFact() returned !ok")
}
if fact.ArticleID == nil || *fact.ArticleID != aliasArticleID {
t.Fatalf("ArticleID = %v, want %d", fact.ArticleID, aliasArticleID)
}
if fact.ContentSourceType == nil || *fact.ContentSourceType != monitoringContentSourceMediaSupplyOrderItem {
t.Fatalf("ContentSourceType = %v, want %q", fact.ContentSourceType, monitoringContentSourceMediaSupplyOrderItem)
}
if fact.ContentSourceID == nil || *fact.ContentSourceID != sourceID {
t.Fatalf("ContentSourceID = %v, want %d", fact.ContentSourceID, sourceID)
}
}
func TestScoreMonitoringAliasCandidateUsesCanonicalArticleKey(t *testing.T) {
input := monitoringAliasLookupInput{
NormalizedURL: "https://m.163.com/dy/article/KRPRE8DJ0556MCR0.html",
@@ -613,6 +645,70 @@ func TestScoreMonitoringAliasCandidateRejectsSameDomainDifferentArticle(t *testi
}
}
func TestMonitoringPublishedAliasIndexMatchesMediaSupplySubmission(t *testing.T) {
index := make(monitoringPublishedAliasIndex)
sourceID := int64(9001)
mediaAlias := monitoringPublishedAlias{
ArticleID: 42,
ArticleTitle: "投稿文章",
PublishPlatform: monitoringMediaSupplySubmissionCitationLabel,
OriginalURL: "https://m.example.com/submitted/article?id=123&utm_source=chat",
NormalizedURL: "https://www.example.com/submitted/article?id=123",
SourceType: monitoringContentSourceMediaSupplyOrderItem,
SourceID: sourceID,
SourcePriority: 1,
}
addMonitoringPublishedAliasToIndex(index, mediaAlias)
match, ok := index.Match(
"https://www.example.com/submitted/article?id=123&utm_medium=ai",
"https://www.example.com/submitted/article?id=123",
)
if !ok {
t.Fatal("Match() = false, want media supply alias match")
}
if match.SourceType != monitoringContentSourceMediaSupplyOrderItem {
t.Fatalf("SourceType = %q, want %q", match.SourceType, monitoringContentSourceMediaSupplyOrderItem)
}
if match.SourceID != sourceID {
t.Fatalf("SourceID = %d, want %d", match.SourceID, sourceID)
}
}
func TestMonitoringPublishedAliasIndexPrefersMediaSupplySubmission(t *testing.T) {
index := make(monitoringPublishedAliasIndex)
normalizedURL := "https://www.example.com/submitted/article?id=123"
addMonitoringPublishedAliasToIndex(index, monitoringPublishedAlias{
ArticleID: 42,
OriginalURL: normalizedURL,
NormalizedURL: normalizedURL,
SourceType: monitoringContentSourcePublishedArticle,
SourceID: 42,
SourcePriority: 2,
})
addMonitoringPublishedAliasToIndex(index, monitoringPublishedAlias{
ArticleID: 42,
OriginalURL: normalizedURL,
NormalizedURL: normalizedURL,
SourceType: monitoringContentSourceMediaSupplyOrderItem,
SourceID: 9001,
SourcePriority: 1,
})
match, ok := index.Match(normalizedURL, normalizedURL)
if !ok {
t.Fatal("Match() = false, want media supply alias match")
}
if match.SourceType != monitoringContentSourceMediaSupplyOrderItem {
t.Fatalf("SourceType = %q, want %q", match.SourceType, monitoringContentSourceMediaSupplyOrderItem)
}
if match.SourceID != 9001 {
t.Fatalf("SourceID = %d, want 9001", match.SourceID)
}
}
func TestBuildMonitoringRawPayloadDeepSeekIncludesSearchResultsInCitationInputs(t *testing.T) {
searchTitle := "搜索网页结果"
@@ -32,7 +32,78 @@ type monitoringAliasQuerier interface {
Query(ctx context.Context, sql string, args ...interface{}) (pgx.Rows, error)
}
func loadMonitoringPublishedAliasIndex(ctx context.Context, q monitoringAliasQuerier, tenantID int64) (monitoringPublishedAliasIndex, error) {
const (
monitoringContentSourcePublishedArticle = "published_article"
monitoringContentSourceManualMark = "manual_mark"
monitoringContentSourceMediaSupplyOrderItem = "media_supply_order_item"
monitoringMediaSupplySubmissionCitationLabel = "投稿引用"
)
func loadMonitoringMediaSupplyPublishedAliases(ctx context.Context, q monitoringAliasQuerier, tenantID int64) ([]monitoringPublishedAlias, error) {
if q == nil {
return nil, nil
}
rows, err := q.Query(ctx, `
SELECT
o.article_id,
i.id,
COALESCE(NULLIF(o.title, ''), NULLIF(i.resource_name_snapshot, ''), '未命名文章') AS article_title,
COALESCE(NULLIF(i.resource_name_snapshot, ''), $2) AS publish_platform,
i.external_article_url
FROM media_supply_order_items i
JOIN media_supply_orders o ON o.id = i.order_id
WHERE o.tenant_id = $1
AND o.deleted_at IS NULL
AND NULLIF(TRIM(COALESCE(i.external_article_url, '')), '') IS NOT NULL
ORDER BY i.updated_at DESC, i.id DESC
`, tenantID, monitoringMediaSupplySubmissionCitationLabel)
if err != nil {
return nil, responseInternalAliasLookupError()
}
defer rows.Close()
items := make([]monitoringPublishedAlias, 0)
for rows.Next() {
var articleID sql.NullInt64
var itemID int64
var articleTitle string
var publishPlatform string
var originalURL string
if scanErr := rows.Scan(
&articleID,
&itemID,
&articleTitle,
&publishPlatform,
&originalURL,
); scanErr != nil {
return nil, responseInternalAliasScanError()
}
normalizedURL := normalizeCitationURL(originalURL)
if normalizedURL == "" {
continue
}
item := monitoringPublishedAlias{
ArticleTitle: articleTitle,
PublishPlatform: publishPlatform,
OriginalURL: strings.TrimSpace(originalURL),
NormalizedURL: normalizedURL,
SourceType: monitoringContentSourceMediaSupplyOrderItem,
SourceID: itemID,
SourcePriority: 1,
}
if articleID.Valid {
item.ArticleID = articleID.Int64
}
items = append(items, item)
}
if err := rows.Err(); err != nil {
return nil, responseInternalAliasScanError()
}
return items, nil
}
func loadMonitoringPublishedAliasIndex(ctx context.Context, q monitoringAliasQuerier, businessQ monitoringAliasQuerier, tenantID int64) (monitoringPublishedAliasIndex, error) {
rows, err := q.Query(ctx, `
WITH alias_sources AS (
SELECT
@@ -42,11 +113,11 @@ func loadMonitoringPublishedAliasIndex(ctx context.Context, q monitoringAliasQue
COALESCE(publish_platform_name_snapshot, '已发布内容') AS publish_platform,
original_url,
normalized_url,
'published_article' AS source_type,
article_id AS source_id,
1 AS source_priority,
updated_at AS sort_updated_at,
id AS sort_id
'published_article' AS source_type,
article_id AS source_id,
2 AS source_priority,
updated_at AS sort_updated_at,
id AS sort_id
FROM monitoring_article_url_aliases
WHERE tenant_id = $1
AND confidence = 'high'
@@ -58,11 +129,11 @@ func loadMonitoringPublishedAliasIndex(ctx context.Context, q monitoringAliasQue
COALESCE(NULLIF(publish_platform, ''), '外部文章') AS publish_platform,
original_url,
normalized_url,
'manual_mark' AS source_type,
id AS source_id,
2 AS source_priority,
updated_at AS sort_updated_at,
id AS sort_id
'manual_mark' AS source_type,
id AS source_id,
3 AS source_priority,
updated_at AS sort_updated_at,
id AS sort_id
FROM monitoring_user_marked_articles
WHERE tenant_id = $1
AND expires_at > NOW()
@@ -74,9 +145,9 @@ func loadMonitoringPublishedAliasIndex(ctx context.Context, q monitoringAliasQue
publish_platform,
original_url,
normalized_url,
source_type,
source_id,
source_priority
source_type,
source_id,
source_priority
FROM alias_sources
ORDER BY source_priority ASC, sort_updated_at DESC, sort_id DESC
`, tenantID)
@@ -111,32 +182,44 @@ func loadMonitoringPublishedAliasIndex(ctx context.Context, q monitoringAliasQue
item.PublishRecordID = nullableInt64Value(publishRecordID)
item.OriginalURL = originalURL
item.NormalizedURL = normalizedURL
for _, key := range monitoringCitationCandidateKeys(originalURL, normalizedURL) {
existing, exists := index[key]
if exists && existing.SourcePriority < item.SourcePriority {
continue
}
if exists && item.SourcePriority < existing.SourcePriority {
index[key] = item
continue
}
if exists && (existing.SourceType != item.SourceType || existing.SourceID != item.SourceID) {
existing.Ambiguous = true
index[key] = existing
continue
}
if !exists {
index[key] = item
}
}
addMonitoringPublishedAliasToIndex(index, item)
}
if err := rows.Err(); err != nil {
return nil, responseInternalAliasScanError()
}
mediaAliases, err := loadMonitoringMediaSupplyPublishedAliases(ctx, businessQ, tenantID)
if err != nil {
return nil, err
}
for _, item := range mediaAliases {
addMonitoringPublishedAliasToIndex(index, item)
}
return index, nil
}
func addMonitoringPublishedAliasToIndex(index monitoringPublishedAliasIndex, item monitoringPublishedAlias) {
for _, key := range monitoringCitationCandidateKeys(item.OriginalURL, item.NormalizedURL) {
existing, exists := index[key]
if exists && existing.SourcePriority < item.SourcePriority {
continue
}
if exists && item.SourcePriority < existing.SourcePriority {
index[key] = item
continue
}
if exists && (existing.SourceType != item.SourceType || existing.SourceID != item.SourceID) {
existing.Ambiguous = true
index[key] = existing
continue
}
if !exists {
index[key] = item
}
}
}
func (index monitoringPublishedAliasIndex) Match(rawURL, normalizedURL string) (monitoringPublishedAlias, bool) {
for _, key := range monitoringCitationCandidateKeys(rawURL, normalizedURL) {
item, ok := index[key]
@@ -3733,9 +3733,9 @@ func (s *MonitoringService) loadCitedArticleSourceMeta(ctx context.Context, tena
manualIDs := make([]int64, 0, len(sources))
for _, source := range sources {
switch source.SourceType {
case "published_article":
case monitoringContentSourcePublishedArticle:
publishedIDs = append(publishedIDs, source.SourceID)
case "manual_mark":
case monitoringContentSourceManualMark:
manualIDs = append(manualIDs, source.SourceID)
}
}
@@ -3766,7 +3766,7 @@ func (s *MonitoringService) loadCitedArticleSourceMeta(ctx context.Context, tena
item.ArticleID = &value
item.ArticleTitle = repairMonitoringMojibake(item.ArticleTitle)
item.PublishPlatform = repairMonitoringMojibake(item.PublishPlatform)
item.SourceType = "published_article"
item.SourceType = monitoringContentSourcePublishedArticle
item.SourceID = articleID
result[monitoringContentSourceKey(item.SourceType, item.SourceID)] = item
}
@@ -3796,13 +3796,68 @@ func (s *MonitoringService) loadCitedArticleSourceMeta(ctx context.Context, tena
}
item.ArticleTitle = repairMonitoringMojibake(item.ArticleTitle)
item.PublishPlatform = repairMonitoringMojibake(item.PublishPlatform)
item.SourceType = "manual_mark"
item.SourceType = monitoringContentSourceManualMark
result[monitoringContentSourceKey(item.SourceType, item.SourceID)] = item
}
if err := rows.Err(); err != nil {
return nil, response.ErrInternal(50041, "scan_failed", "failed to iterate marked article metadata")
}
}
mediaItems, err := s.loadMediaSupplyCitedArticleSourceMeta(ctx, tenantID, sources)
if err != nil {
return nil, err
}
for key, item := range mediaItems {
result[key] = item
}
return result, nil
}
func (s *MonitoringService) loadMediaSupplyCitedArticleSourceMeta(ctx context.Context, tenantID int64, sources []monitoringCitedArticleSourceRow) (map[string]MonitoringCitedArticle, error) {
result := make(map[string]MonitoringCitedArticle)
mediaItemIDs := make([]int64, 0, len(sources))
for _, source := range sources {
if source.SourceType == monitoringContentSourceMediaSupplyOrderItem {
mediaItemIDs = append(mediaItemIDs, source.SourceID)
}
}
if len(mediaItemIDs) == 0 {
return result, nil
}
rows, err := s.businessPool.Query(ctx, `
SELECT
i.id,
o.article_id,
COALESCE(NULLIF(o.title, ''), NULLIF(i.resource_name_snapshot, ''), '未命名文章') AS article_title,
COALESCE(NULLIF(i.resource_name_snapshot, ''), $3) AS publish_platform,
i.external_article_url
FROM media_supply_order_items i
JOIN media_supply_orders o ON o.id = i.order_id
WHERE o.tenant_id = $1
AND i.id = ANY($2)
AND o.deleted_at IS NULL
`, tenantID, mediaItemIDs, monitoringMediaSupplySubmissionCitationLabel)
if err != nil {
return nil, response.ErrInternal(50041, "query_failed", "failed to load media supply citation metadata")
}
defer rows.Close()
for rows.Next() {
var item MonitoringCitedArticle
var articleID sql.NullInt64
if scanErr := rows.Scan(&item.SourceID, &articleID, &item.ArticleTitle, &item.PublishPlatform, &item.SourceURL); scanErr != nil {
return nil, response.ErrInternal(50041, "scan_failed", "failed to parse media supply citation metadata")
}
item.ArticleID = nullableInt64Value(articleID)
item.ArticleTitle = repairMonitoringMojibake(item.ArticleTitle)
item.PublishPlatform = repairMonitoringMojibake(item.PublishPlatform)
item.SourceType = monitoringContentSourceMediaSupplyOrderItem
result[monitoringContentSourceKey(item.SourceType, item.SourceID)] = item
}
if err := rows.Err(); err != nil {
return nil, response.ErrInternal(50041, "scan_failed", "failed to iterate media supply citation metadata")
}
return result, nil
}
@@ -4012,7 +4067,7 @@ func (s *MonitoringService) loadCitationsForRuns(ctx context.Context, tenantID i
return result, nil
}
aliasIndex, err := loadMonitoringPublishedAliasIndex(ctx, s.monitoringPool, tenantID)
aliasIndex, err := loadMonitoringPublishedAliasIndex(ctx, s.monitoringPool, s.businessPool, tenantID)
if err != nil {
return nil, err
}
@@ -4120,7 +4175,7 @@ func (s *MonitoringService) loadCitationsForRuns(ctx context.Context, tenantID i
articleTitle = &title
aliasSourceType := strings.TrimSpace(alias.SourceType)
if aliasSourceType == "" {
aliasSourceType = "published_article"
aliasSourceType = monitoringContentSourcePublishedArticle
}
sourceType = &aliasSourceType
aliasSourceID := alias.SourceID