fix(recognize): party blocks belong before signatures, not at top
- Prompt: explicit rule that party blocks always go at the end, before signatures; preamble in the beginning is a paragraph, not party - Postprocess: deterministic reorder pulls party blocks to the position right before the last signatures block (or to the end if no signatures) Existing imported templates can be re-imported, or party blocks moved manually in the editor (↑↓ buttons). New imports come out in correct order. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -57,15 +57,20 @@ const SYSTEM_PROMPT = `Ты — парсер юридических докуме
|
||||
}
|
||||
|
||||
Важные правила:
|
||||
1. Если в документе встречается реквизитный блок одной из сторон (ИНН/КПП/адрес/банк) — выдай ОДИН блок "party" с правильным role, не добавляй параграф с текстовым перечислением реквизитов.
|
||||
2. Услуги/работы в табличной форме — НЕ переписывай построчно, ставь блок "services_table" (lines пустой массив, строки пользователь добавит вручную).
|
||||
3. Если есть итоговые суммы (Итого, в т.ч. НДС, сумма прописью) — ставь блок "totals" со включенными нужными опциями.
|
||||
4. Подписи в конце — блок "signatures" с указанием каких сторон видно.
|
||||
5. Нумерованные пункты «1. Предмет договора», «2. Цена и порядок расчётов» и т.п. — каждый раздел как блок "terms" (заголовок раздела можно положить отдельным "heading" level 2).
|
||||
6. Не выдумывай блоки, которых нет в документе.
|
||||
7. text внутри блоков — обычная строка (не TipTap JSON), может содержать \\n для новых параграфов внутри блока.
|
||||
8. Заполняй "title" коротким названием документа.
|
||||
9. Если не уверен в типе документа — ставь "contract".`;
|
||||
1. Блоки "party" (реквизиты сторон) ВСЕГДА ставь в конце документа — НЕПОСРЕДСТВЕННО ПЕРЕД блоком "signatures". Это адреса/банковские реквизиты, они идут в самом низу.
|
||||
2. Преамбула в начале документа («ООО Х в лице ..., далее «Исполнитель», и ИП Y, заключили договор...») — это блок "paragraph", а не "party". Конкретные реквизиты не дублируй, имя стороны заменяй плейсхолдерами.
|
||||
3. Если в документе встречается развёрнутый блок реквизитов стороны (ИНН/КПП/адрес/банк/р/счёт перечислены явно) — выдай ОДИН блок "party" с правильным role, БЕЗ дублирования текстом.
|
||||
4. Услуги/работы в табличной форме — НЕ переписывай построчно, ставь блок "services_table" (lines пустой массив, строки пользователь добавит вручную).
|
||||
5. Если есть итоговые суммы (Итого, в т.ч. НДС, сумма прописью) — ставь блок "totals" со включенными нужными опциями.
|
||||
6. Подписи в конце — блок "signatures" с указанием каких сторон видно.
|
||||
7. Нумерованные пункты «1. Предмет договора», «2. Цена и порядок расчётов» и т.п. — каждый раздел как блок "terms" (заголовок раздела можно положить отдельным "heading" level 2).
|
||||
8. Не выдумывай блоки, которых нет в документе.
|
||||
9. text внутри блоков — обычная строка (не TipTap JSON), может содержать \\n для новых параграфов внутри блока.
|
||||
10. Заполняй "title" коротким названием документа.
|
||||
11. Если не уверен в типе документа — ставь "contract".
|
||||
|
||||
Типичный порядок блоков:
|
||||
heading → (опц.) paragraph-преамбула → terms (1. Предмет) → terms (2. Цена) → services_table → totals → terms (остальные пункты) → heading "Адреса и реквизиты сторон" → party (executor) → party (customer) → signatures.`;
|
||||
|
||||
function uid(): string {
|
||||
return Math.random().toString(36).slice(2, 11);
|
||||
@@ -82,10 +87,36 @@ function plainToRich(text: string): unknown {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Переставляет блоки в правильный порядок: party-блоки уходят в конец, перед signatures.
|
||||
* Гарантия даже если LLM проигнорировала промпт.
|
||||
*/
|
||||
function reorderForFinalLayout<T extends { type: string }>(blocks: T[]): T[] {
|
||||
const parties: T[] = [];
|
||||
const rest: T[] = [];
|
||||
for (const b of blocks) {
|
||||
if (b.type === 'party') parties.push(b);
|
||||
else rest.push(b);
|
||||
}
|
||||
if (parties.length === 0) return blocks;
|
||||
|
||||
// Найти ПОСЛЕДНИЙ signatures — party вставляется перед ним
|
||||
let sigIdx = -1;
|
||||
for (let i = rest.length - 1; i >= 0; i--) {
|
||||
if (rest[i]!.type === 'signatures') {
|
||||
sigIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (sigIdx === -1) return [...rest, ...parties];
|
||||
return [...rest.slice(0, sigIdx), ...parties, ...rest.slice(sigIdx)];
|
||||
}
|
||||
|
||||
function llmToDocBody(result: LlmResult): { docBody: unknown; docType: DocType; title: string } {
|
||||
const docType: DocType = (result.docType as DocType) ?? 'contract';
|
||||
const title = result.title ?? 'Документ';
|
||||
const blocks = (result.blocks ?? []).map((b) => {
|
||||
const orderedRaw = reorderForFinalLayout(result.blocks ?? []);
|
||||
const blocks = orderedRaw.map((b) => {
|
||||
const id = uid();
|
||||
switch (b.type) {
|
||||
case 'heading':
|
||||
|
||||
Reference in New Issue
Block a user