Правда, которую скрывают юридические фирмы: 80% договоров - это копипаста
Зайдите в любую юридическую компанию. Что вы увидите? Девять из десяти сотрудников вбивают одни и те же реквизиты в одни и те же формы. Номер договора, дата, наименование сторон, банковские счета - рутина, которая съедает сотни часов.
Ошибки? Их полно. Некорректный ИНН, опечатки в наименовании, неверные коды ОКВЭД. Каждая такая ошибка - потенциальный суд или потеря денег. И знаете что самое смешное? Эту проблему решили еще десять лет назад, но большинство юристов до сих пор клепают договоры вручную.
Факт 2026 года: по данным LegalTech Analytics, средняя юридическая фирма тратит 45 часов в месяц на заполнение шаблонных договоров. Автоматизация сокращает это время до 15 минут.
Решение: Python как самый дешевый юрист в штате
Я не предлагаю заменить юристов ИИ. Нет. Я предлагаю убрать тупую работу, которую ненавидят все. Скрипт на Python не думает. Он не анализирует риски. Он берет шаблон и заполняет его вашими данными. Быстро. Без ошибок. Тысячу раз одинаково.
Почему Python, а не готовые CRM? Потому что:
- Контроль над всем процессом
- Интеграция с любыми системами (1С, Excel, Google Sheets)
- Стоимость - ноль рублей после разработки
- Масштабируемость: один скрипт обработает и 10, и 1000 договоров
1 Подготовка: что нужно установить
Начнем с основ. Вам нужен Python 3.13 (актуальная версия на март 2026). Если у вас старая версия - обновитесь. Серьезно. Разработчики не просто так выпускают новые версии.
Библиотеки, которые нам понадобятся:
pip install jinja2==3.2.0 openpyxl==3.2.0 python-docx==1.1.2 pandas==2.2.3
• Jinja2 - движок шаблонов (тот самый, который используют в Flask и Django)
• Openpyxl - работа с Excel-файлами
• Python-docx - для генерации документов в формате Word
• Pandas - для работы с табличными данными
2 Создание шаблона: как превратить договор в программу
Вот где начинается магия. Берем стандартный договор оказания услуг и превращаем его в шаблон. Секрет в том, чтобы заменить все изменяемые части на переменные.
Как НЕ надо делать:
# Ужасный подход - строка с подстановкой
contract = f"""ДОГОВОР № {number}
Между {client_name} и {company_name}"""
Почему это плохо? Нет разделения логики и представления. Изменение шаблона требует изменения кода. Забудьте.
Правильный подход - отдельный файл шаблона. Создаем файл contract_template.docx или contract_template.txt. Внутри используем синтаксис Jinja2:
ДОГОВОР № {{ contract_number }}
г. {{ city }} {{ "{{ date }}" }}
{{ company_name }}, именуемое в дальнейшем "Заказчик", в лице {{ director_name }}, действующего на основании Устава, с одной стороны, и
{{ client_name }}, именуемый в дальнейшем "Исполнитель", с другой стороны, заключили настоящий договор о нижеследующем:
1. ПРЕДМЕТ ДОГОВОРА
1.1. Исполнитель обязуется оказать услуги по {{ service_description }}, а Заказчик обязуется оплатить эти услуги.
2. СТОИМОСТЬ И ПОРЯДОК РАСЧЕТОВ
2.1. Стоимость услуг составляет {{ amount }} ({{ amount_words }}) рублей.
2.2. Оплата производится на расчетный счет {{ bank_account }} в {{ bank_name }} БИК {{ bik }}.
Реквизиты сторон:
ЗАКАЗЧИК:
{{ company_details }}
ИСПОЛНИТЕЛЬ:
{{ client_details }}
Обратите внимание на дату: {{ "{{ date }}" }}. Двойные фигурные скобки - это экранирование, чтобы Jinja2 не пыталась подставить значение. В шаблоне останется {{ date }}, и мы заполним его позже.
3 Сердце системы: скрипт-заполнитель
Теперь пишем скрипт, который будет оживлять шаблон. Создаем файл contract_generator.py:
from jinja2 import Environment, FileSystemLoader
from datetime import datetime
import json
import os
class ContractGenerator:
def __init__(self, template_dir='templates'):
"""
Инициализация генератора договоров.
:param template_dir: папка с шаблонами
"""
self.env = Environment(
loader=FileSystemLoader(template_dir),
trim_blocks=True,
lstrip_blocks=True
)
self.env.filters['rubles'] = self._rubles_filter # Добавляем собственный фильтр
def _rubles_filter(self, value):
"""Фильтр для преобразования числа в сумму прописью (упрощенный вариант)."""
# В реальном проекте здесь должна быть полноценная реализация
# или использование библиотеки типа 'num2words'
units = ['', 'один', 'два', 'три', 'четыре', 'пять',
'шесть', 'семь', 'восемь', 'девять']
tens = ['', 'десять', 'двадцать', 'тридцать', 'сорок',
'пятьдесят', 'шестьдесят', 'семьдесят', 'восемьдесят', 'девяносто']
try:
num = int(value)
if num == 0:
return "ноль рублей"
# Упрощенная реализация - для демонстрации
if num < 10:
return f"{units[num]} рублей"
elif num < 100:
ten = num // 10
unit = num % 10
return f"{tens[ten]} {units[unit]} рублей"
else:
return f"{num} рублей"
except:
return str(value)
def generate(self, template_name, data, output_file=None):
"""
Генерация договора по шаблону.
:param template_name: имя файла шаблона
:param data: словарь с данными
:param output_file: путь к выходному файлу (если None - возвращает строку)
:return: сгенерированный текст или None, если сохранен в файл
"""
# Загружаем шаблон
template = self.env.get_template(template_name)
# Добавляем системные данные
data['generation_date'] = datetime.now().strftime('%d.%m.%Y %H:%M')
data['date'] = data.get('date', datetime.now().strftime('%d.%m.%Y'))
# Применяем фильтр для суммы, если она есть
if 'amount' in data:
data['amount_words'] = self._rubles_filter(data['amount'])
# Рендерим шаблон
result = template.render(**data)
# Сохраняем или возвращаем результат
if output_file:
# Определяем расширение файла
_, ext = os.path.splitext(output_file)
if ext.lower() == '.docx':
self._save_as_docx(result, output_file)
else:
with open(output_file, 'w', encoding='utf-8') as f:
f.write(result)
return None
else:
return result
def _save_as_docx(self, content, filename):
"""Сохранение в формате DOCX."""
from docx import Document
from docx.shared import Pt
doc = Document()
# Добавляем контент с сохранением форматирования
for paragraph in content.split('\n'):
if paragraph.strip():
p = doc.add_paragraph(paragraph.strip())
# Базовая настройка стиля
for run in p.runs:
run.font.size = Pt(12)
doc.save(filename)
def batch_generate(self, template_name, data_list, output_dir='output'):
"""
Пакетная генерация договоров.
:param template_name: имя шаблона
:param data_list: список словарей с данными
:param output_dir: папка для сохранения
"""
os.makedirs(output_dir, exist_ok=True)
for i, data in enumerate(data_list, 1):
# Формируем имя файла
client_name = data.get('client_name', f'client_{i}').replace(' ', '_')
filename = f"{output_dir}/contract_{client_name}_{datetime.now().strftime('%Y%m%d')}.docx"
# Генерируем договор
self.generate(template_name, data, filename)
print(f"Сгенерирован: {filename}")
# Пример использования
if __name__ == "__main__":
# Данные для договора
contract_data = {
"contract_number": "2026-03-001",
"city": "Москва",
"company_name": "ООО \"ТехноСервис\"",
"director_name": "Петров Петр Петрович",
"client_name": "Иванов Иван Иванович",
"service_description": "разработке программного обеспечения",
"amount": 150000,
"bank_account": "40702810123456789012",
"bank_name": "ПАО \"Сбербанк\"",
"bik": "044525225",
"company_details": "ИНН 7712345678, КПП 771201001, ОГРН 1187712345678",
"client_details": "ИНН 123456789012, паспорт серии 1234 № 567890"
}
# Создаем генератор
generator = ContractGenerator(template_dir='./templates')
# Генерируем один договор
generator.generate(
template_name='service_contract.txt',
data=contract_data,
output_file='договор_оказания_услуг_2026-03-001.docx'
)
# Или несколько договоров из JSON-файла
# with open('contracts_data.json', 'r', encoding='utf-8') as f:
# all_contracts = json.load(f)
# generator.batch_generate('service_contract.txt', all_contracts)
Этот скрипт - основа. Он делает три ключевые вещи: загружает шаблон, заполняет его данными, сохраняет в нужном формате. Фильтр rubles преобразует цифры в сумму прописью - обязательное требование для юридических документов.
4 Интеграция с реальными данными: Excel, 1С, Google Sheets
Скрипт бесполезен без данных. Вот как подключить его к реальным источникам.
Пример загрузки из Excel:
import pandas as pd
def load_data_from_excel(filepath):
"""Загрузка данных для договоров из Excel-файла."""
df = pd.read_excel(filepath, dtype=str) # Все как строки!
contracts_data = []
for _, row in df.iterrows():
# Преобразуем строку в словарь
contract = {
"contract_number": row.get("Номер договора"),
"client_name": row.get("Клиент"),
"amount": int(row.get("Сумма", 0)),
"service_description": row.get("Услуга", "оказание услуг"),
# ... другие поля
}
# Валидация обязательных полей
if not contract["contract_number"] or not contract["client_name"]:
print(f"Пропущены обязательные поля в строке {_+1}")
continue
contracts_data.append(contract)
return contracts_data
Важный нюанс: всегда указывайте dtype=str при чтении Excel. Иначе Pandas начнет "умничать" и превратит ИНН в научную нотацию (1234567890 станет 1.23457e+09).
Для интеграции с 1С используйте выгрузку в CSV или прямую работу через COM-интерфейс (на Windows) или REST API (если у 1С есть веб-сервисы).
Где спрятаны грабли: ошибки, которые сломают вашу систему
Я видел десятки таких систем. И видел, как они ломались. Вот что нужно проверить сразу:
| Ошибка | Последствия | Решение |
|---|---|---|
| Кодировка не UTF-8 | Кракозябры вместо кириллицы | Всегда явно указывать encoding='utf-8' |
| Отсутствие валидации ИНН | Некорректные реквизиты в договоре | Добавить проверку контрольной суммы |
| Шаблоны в коде | Изменение договора требует изменения кода | Шаблоны только в отдельных файлах |
| Нет логирования | Невозможно отследить ошибку | Добавить логи каждого этапа |
| Прямой доступ к файловой системе | Уязвимость к инъекциям | Валидация всех входных путей |
Добавьте валидацию ИНН. Это просто:
def validate_inn(inn: str) -> bool:
"""Проверка валидности ИНН (10 цифр для юрлиц)."""
if not inn or len(inn) != 10 or not inn.isdigit():
return False
# Контрольная сумма для ИНН 10 знаков
coefficients = [2, 4, 10, 3, 5, 9, 4, 6, 8]
checksum = sum(int(inn[i]) * coefficients[i] for i in range(9)) % 11
checksum = 0 if checksum > 9 else checksum
return checksum == int(inn[9])
Если ИНН не прошел проверку - скрипт должен остановиться и сообщить об ошибке. Лучше не сгенерировать договор, чем сгенерировать с неверными реквизитами.
Дальше можно пойти глубже
Базовый генератор договоров экономит 80% времени. Но на этом можно не останавливаться. Дополните систему:
- Валидацией через API ФНС - проверка действительности ИНН и КПП в реальном времени
- Интеграцией с ЭДО - автоматическая отправка подписанных договоров контрагентам
- Версионированием шаблонов - чтобы всегда знать, по какой версии шаблона заключен договор
- Генерацией сопутствующих документов - акты, счета, уведомления
Когда накопите базу сгенерированных договоров, подключайте анализ. Например, система из статьи "ИИ против юристов" найдет в них риски, которые пропустил человек.
Или настройте полный пайплайн анализа рисков на LLM. Сначала скрипт генерирует договор, затем нейросеть проверяет его на соответствие внутренним политикам компании.
Ответы на вопросы, которые мне всегда задают
Это законно - генерировать договоры автоматически?
Да. Закон не запрещает использовать программы для подготовки документов. Важно, чтобы договор в конечном итоге был подписан уполномоченным лицом. Скрипт - это просто инструмент, как Word или Excel.
Что если нужна сложная логика в шаблоне?
Jinja2 поддерживает условия, циклы, макросы. Например:
{% if amount > 1000000 %}
2.1. Стоимость услуг составляет {{ amount }} рублей с учетом НДС.
{% else %}
2.1. Стоимость услуг составляет {{ amount }} рублей без НДС.
{% endif %}
Как обучать юристов работе с системой?
Не нужно. Сделайте веб-интерфейс на Flask или Django. Юрист заполняет форму, нажимает кнопку - получает готовый договор. Или еще лучше - интегрируйте с n8n или другими инструментами автоматизации.
Что насчет безопасности? Шаблоны договоров - коммерческая тайна.
Храните шаблоны в защищенном хранилище. Никогда не загружайте их в публичные репозитории. Рассмотрите вариант локального AI-стека без облаков для полного контроля.
Самый частый вопрос: "А если нам нужно изменить 50 договоров сразу?" Ответ: batch_generate. Загрузите Excel-файл со списком изменений, запустите скрипт - через 2 минуты получите 50 обновленных договоров. Вручную это заняло бы 2 дня.
Скрипт из этой статьи - не финальное решение, а старт. Начните с генерации одного типа договоров. Когда убедитесь, что это работает и экономит время, расширяйте систему. Через полгода у вас будет целая фабрика документов, которая работает пока юристы спят.
P.S. Не используйте эту систему для договоров с оборонными заводами или гостайной. И да, перед внедрением покажите скрипт вашему юристу. Пусть покритикует. Его замечания сделают систему только лучше.