多个链接不等于多份证据,新闻核验看板怎样合并来源

同一篇通稿被十个网站转载,页面若只数链接,会显示"10 个独立来源",可信指数也被虚高。新闻核验真正要统计的是相互独立的证据源,以及这些来源分别支持或反驳了哪条陈述。

新闻可信度看板已经展示可信指数、独立来源和待核事实,场景页也区分官方发布、现场媒体和社交平台。我将页面数据拆成陈述、来源和证据三层,使"来源数量"不再等于 URL 数量。

一条新闻可以包含多条待核陈述

整篇新闻只有一个真假值会丢失细节。同一报道中的时间可能已经证实,伤亡数字仍在变化,地点描述也可能有误。

ts 复制代码
export type ClaimStatus = 'unverified' | 'supported' | 'disputed' | 'insufficient'
export type EvidenceStance = 'support' | 'contradict' | 'context'

export interface Claim {
  id: string
  text: string
  status: ClaimStatus
  updatedAt: number
}

export interface Source {
  id: string
  publisher: string
  canonicalUrl: string
  originId: string
  publishedAt: number
  sourceType: 'official' | 'media' | 'eyewitness' | 'social'
}

export interface Evidence {
  id: string
  claimId: string
  sourceId: string
  stance: EvidenceStance
  excerpt: string
  capturedAt: number
}

originId 表示内容最初来源。转载站点可以拥有不同 Source.id 和网址,但只要追溯到同一篇原稿,它们就在独立性统计中归为一组。

URL 先规范化,再判断转载关系

常见链接带有追踪参数和锚点。若不清理,同一页面就可能被当成多个来源。

ts 复制代码
export function canonicalize(raw: string): string {
  try {
    const url = new URL(raw)
    const removable = ['utm_source', 'utm_medium', 'utm_campaign', 'from', 'share']
    removable.forEach((key: string) => url.searchParams.delete(key))
    url.hash = ''
    url.hostname = url.hostname.toLocaleLowerCase()
    if (url.pathname.length > 1 && url.pathname.endsWith('/')) {
      url.pathname = url.pathname.slice(0, -1)
    }
    return url.toString()
  } catch (_) {
    return raw.trim()
  }
}

export function uniqueOrigins(sources: Source[]): Source[] {
  const map = new Map<string, Source>()
  sources.forEach((source: Source) => {
    const key = source.originId.length > 0 ? source.originId :
      canonicalize(source.canonicalUrl)
    const previous = map.get(key)
    if (!previous || source.publishedAt < previous.publishedAt) {
      map.set(key, source)
    }
  })
  return Array.from(map.values())
}

仅靠 URL 不能可靠识别所有转载。真实服务还需要内容指纹、引用链和人工确认;本地界面先允许录入 originId,把独立性规则放在模型中,而不是写死为域名数量。

每条陈述独立计算状态

来源可信度不能替代证据关系。官方来源可能只确认事件发生,没有确认传播最广的数字。状态计算只看与当前陈述关联的证据。

ts 复制代码
export interface ClaimAssessment {
  claimId: string
  status: ClaimStatus
  independentSupport: number
  independentContradiction: number
}

export function assessClaim(
  claim: Claim,
  evidence: Evidence[],
  sources: Source[]
): ClaimAssessment {
  const sourceMap = new Map(sources.map((source: Source) =>
    [source.id, source]))
  const related = evidence.filter((item: Evidence) => item.claimId === claim.id)
  const support = new Set<string>()
  const contradiction = new Set<string>()

  related.forEach((item: Evidence) => {
    const source = sourceMap.get(item.sourceId)
    if (!source || item.stance === 'context') return
    const origin = source.originId || canonicalize(source.canonicalUrl)
    if (item.stance === 'support') support.add(origin)
    if (item.stance === 'contradict') contradiction.add(origin)
  })

  let status: ClaimStatus = 'unverified'
  if (support.size >= 2 && contradiction.size === 0) status = 'supported'
  else if (contradiction.size > 0) status = 'disputed'
  else if (support.size === 1) status = 'insufficient'

  return {
    claimId: claim.id,
    status,
    independentSupport: support.size,
    independentContradiction: contradiction.size
  }
}

这里设定"两份独立支持且无反证"为本地演示规则,不代表新闻事实核验的通用结论。界面应把依据数量和冲突状态展示出来,让读者知道评分如何形成。

看板评分保留待核数量

总分是导航信息,不能覆盖明细。我根据已支持、存在争议和证据不足的陈述生成简单分数,同时返回待核数量。

ts 复制代码
export interface BoardSummary {
  score: number
  independentSources: number
  pendingClaims: number
  assessments: ClaimAssessment[]
}

export function summarizeBoard(
  claims: Claim[],
  evidence: Evidence[],
  sources: Source[]
): BoardSummary {
  const assessments = claims.map((claim: Claim) =>
    assessClaim(claim, evidence, sources))
  const raw = assessments.reduce((sum: number, item: ClaimAssessment) => {
    if (item.status === 'supported') return sum + 100
    if (item.status === 'insufficient') return sum + 55
    if (item.status === 'disputed') return sum + 25
    return sum + 40
  }, 0)
  const score = assessments.length === 0 ? 0 :
    Math.round(raw / assessments.length)
  const pendingClaims = assessments.filter((item: ClaimAssessment) =>
    item.status !== 'supported').length

  return {
    score,
    independentSources: uniqueOrigins(sources).length,
    pendingClaims,
    assessments
  }
}

页面上的"可信指数 82""独立来源 7""待核事实 3"就可以从同一份 BoardSummary 读取。点击待核数量时,列表过滤到 status !== 'supported',不用另外维护一套待办数组。

晚到证据不覆盖人工结论

证据抓取可能异步完成。自动评估结果到达时,如果用户已经人工标记陈述,需要保留人工版本。模型可以增加 decisionSource 和修订号。

ts 复制代码
export interface ClaimDecision {
  claimId: string
  status: ClaimStatus
  decisionSource: 'automatic' | 'manual'
  revision: number
}

export function applyAutomaticDecision(
  current: ClaimDecision,
  next: ClaimDecision
): ClaimDecision {
  if (current.decisionSource === 'manual') {
    return current
  }
  return next.revision >= current.revision ? next : current
}

报告页可以继续使用环形综合评分,但"值得关注"区域应由争议项和待核项生成,而不是固定文案。

当前项目使用本地演示数据,不会自动判断现实新闻真伪。后续接入抓取或检索服务时,外部能力只负责产生来源与证据;来源归并、陈述状态、人工覆盖和页面选择仍留在本地业务层。即使一下子导入几十个链接,看板统计的也会是证据链,而不是链接数量。

相关推荐
lisanmengmeng2 分钟前
pushgateway
linux·运维·服务器·网络
光锥智能3 分钟前
引领新一代人机交互,科大讯飞全新发布星火语音基座大模型 SparkAudio 1.0
人工智能·人机交互·语音识别
神奇的代码在哪里3 分钟前
MOSS-Image-SFT 开源解读:JSON 布局控制文字与排版
人工智能·开源·文生图·diffusers·moss-image
秋饼4 分钟前
Spring AI + MCP 工具调用可靠性实战:参数校验、权限防线与人工确认生产级落地
java·ai·技术分享·后端开发
猪腰子正7 分钟前
深度学习环境安装教程-适用Windows系统
人工智能·windows·深度学习
步行cgn7 分钟前
RESTful 是什么?怎么记住它?
java
随性而行3608 分钟前
企业微信 Webhook 接收不到消息?网络连通性、URL 签名验签与 API 返回错误码排查指南
java·网络·人工智能·后端·微信·企业微信
Marst Code8 分钟前
(Unix)一切皆文件
服务器·unix
薛之谦_10 分钟前
JD Hotkey 项目实战:京东开源的热点探测与秒杀系统
java·安全
yuzhiboyouye10 分钟前
@EnumValue和@JsonValue
java