Expose persistent collection gaps and public Congress links
Retain bill and attachment failure reasons and derive retry counts from saved cursors. Show gap details and retry exhaustion in the collection console while preserving historical records and retry limits. Convert Congress API bill links to public pages and cover diagnostics, mixed dated versions, persistent counts and legacy records with regression tests.
This commit is contained in:
1 parent
dcad5b76a8
commit
d5d350b7ec
8 files changed
+182
-14
No files matched your search
@@ -19,7 +19,13 @@ export interface SourceInfo {
|
||||
paused_until: string | null
|
||||
collection_stats?: Record<string, number>
|
||||
configuration_error?: string | null
|
||||
history_cursor?: { history?: { scanned?: boolean }, realtime?: { entry: number } }
|
||||
history_cursor?: {
|
||||
history?: { scanned?: boolean }
|
||||
realtime?: { entry: number }
|
||||
document_retries?: Record<string, CollectionRetry>
|
||||
attachment_retries?: Record<string, CollectionRetry>
|
||||
bill_retries?: Record<string, CollectionRetry>
|
||||
}
|
||||
supports_history: boolean
|
||||
history_target: string | null
|
||||
history_position: string | null
|
||||
@@ -30,6 +36,13 @@ export interface SourceInfo {
|
||||
total_items: number
|
||||
}
|
||||
|
||||
export interface CollectionRetry {
|
||||
document: { url: string, canonical?: string }
|
||||
attempts: number
|
||||
next_at: number
|
||||
error?: string
|
||||
}
|
||||
|
||||
export interface SourcesInfo {
|
||||
manager_alive: boolean
|
||||
db_size_bytes: number
|
||||
|
||||
@@ -7,7 +7,7 @@ import { useSessionScope } from "@@/composables/useSessionScope"
|
||||
import { formatTime } from "@@/utils/text"
|
||||
import { CircleCheck, RefreshRight } from "@element-plus/icons-vue"
|
||||
import dayjs from "dayjs"
|
||||
import { sourceStatus } from "../sourceStatus"
|
||||
import { collectionGaps, sourceStatus } from "../sourceStatus"
|
||||
|
||||
/** logs 只用来发现「本轮完成」以刷新最新入库;alert 为连续失败达到告警阈值 */
|
||||
const props = defineProps<{
|
||||
@@ -89,6 +89,7 @@ async function cancelHistory() {
|
||||
}
|
||||
|
||||
const statusTag = computed(() => sourceStatus(props.source, props.restarting))
|
||||
const gaps = computed(() => collectionGaps(props.source))
|
||||
|
||||
onMounted(loadLatest)
|
||||
</script>
|
||||
@@ -144,7 +145,7 @@ onMounted(loadLatest)
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="source.collection_stats?.unresolved_bills" class="text-xs text-amber-600">
|
||||
待核验法案 {{ source.collection_stats.unresolved_bills }};官方资料尚不充分
|
||||
待核验法案 {{ source.collection_stats.unresolved_bills }};待重试 {{ source.collection_stats.bill_retry_pending }};重试已耗尽 {{ source.collection_stats.bill_retry_exhausted }}
|
||||
</div>
|
||||
<div v-if="source.collection_stats?.attachment_gaps" class="text-xs text-amber-600">
|
||||
附件待核验文档 {{ source.collection_stats.attachment_gaps }};待重试 {{ source.collection_stats.attachment_retry_pending }};重试已耗尽 {{ source.collection_stats.attachment_retry_exhausted }},请核对原文
|
||||
@@ -152,6 +153,18 @@ onMounted(loadLatest)
|
||||
<div v-if="source.collection_stats?.document_gaps" class="text-xs text-amber-600">
|
||||
正文待核验 {{ source.collection_stats.document_gaps }};待重试 {{ source.collection_stats.document_retry_pending }};重试已耗尽 {{ source.collection_stats.document_retry_exhausted }},请核对来源日志
|
||||
</div>
|
||||
<details v-if="gaps.length" class="text-xs text-amber-700">
|
||||
<summary class="cursor-pointer">
|
||||
缺口详情({{ gaps.length }})
|
||||
</summary>
|
||||
<div v-for="gap in gaps" :key="gap.key" class="mt-2 border border-amber-200 rounded border-solid p-2">
|
||||
<div>{{ gap.label }};已尝试 {{ gap.attempts }} 次;{{ gap.exhausted ? "重试已耗尽" : `下次重试不早于 ${formatTime(gap.nextAt)}` }}</div>
|
||||
<div class="my-1 whitespace-pre-wrap break-words">
|
||||
{{ gap.error }}
|
||||
</div>
|
||||
<a v-if="gap.url.startsWith('https://')" :href="gap.url" target="_blank" rel="noopener noreferrer" class="break-all underline">查看原文:{{ gap.url }}</a>
|
||||
</div>
|
||||
</details>
|
||||
<div v-if="source.collection_stats?.discovered" class="text-xs text-slate-500">
|
||||
本轮发现 {{ source.collection_stats.discovered }};筛除 {{ source.collection_stats.excluded ?? 0 }};不完整包 {{ source.collection_stats.partial ?? 0 }}
|
||||
</div>
|
||||
|
||||
@@ -15,7 +15,41 @@ export function sourceStatus(source: SourceInfo, restarting = false, now = Date.
|
||||
return { type: "primary", text: "采集中" } as const
|
||||
if (source.consecutive_failures)
|
||||
return { type: "danger", text: "失败待重试" } as const
|
||||
if (source.collection_stats?.document_gaps)
|
||||
return { type: "warning", text: "存在文档缺口" } as const
|
||||
if (source.collection_stats?.document_gaps || source.collection_stats?.attachment_gaps || source.collection_stats?.unresolved_bills)
|
||||
return { type: "warning", text: "存在采集缺口" } as const
|
||||
return { type: "info", text: "等待调度" } as const
|
||||
}
|
||||
|
||||
function originalUrl(url: string) {
|
||||
const bill = /^https:\/\/api\.congress\.gov\/v3\/bill\/(\d+)\/([a-z]+)\/(\d+)\/?(?:\?[^#]*)?(?:#.*)?$/i.exec(url)
|
||||
const billTypes: Record<string, string> = {
|
||||
hr: "house-bill",
|
||||
s: "senate-bill",
|
||||
hjres: "house-joint-resolution",
|
||||
sjres: "senate-joint-resolution",
|
||||
hconres: "house-concurrent-resolution",
|
||||
sconres: "senate-concurrent-resolution",
|
||||
hres: "house-resolution",
|
||||
sres: "senate-resolution"
|
||||
}
|
||||
if (bill && billTypes[bill[2].toLowerCase()])
|
||||
return `https://www.congress.gov/bill/${bill[1]}th-congress/${billTypes[bill[2].toLowerCase()]}/${bill[3]}`
|
||||
return url
|
||||
}
|
||||
|
||||
export function collectionGaps(source: Pick<SourceInfo, "history_cursor">) {
|
||||
const groups = [
|
||||
["document_retries", "正文"],
|
||||
["attachment_retries", "附件"],
|
||||
["bill_retries", "法案"]
|
||||
] as const
|
||||
return groups.flatMap(([key, label]) => Object.entries(source.history_cursor?.[key] ?? {}).map(([url, retry]) => ({
|
||||
key: `${key}:${url}`,
|
||||
label,
|
||||
url: originalUrl(retry.document.canonical ?? retry.document.url),
|
||||
error: retry.error || "历史记录未保存具体原因,请核对原文",
|
||||
attempts: retry.attempts,
|
||||
exhausted: retry.attempts >= 3,
|
||||
nextAt: new Date(retry.next_at * 1000).toISOString()
|
||||
})))
|
||||
}
|
||||
Reference in new issue
Block a user