처리 머신
{#if totalFailed > 0} {/if} {freshLabel}{#if stale} · 갱신 지연{/if}
지배 백로그 요약 대기 {eta.pending.toLocaleString()} · 순소화 {formatRate(eta.done_rate_1h)}/h · 유입 {formatRate(eta.inflow_rate_1h)}/h 정직 ETA {honestEtaLabel}
{#each lanes as lane (lane.key)}
{lane.meta.label} {lane.meta.model} {formatRate(lane.card?.done_1h ?? 0)}/h {#each bgForMachine(lane.key) as j (j.id)}생성 중: {j.label ?? j.kind}{#if j.total} {j.processed}/{j.total}{/if}{/each} {#if lane.key === 'macbook' && (lane.card?.deferred_pending ?? 0) > 0} 보류 {lane.card?.deferred_pending} {/if} {#if lane.card?.state === 'deferred'} 잠듦 — 요약은 맥미니로 복귀 {/if}
{#each lane.nodes as n (n.def.key)} {@const idle = n.pending + n.processing + n.doneToday + n.failed === 0} {/each} {#if lane.key === 'macbook' && offloadActive} {/if}
{/each}
{#if burn}
요약 백로그 24시간 유입(회색) vs 소화(녹색) {#if offloadActive}맥북 합류 {burn.markHour} — 소화 급증{/if}
{#if offloadActive} {/if}
{#each mainNodes.filter((n) => n.pending > 0 && n.def.key !== 'summarize') as n (n.def.key)} {n.def.label} 대기 {n.pending.toLocaleString()}{#if netEtaLabel(n)} · {netEtaLabel(n)}{/if} {/each}
{/if}

{#each auxActive as n, i (n.def.key)} {i > 0 ? ' · ' : '보조: '}{n.def.label}({n.def.engine}) 대기 {n.pending.toLocaleString()} · {formatRate(n.done1h)}/h{n.failed > 0 ? ` · 실패 ${n.failed}` : ''} {/each} {#if auxIdle.length > 0} {auxActive.length > 0 ? ' — ' : ''}한가: {auxIdle.map((n) => n.def.label).join(' · ')} {/if} — 뉴스 등 일부 소스는 분류/추출을 건너뜀 (흐름 그림은 대표 경로)

{#if selectedNode}
{selectedNode.def.label} — {selectedNode.def.engine} {selectedNode.def.sub} · {MACHINE_META[selectedNode.def.machine].label}
대기
{selectedNode.pending.toLocaleString()}
처리율 (1h)
{formatRate(selectedNode.done1h)}/h
오늘 완료
{selectedNode.doneToday.toLocaleString()}
소진 예상
{#if selectedNode.inflowDominant}유입 우세{:else if selectedNode.etaMinutes != null}{etaShort(selectedNode.etaMinutes)}{:else if selectedNode.pending === 0}한가{:else}—{/if}
{#if selectedNode.perStage.length > 1} {#each selectedNode.perStage as row (row.stage)}
{flowStageLabel(row.stage)} 대기 {row.pending.toLocaleString()} · {formatRate(row.done_1h)}/h · 오늘 {row.done_today.toLocaleString()} {#if row.failed > 0}· 실패 {row.failed}{/if}
{/each} {/if}
{#if selectedNode.oldestAgeSec != null && selectedNode.oldestAgeSec > 600} 가장 오래 기다린 항목 {formatAgeSec(selectedNode.oldestAgeSec)} {/if} {#each nodeCurrent(selectedNode.def) as c, i (c.document_id + c.stage)} {i === 0 && !(selectedNode.oldestAgeSec != null && selectedNode.oldestAgeSec > 600) ? '' : ' · '}지금: {c.title} ({flowStageLabel(c.stage)}) {/each} {#if selectedNode.failed > 0} · {/if}
{/if} {#if bgJobs.length > 0}
백그라운드 작업
{#each bgJobs as j (j.id)}
{j.kind} {j.label ?? '작업'} {#if j.total}{j.processed.toLocaleString()}/{j.total.toLocaleString()}{:else}{j.processed.toLocaleString()}건{/if} · {fmtElapsed(j.elapsed_sec)}
{#if j.stale}
heartbeat 끊김 — 프로세스 중단 추정 (재개 필요할 수 있음)
{:else if j.state === 'failed'}
실패{#if j.error} · {j.error}{/if}
{/if}
{/each}
{/if} {#if failOpen}
실패 처리 영구 실패 {failItems.length}건 — 자동 재시도 3회 소진, 수동 조치 대기
{#if failLoading}

불러오는 중…

{:else if failItems.length === 0}

영구 실패 항목 없음

{:else} {#each failGroups as g (g.key)}
{flowStageLabel(g.stage)} {g.items.length}건 {g.pattern}{g.items[0]?.error_message && g.items[0].error_message.length > 36 ? '…' : ''}
{#each expanded[g.key] ? g.items : g.items.slice(0, 4) as it (it.id)}
{it.title} 시도 {it.attempts}/{it.max_attempts} {it.error_message ?? ''}
{/each} {#if g.items.length > 4 && !expanded[g.key]} {/if} {#if g.items.length > 1}
{/if}
{/each}

재시도 = 시도 횟수 리셋 후 큐 재진입 (자동 재시도 3회 새로 부여) · 건너뛰기 = 이 단계 완료 처리(후속 단계 연쇄 없음, 감사 마킹) · 같은 오류가 반복되는 항목(빈 텍스트 등)은 건너뛰기 권장

{/if}
{/if}