Files
contracts/History/llm-analysis/decoupling-final-plan.md
T

4.2 KiB

Финальный план рефакторинга — store + render

Согласовано: DeepSeek + Opus. Все открытые вопросы закрыты.

Архитектурное решение

Патерн: единый state объект + одна render(state) + actions мутируют state.

action → мутация state → render(state)
  • Асинхронные операции (upload, classify, compare-SSE) — actions, пишут в state
  • Синхронные трансформации (group, applyParseResult) — чистые функции
  • DOM — проекция state, никто не читает из DOM
  • console.log(state) показывает всё

Структура state

state = {
  batchId, contractId,
  files: [{ id, name, size, status: { kind, pct, text, count }, parse, classify, supp_id, detailOpen }],
  groups: [{
    contractNumber, counterparty, documents, unresolved,
    compare: { status, sections: { [suppId]: { header, body, ops } }, totalTime, collapsed }
  }],
  compareAll: { status, sections, totalTime },
  ui: { steps: { upload, classify, groups, compare } },
  _activeCompare: { es, timer }
}

Фазы

Фаза 0 — Каркас

  • Ввести state, render(state) (пока вызывает существующие рендеры)
  • Соглашение: мутировал → render()

Фаза 1 — Файлы

  • fileQueuestate.files
  • renderTablerenderFiles(state)
  • fileInput change разбит:
    • onFilesSelected(files) — оркестратор (10-15 строк)
    • reconcileSelection(newFiles) — чистая мутация массива
    • addZipFiles(file) — async-action
    • addRegularFile(file) — async-action
    • applyParseResult(entry, parsed) — чистая функция (убирает дубль zip/обычной ветки)
    • finalizeUpload() — refreshSupps + stepDone + syncDB + кнопки
  • status → структура { kind, pct, text, count }, HTML через statusToHTML(status)
  • Убрать renderTable() из обработчиков — только render() в конце action

Фаза 2 — Группы

  • loadGroupsloadGroupsAction() (fetch → state.groups) + renderGroups(state)
  • buildGroupCardrenderGroupCard(group) — чистая HTML-функция
  • markGroupDone удалить — готовность = state.groups[gi].compare.status === 'done'
  • Убрать window._groupsData
  • renderGroups пересобирает ВСЕ карточки целиком

Фаза 3 — Сравнение/SSE

  • Унификация через общие функции:
    • applyCompareEvent(target, event) — чистая мутация SSE-события в target
    • startCompareSSE(url, target, { onDone }) — жизненный цикл SSE
    • renderCompareSections(sections) — общий рендер таблиц ops
  • runCompareForGroup → тонкая обёртка над startCompareSSE
  • llmBtn → тонкая обёртка над startCompareSSE
  • ~160 строк дублирования → ~60 строк общих + 2 вызова

Фаза 4 — Чистка

  • contractId, batchIdstate
  • Степпер → renderStepper(state)
  • Промпты, чат, showText — вне scope (отдельная итерация)

Верификация

  1. После каждой правки: node -c на app.js + app_utils.js
  2. После Фазы 2: node-тесты на чистые функции (applyParseResult, statusToHTML, renderGroupCard)
  3. Ручной прогон upload→classify→group→compare после git push
  4. Регресс: «Готово» не перезапускается, параллельные сравнения заблокированы, группы не затираются

Релевантные файлы

  • contractor/deploy/app.js — все фазы (~1040 строк)
  • contractor/deploy/app_utils.js — removeFile/moveUp/moveDown (Фаза 1)
  • contractor/index.cfm — DOM-скелет, версия v1.0.177, ?v= для cache bust