этап 2: переиспользуемый модуль upload (sink) вместо рукописного транспорта

- копирую модуль upload/ из drhider (слои 1-2)
- blueprint: параметр sink (drhider-сессия по умолчанию, сверка — DB+парсинг)
- upload_bp: contracts_upload_sink = _store_and_parse
- routes: регистрирую create_upload_refs_blueprint(cfg, sink=...)
- app.py: корень репо в sys.path (для import upload)
- History: план переиспользования + ревью Соннета
This commit is contained in:
“Naeel”
2026-08-26 08:07:43 +03:00
parent 55bd7a027d
commit db58a433fb
46 changed files with 1986 additions and 27 deletions
@@ -0,0 +1,74 @@
// renderProcTable — 3-секционная таблица во время обработки
// (готово / текущий / ожидают / пропущены сверх лимита).
import { fmtSec } from './fmt_sec.js';
import { estForFile, DEFAULT_EST_MB_SEC } from './est_for_file.js';
import { procRow } from './proc_row.js';
export function renderProcTable(state, els, cfg) {
const procState = state.proc.procState;
const groups = { done: [], current: [], pending: [], over: [] };
for (let i = 0; i < state.files.length; i++) {
if (state.overNames.has(state.files[i].name)) { groups.over.push(i); continue; }
const st = procState[i] ? procState[i].st : 'pending';
if (st === 'done' || st === 'skipped') groups.done.push(i);
else if (st === 'current') groups.current.push(i);
else groups.pending.push(i);
}
// Оценка скорости из текущего файла (сек/символ) — для «ожидающих»
let rate = null;
for (const i of groups.current) {
const p = procState[i];
const cur = (performance.now() - p.t0) / 1000;
const total = cur + (p.eta != null ? p.eta : 0);
if (p.chars > 0 && total > 0) rate = total / p.chars;
}
const rows = [];
if (groups.done.length) {
rows.push('<tr class="grp-row"><td colspan="4">✓ Обработанные (' + groups.done.length + ')</td></tr>');
for (const i of groups.done) {
const p = procState[i];
const txt = p.st === 'skipped'
? '<span style="color:#c0392b;" title="Не удалось прочитать файл: пустой, повреждённый или скан без текста">не извлечён</span>'
: '<span style="color:#22c55e;">✓ ' + (p.elapsed ? p.elapsed.toFixed(1) : '0.0') + 'с</span>';
rows.push(procRow(state, i, txt));
}
}
if (groups.over.length) {
rows.push('<tr class="grp-row"><td colspan="4">⛔ Пропущены (сверх лимита) (' + groups.over.length + ')</td></tr>');
for (const i of groups.over) {
rows.push(procRow(state, i, '<span style="color:#c0392b;">пропущен (лимит)</span>'));
}
}
if (groups.current.length) {
rows.push('<tr class="grp-row"><td colspan="4">▶ Текущий файл</td></tr>');
for (const i of groups.current) {
const p = procState[i];
const cur = ((performance.now() - p.t0) / 1000).toFixed(1);
const eta = (p.eta != null) ? ' / ~' + fmtSec(p.eta) : '';
rows.push(procRow(state, i, '<span style="color:#2563eb;">⏳ ' + cur + 'с' + eta + '</span>'));
}
}
if (groups.pending.length) {
rows.push('<tr class="grp-row"><td colspan="4">○ Ожидают обработки (' + groups.pending.length + ')</td></tr>');
for (const i of groups.pending) {
const p = procState[i] || {};
// Оценка: LLM (chars x rate) если известна; иначе грубая по размеру/скорости извлечения
if (rate && !p.est && p.chars > 0) p.est = p.chars * rate;
if (!p.est) {
const szMB = (state.files[i] ? state.files[i].size : 0) / 1048576;
const k = (state.proc && state.proc.procExtractRate) || cfg.estMbSec || DEFAULT_EST_MB_SEC; // сек/МБ
p.est = Math.max(1, Math.round(szMB * k));
}
let txt;
if (p.st === 'analyzed') txt = '<span style="color:#7d3c98;">анализ ✓</span>';
else if (p.st === 'current') txt = '<span style="color:#2563eb;">⏳</span>';
else if (p.est != null) txt = '<span style="color:#999;">~' + fmtSec(p.est) + '</span>';
else txt = '<span style="color:#999;">—</span>';
rows.push(procRow(state, i, txt));
}
}
els.tableBody.innerHTML = rows.join('');
const cntDone = groups.done.length;
els.countEl.textContent = 'Готово ' + cntDone + ' / ' + state.files.length + ' файлов';
}