feat: add monitoring marked articles and retention updates
This commit is contained in:
@@ -279,6 +279,8 @@ type monitoringAliasResolution struct {
|
||||
ArticleID *int64
|
||||
PublishRecordID *int64
|
||||
NormalizedURLKey string
|
||||
SourceType string
|
||||
SourceID *int64
|
||||
ResolutionStatus string
|
||||
ResolutionConfidence string
|
||||
}
|
||||
@@ -298,6 +300,9 @@ type monitoringAliasCandidate struct {
|
||||
MatchKeys []string
|
||||
ArticleID *int64
|
||||
PublishRecordID *int64
|
||||
SourceType string
|
||||
SourceID *int64
|
||||
SourcePriority int
|
||||
RegistrableDomain string
|
||||
SiteKey string
|
||||
Host string
|
||||
@@ -316,6 +321,8 @@ type monitoringCitationFact struct {
|
||||
SiteKey string
|
||||
ArticleID *int64
|
||||
PublishRecordID *int64
|
||||
ContentSourceType *string
|
||||
ContentSourceID *int64
|
||||
ResolutionStatus string
|
||||
ResolutionConfidence string
|
||||
}
|
||||
@@ -2119,7 +2126,7 @@ func (s *MonitoringCallbackService) processTaskResult(ctx context.Context, task
|
||||
return nil, err
|
||||
}
|
||||
citationSourceCount++
|
||||
if fact.ArticleID != nil {
|
||||
if fact.ContentSourceType != nil && fact.ContentSourceID != nil {
|
||||
contentCitationCount++
|
||||
}
|
||||
}
|
||||
@@ -2591,23 +2598,61 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
|
||||
}
|
||||
|
||||
rows, err := tx.Query(ctx, `
|
||||
WITH alias_sources AS (
|
||||
SELECT
|
||||
normalized_url,
|
||||
article_id,
|
||||
publish_record_id,
|
||||
'published_article' AS source_type,
|
||||
article_id AS source_id,
|
||||
1 AS source_priority,
|
||||
registrable_domain,
|
||||
site_key,
|
||||
host,
|
||||
last_path_segment,
|
||||
article_title_snapshot,
|
||||
updated_at,
|
||||
id
|
||||
FROM monitoring_article_url_aliases
|
||||
WHERE tenant_id = $1
|
||||
AND confidence = 'high'
|
||||
UNION ALL
|
||||
SELECT
|
||||
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,
|
||||
site_key,
|
||||
host,
|
||||
last_path_segment,
|
||||
article_title AS article_title_snapshot,
|
||||
updated_at,
|
||||
id
|
||||
FROM monitoring_user_marked_articles
|
||||
WHERE tenant_id = $1
|
||||
AND expires_at > NOW()
|
||||
)
|
||||
SELECT
|
||||
normalized_url,
|
||||
article_id,
|
||||
publish_record_id,
|
||||
registrable_domain,
|
||||
source_type,
|
||||
source_id,
|
||||
source_priority,
|
||||
registrable_domain,
|
||||
site_key,
|
||||
host,
|
||||
last_path_segment,
|
||||
article_title_snapshot
|
||||
FROM monitoring_article_url_aliases
|
||||
WHERE tenant_id = $1
|
||||
AND (
|
||||
normalized_url = ANY($2::text[])
|
||||
OR NULLIF(LOWER(TRIM(registrable_domain)), '') = ANY($3::text[])
|
||||
OR NULLIF(LOWER(TRIM(site_key)), '') = ANY($3::text[])
|
||||
OR NULLIF(LOWER(TRIM(host)), '') = ANY($3::text[])
|
||||
)
|
||||
FROM alias_sources
|
||||
WHERE normalized_url = ANY($2::text[])
|
||||
OR NULLIF(LOWER(TRIM(registrable_domain)), '') = ANY($3::text[])
|
||||
OR NULLIF(LOWER(TRIM(site_key)), '') = ANY($3::text[])
|
||||
OR NULLIF(LOWER(TRIM(host)), '') = ANY($3::text[])
|
||||
ORDER BY updated_at DESC, id DESC
|
||||
`, tenantID, normalizedURLs, domainKeys)
|
||||
if err != nil {
|
||||
return nil, response.ErrInternal(50041, "alias_lookup_failed", "failed to resolve article aliases")
|
||||
@@ -2619,6 +2664,7 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
|
||||
var candidate monitoringAliasCandidate
|
||||
var articleID sql.NullInt64
|
||||
var publishRecordID sql.NullInt64
|
||||
var sourceID sql.NullInt64
|
||||
var registrableDomain sql.NullString
|
||||
var siteKey sql.NullString
|
||||
var host sql.NullString
|
||||
@@ -2628,6 +2674,9 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
|
||||
&candidate.NormalizedURL,
|
||||
&articleID,
|
||||
&publishRecordID,
|
||||
&candidate.SourceType,
|
||||
&sourceID,
|
||||
&candidate.SourcePriority,
|
||||
®istrableDomain,
|
||||
&siteKey,
|
||||
&host,
|
||||
@@ -2638,6 +2687,7 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
|
||||
}
|
||||
candidate.ArticleID = nullableInt64Value(articleID)
|
||||
candidate.PublishRecordID = nullableInt64Value(publishRecordID)
|
||||
candidate.SourceID = nullableInt64Value(sourceID)
|
||||
candidate.RegistrableDomain = strings.ToLower(strings.TrimSpace(nullableStringText(registrableDomain)))
|
||||
candidate.SiteKey = strings.ToLower(strings.TrimSpace(nullableStringText(siteKey)))
|
||||
candidate.Host = strings.ToLower(strings.TrimSpace(nullableStringText(host)))
|
||||
@@ -2665,8 +2715,14 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
|
||||
best = candidate
|
||||
continue
|
||||
}
|
||||
if score == bestScore && !sameInt64Pointer(candidate.ArticleID, best.ArticleID) {
|
||||
bestAmbiguous = true
|
||||
if score == bestScore && !sameMonitoringAliasCandidateSource(candidate, best) {
|
||||
if best.SourcePriority == 0 || candidate.SourcePriority == best.SourcePriority {
|
||||
bestAmbiguous = true
|
||||
}
|
||||
if best.SourcePriority == 0 || (candidate.SourcePriority > 0 && candidate.SourcePriority < best.SourcePriority) {
|
||||
bestAmbiguous = false
|
||||
best = candidate
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2677,6 +2733,8 @@ func (s *MonitoringCallbackService) loadAliasResolutions(ctx context.Context, tx
|
||||
ArticleID: cloneInt64(best.ArticleID),
|
||||
PublishRecordID: cloneInt64(best.PublishRecordID),
|
||||
NormalizedURLKey: best.NormalizedURL,
|
||||
SourceType: strings.TrimSpace(best.SourceType),
|
||||
SourceID: cloneInt64(best.SourceID),
|
||||
ResolutionStatus: "resolved",
|
||||
ResolutionConfidence: "high",
|
||||
}
|
||||
@@ -2745,19 +2803,28 @@ func sameInt64Pointer(left, right *int64) bool {
|
||||
return *left == *right
|
||||
}
|
||||
|
||||
func sameMonitoringAliasCandidateSource(left, right monitoringAliasCandidate) bool {
|
||||
leftType := strings.TrimSpace(left.SourceType)
|
||||
rightType := strings.TrimSpace(right.SourceType)
|
||||
if leftType == "" && rightType == "" {
|
||||
return sameInt64Pointer(left.ArticleID, right.ArticleID)
|
||||
}
|
||||
return leftType == rightType && sameInt64Pointer(left.SourceID, right.SourceID)
|
||||
}
|
||||
|
||||
func (s *MonitoringCallbackService) insertCitationFact(ctx context.Context, tx pgx.Tx, task *monitoringCollectTask, runID int64, fact monitoringCitationFact) error {
|
||||
if _, err := tx.Exec(ctx, `
|
||||
INSERT INTO monitoring_citation_facts (
|
||||
tenant_id, run_id, brand_id, ai_platform_id, business_date,
|
||||
cited_url, cited_title, normalized_url, host, registrable_domain,
|
||||
subdomain, suffix, site_key, article_id, publish_record_id,
|
||||
resolution_status, resolution_confidence
|
||||
content_source_type, content_source_id, resolution_status, resolution_confidence
|
||||
)
|
||||
VALUES (
|
||||
$1, $2, $3, $4, $5::date,
|
||||
$6, $7, $8, $9, $10,
|
||||
$11, $12, $13, $14, $15,
|
||||
$16, $17
|
||||
$16, $17, $18, $19
|
||||
)
|
||||
`,
|
||||
task.TenantID,
|
||||
@@ -2775,6 +2842,8 @@ func (s *MonitoringCallbackService) insertCitationFact(ctx context.Context, tx p
|
||||
fact.SiteKey,
|
||||
nullableInt64(fact.ArticleID),
|
||||
nullableInt64(fact.PublishRecordID),
|
||||
nullableString(fact.ContentSourceType),
|
||||
nullableInt64(fact.ContentSourceID),
|
||||
fact.ResolutionStatus,
|
||||
fact.ResolutionConfidence,
|
||||
); err != nil {
|
||||
@@ -2926,10 +2995,10 @@ func (s *MonitoringCallbackService) loadExistingRunSummary(ctx context.Context,
|
||||
var citationSourceCount int
|
||||
var contentCitationCount int
|
||||
if err := s.monitoringPool.QueryRow(ctx, `
|
||||
SELECT
|
||||
COUNT(*)::int AS citation_source_count,
|
||||
COUNT(*) FILTER (WHERE article_id IS NOT NULL AND $3::text <> 'qwen')::int AS content_citation_count
|
||||
FROM monitoring_citation_facts
|
||||
SELECT
|
||||
COUNT(*)::int AS citation_source_count,
|
||||
COUNT(*) FILTER (WHERE content_source_type IS NOT NULL AND content_source_id IS NOT NULL AND $3::text <> 'qwen')::int AS content_citation_count
|
||||
FROM monitoring_citation_facts
|
||||
WHERE tenant_id = $1
|
||||
AND run_id = $2
|
||||
`, task.TenantID, runID.Int64, normalizeMonitoringPlatformID(task.AIPlatformID)).Scan(&citationSourceCount, &contentCitationCount); err != nil {
|
||||
@@ -3173,6 +3242,8 @@ func buildMonitoringCitationFact(input MonitoringSourceItem, aliasMap map[string
|
||||
|
||||
var articleID *int64
|
||||
var publishRecordID *int64
|
||||
var contentSourceType *string
|
||||
var contentSourceID *int64
|
||||
var aliasResolution *monitoringAliasResolution
|
||||
if resolveContentCitations {
|
||||
articleID = cloneInt64(input.ArticleID)
|
||||
@@ -3186,6 +3257,10 @@ func buildMonitoringCitationFact(input MonitoringSourceItem, aliasMap map[string
|
||||
if publishRecordID == nil {
|
||||
publishRecordID = cloneInt64(alias.PublishRecordID)
|
||||
}
|
||||
if strings.TrimSpace(alias.SourceType) != "" && alias.SourceID != nil {
|
||||
contentSourceType = optionalString(strings.TrimSpace(alias.SourceType))
|
||||
contentSourceID = cloneInt64(alias.SourceID)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3217,6 +3292,8 @@ func buildMonitoringCitationFact(input MonitoringSourceItem, aliasMap map[string
|
||||
SiteKey: siteKey,
|
||||
ArticleID: articleID,
|
||||
PublishRecordID: publishRecordID,
|
||||
ContentSourceType: contentSourceType,
|
||||
ContentSourceID: contentSourceID,
|
||||
ResolutionStatus: resolutionStatus,
|
||||
ResolutionConfidence: resolutionConfidence,
|
||||
}, true
|
||||
|
||||
Reference in New Issue
Block a user