Показывает: главный flow генерации протокола — 4 фазы от agendaId до protocolId. Как данные проходят по модулям и внешним сервисам.
Входная точка: ProtocolGenerator.generate(agendaId) — вызывается из ProtocolGenerationTaskService.runGeneration (async) или ProtocolLegacyController (sync v1 bridge).
Актуальные ADR: ADR-0001, ADR-0008, ADR-0009, ADR-0011.
sequenceDiagram
autonumber
participant Gen as ProtocolGenerator<br/>(protocol.service)
participant AP as AgendaProvider<br/>(integration.agenda)
participant AS as agenda-service<br/>⟶ external
participant EP as ExtractProvider<br/>(extract.adapter)
participant ES as ExtractSynthesizer<br/>(extract.adapter)
participant XDB as extract<br/>(DB)
participant PS as PrkkExtractStripper<br/>(document.prkk)
participant ECM as ECM<br/>⟶ external
participant Asm as ProtocolAssembler<br/>(protocol.service)
participant SM as SelectiveMerger<br/>(document.merge)
participant RG as ReportGenerator<br/>(document.generation)
participant Crud as ProtocolCrudService<br/>(protocol.service)
participant PDB as protocol<br/>(DB)
participant EV as ProtocolEventsProvider<br/>(integration.common)
participant CS as common-service<br/>⟶ external
%% ── Phase 1 ──
rect rgb(245, 255, 245)
Note over Gen,AS: PHASE 1 — FETCH_VALIDATE
Gen->>AP: loadSnapshot(agendaId)
Note over AP: internally StructuredTaskScope<br/>3 parallel Feign calls
par
AP->>AS: GET /api/v2/agenda/{id}
and
AP->>AS: GET /api/v2/agenda/{id}/questions
and
AP->>AS: POST /api/v1/decision/votes (batch by questionIds)
end
AS-->>AP: agendaDto, questionsDto[], votesByQuestion
Note over AP: AgendaMapper.toDomain(...)<br/>RLS check: if any accessForbidden → Forbidden
AP-->>Gen: AgendaSnapshot (agenda, questions, votes)
Note over Gen: validate: not empty<br/>split PRKK vs regular questions
Note over Gen: ProtocolGenerationContext.AfterPhase1
end
%% ── Phase 2 ──
rect rgb(255, 250, 235)
Note over Gen,XDB: PHASE 2 — SIMPLE_EXTRACTS<br/>(детально — см. phase2-extract-synthesis.md)
Gen->>EP: findByAgenda(agendaId)
EP->>XDB: SELECT WHERE type=SIMPLE AND questionId IN (...)
XDB-->>EP: existing SIMPLE extracts
EP-->>Gen: existing extracts
Note over Gen: diff → missing questionIds
Gen->>ES: ensureForQuestions(agendaId, missing)
Note over ES: StructuredTaskScope<br/>parallel gen per missing question
ES->>XDB: saveAll(newExtracts)
Note over ES: on DuplicateException<br/>→ findExisting (cache-hit)
ES-->>Gen: newly-created SIMPLE extracts
Note over Gen: PRKK placeholders (in-memory только)<br/>Extract.type=SHORT, fileId=prkkExtractFileId
Note over Gen: AfterPhase2
end
%% ── Phase 3 ──
rect rgb(255, 245, 245)
Note over Gen,ECM: PHASE 3 — SHORT_EXTRACTS + PRKK strip<br/>(детально — см. phase3-short-prkk-branching.md)
par Regular branch
Gen->>ES: synthesizeShortExtracts(regularSimples, ctx)
Note over ES: StructuredTaskScope<br/>parallel SIMPLE→SHORT через template
ES->>XDB: saveAll(shortExtracts) + duplicate handling
ES-->>Gen: List<Extract> SHORT
and PRKK branch
Gen->>PS: stripAll(prkkSimples, questionsMap, participantsLine)
loop для каждой PRKK-выписки
PS->>ECM: GET file bytes by fileId
ECM-->>PS: docx bytes
Note over PS: strip header/footer<br/>inject participants line
end
PS-->>Gen: Map<UUID, byte[]> prkkStrippedBytes
end
Note over Gen: AfterPhase3
end
%% ── Phase 4 ──
rect rgb(245, 245, 255)
Note over Gen,ECM: PHASE 4 — BUILD_PROTOCOL
Gen->>Asm: assemble(afterPhase3)
Note over Asm: sort shortExtracts by questionNumber
Note over Asm: strategy = resolveStrategy(agenda.type())<br/>→ Bank|Group|Smb|GoSmb
Asm->>SM: merge(sortedShort, prkkStrippedBytes)
Note over SM: per-extract normalization:<br/>regular → нормализуем шрифты<br/>PRKK bytes → as-is
SM-->>Asm: MergedDocument (path)
Note over Asm: strategy.buildReportData(agenda, questions, extracts)<br/>→ ReportData (Header, Participants, QuestionSection[], documentPath)
Asm->>RG: stampTemplate(strategy.templatePath, reportData)
RG-->>Asm: docx bytes (final protocol)
Note over Asm: fileName = ProtocolNameFormatter.format(agenda)<br/>«ПРОТОКОЛ № 042 от 18.04.2026 (ОЧНЫЙ).docx»
Asm->>ECM: saveProtocolFile(bytes, fileName)
ECM-->>Asm: externalStorageKey
Asm-->>Gen: ProtocolFile
Gen->>Crud: create(new ProtocolCreateRequest(agendaId, CREATED))
Crud->>PDB: INSERT protocol (status=CREATED, author=currentUser)
PDB-->>Crud: protocolId
Crud-->>Gen: ProtocolResponse
Gen->>EV: notifyGenerationCompleted(protocolId, agendaId)
EV->>CS: POST /api/v1/common/events/send...<br/>ObesrabuoProtocolGenerated
CS-->>EV: accepted
end
Gen-->>Gen: return protocolId
- Fail-fast (ADR-0009): любая ошибка в любой фазе → Generator throws, TaskService переводит task в FAILED, никаких partial-протоколов.
- ACL fence (ADR-0011):
AgendaMapper проверяет accessForbidden при маппинге — domain Agenda гарантированно без forbidden-вопросов.
- DIP (ADR-0001): Generator зависит от портов в
protocol/port/. Реализации (AgendaFeignAdapter, ExtractProviderAdapter и т.д.) — в других модулях.
- Parallelism инкапсулирован в адаптерах. Generator — тонкий оркестратор.
- Protocol entity создаётся последней (шаг 18-19) — после успешной сборки файла. При провале раньше — entity не появляется.
- Streaming events (
ProtocolStreamingProvider → common) — для BI/CRM replication. Эмитятся после events, ортогонально основному flow.
- Feature flag check (
FeatureFlagProvider.isEnabled("PRKK_EXTRACT_SKIP_FORMATTING")) — вызывается в ProtocolGenerator перед Phase 3 ветвлением. По умолчанию true.
- SecurityContext / MDC propagation — автоматически через
ContextPropagatingTaskDecorator.
- Метрики (micrometer timers) — по каждой фазе, не в sequence.