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

同一篇通稿被十个网站转载,页面若只数链接,会显示"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
}

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

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

相关推荐
连涨- AI脑波英语1 小时前
教育集团新增脑机单词速记产品线,怎样把教练工时算进试点成本?
人工智能·脑机单词速记
打工仔折腾 AI1 小时前
从BPE到SentencePiece:Transformer分词原理与Python实战对比
android·人工智能·python·深度学习·langchain·transformer·ai agent 实战
sunneo1 小时前
每周GitCode开源项目推荐
人工智能
骑着蜗牛撵大象3271 小时前
Qt 事件机制详解:从 QEvent 派生类到事件过滤器的全景指南
前端·python
AI砖家1 小时前
实测 AI提示词网站 AI妙词:一个「看过效果再用」的中文提示词库,这次更新有点东西
人工智能·ai提示词·ai特效提示词·国内好用的ai提示词
旺仔Sec1 小时前
2026年江西省职业院校技能大赛(中职组)应用软件系统开发赛项竞赛样题
java·应用软件系统开发
量子-Alex1 小时前
【大模型后训练SFT】LIMA: Less Is More for Alignment
人工智能
小虎牙^O^1 小时前
模糊数学综合评价:从模糊数学到建模评价类模型
人工智能·算法
昇腾CANN1 小时前
CANN 端云协同:节约开发、运行成本,支撑Mate90发布会鸿蒙独占应用创新
人工智能·昇腾·cann·cann开源