AI-генерация заявок на гранты
Грантовая заявка — специфический жанр: структурированный документ с жёсткими требованиями к объёму, разделам и формулировкам. Организации тратят 40–80 часов на одну заявку в крупный фонд. AI не пишет заявку за исследователя — это невозможно и опасно. AI ускоряет черновую работу: трансформирует технические описания в грантовый язык, проверяет соответствие требованиям фонда, генерирует шаблоны разделов и помогает с логикой нарратива.
Архитектура системы генерации заявок
from anthropic import Anthropic
from langchain_openai import OpenAIEmbeddings
from langchain_community.vectorstores import Chroma
import json
from pathlib import Path
client = Anthropic()
embeddings = OpenAIEmbeddings(model="text-embedding-3-small")
class GrantRequirementsAnalyzer:
"""Анализирует требования грантового фонда"""
def parse_call_for_proposals(self, cfp_text: str) -> dict:
"""Извлекает структурированные требования из конкурсной документации"""
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
messages=[{
"role": "user",
"content": f"""Проанализируй конкурсную документацию и извлеки требования.
Верни JSON:
{{
"funder_name": "название фонда",
"program_name": "название программы",
"max_grant_size": "сумма или null",
"eligible_organizations": ["тип орг. 1", "тип орг. 2"],
"priority_areas": ["приоритет 1", "приоритет 2"],
"required_sections": [
{{"name": "название раздела", "max_words": 500, "description": "что должно быть"}}
],
"evaluation_criteria": [
{{"criterion": "название", "weight": "процент или описание"}}
],
"key_keywords": ["ключевые слова для использования"],
"prohibited": ["что нельзя указывать или делать"],
"deadline": "дата или null",
"reporting_requirements": "требования к отчётности"
}}
Документация:
{cfp_text[:8000]}
Только JSON."""
}],
)
text = response.content[0].text
return json.loads(text[text.find("{"):text.rfind("}") + 1])
class OrganizationProfileBuilder:
"""Строит профиль организации для переиспользования в заявках"""
def __init__(self, org_id: str):
self.vectorstore = Chroma(
collection_name=f"org_profile_{org_id}",
embedding_function=embeddings,
)
def add_document(self, doc_type: str, content: str, metadata: dict = {}):
"""
doc_type: "previous_grant" | "publication" | "team_bio" |
"project_results" | "financial_report" | "partner_letter"
"""
self.vectorstore.add_texts(
texts=[content],
metadatas=[{"doc_type": doc_type, **metadata}]
)
def find_relevant_evidence(self, query: str, doc_types: list = None) -> list[dict]:
"""Находит релевантные материалы из архива организации"""
filter_dict = {"doc_type": {"$in": doc_types}} if doc_types else None
results = self.vectorstore.similarity_search_with_score(
query, k=6, filter=filter_dict
)
return [
{
"content": doc.page_content,
"doc_type": doc.metadata.get("doc_type"),
"relevance": round(1 - score, 2),
}
for doc, score in results
if (1 - score) > 0.5
]
class GrantSectionWriter:
"""Пишет отдельные разделы грантовой заявки"""
SECTION_PROMPTS = {
"problem_statement": """Напиши раздел «Обоснование проблемы».
Используй данные и статистику. Покажи значимость проблемы для целевой аудитории фонда.
Избегай общих слов — конкретные факты, ссылки на исследования.""",
"objectives": """Напиши раздел «Цели и задачи проекта».
Цели должны быть SMART: конкретными, измеримыми, достижимыми, релевантными, ограниченными по времени.
Формат: 1-2 цели + 4-6 задач.""",
"methodology": """Напиши раздел «Методология / план реализации».
Детальное описание: этапы, методы, инструменты, сроки.
Включи план управления рисками.""",
"expected_results": """Напиши раздел «Ожидаемые результаты и показатели».
Чёткие количественные KPI. Как будет измеряться успех?
Разделить: прямые результаты (outputs), результаты изменений (outcomes), долгосрочное воздействие (impact).""",
"team": """Напиши раздел «Команда проекта».
Компетенции ключевых участников. Релевантный опыт. Почему именно эта команда справится?""",
"budget_justification": """Напиши обоснование бюджета.
Каждая статья расходов должна быть обоснована необходимостью для проекта.
Покажи соответствие запрашиваемых сумм рыночным ценам.""",
"sustainability": """Напиши раздел «Устойчивость проекта».
Как проект будет развиваться/поддерживаться после окончания грантового периода?
Источники дальнейшего финансирования.""",
"innovation": """Напиши раздел «Новизна и инновационность».
В чём уникальность подхода? Чем отличается от существующих решений?""",
}
def write_section(
self,
section_name: str,
project_brief: dict,
requirements: dict,
org_evidence: list[dict],
word_limit: int = 500,
) -> str:
"""Генерирует раздел заявки"""
section_instruction = self.SECTION_PROMPTS.get(
section_name,
f"Напиши раздел '{section_name}'."
)
evidence_text = ""
if org_evidence:
evidence_text = "\n\nРелевантные материалы организации:\n" + "\n\n".join([
f"[{e['doc_type']}]: {e['content'][:500]}"
for e in org_evidence[:4]
])
keywords_instruction = ""
if requirements.get("key_keywords"):
keywords_instruction = f"\nВажно использовать ключевые слова фонда: {', '.join(requirements['key_keywords'][:10])}"
response = client.messages.create(
model="claude-sonnet-4-5",
max_tokens=2048,
system=f"""Ты — эксперт по написанию грантовых заявок.
Пишешь для фонда: {requirements.get('funder_name', 'неизвестен')}.
Программа: {requirements.get('program_name', '')}.
Приоритеты фонда: {', '.join(requirements.get('priority_areas', []))}.
Ограничение: {word_limit} слов.{keywords_instruction}
Стиль: профессиональный, конкретный, ориентированный на результат.
НЕ используй шаблонные фразы вроде «данный проект направлен на...»""",
messages=[{
"role": "user",
"content": f"""{section_instruction}
Описание проекта:
{json.dumps(project_brief, ensure_ascii=False, indent=2)}
{evidence_text}
Напиши раздел ({word_limit} слов)."""
}],
)
return response.content[0].text
class GrantApplicationSystem:
"""Полная система генерации грантовых заявок"""
def __init__(self, org_id: str):
self.analyzer = GrantRequirementsAnalyzer()
self.org_profile = OrganizationProfileBuilder(org_id)
self.writer = GrantSectionWriter()
def generate_full_application(
self,
cfp_text: str,
project_brief: dict,
) -> dict:
"""Генерирует полную грантовую заявку"""
# 1. Анализируем требования фонда
requirements = self.analyzer.parse_call_for_proposals(cfp_text)
# 2. Генерируем разделы
application = {
"metadata": {
"funder": requirements.get("funder_name"),
"program": requirements.get("program_name"),
"generated_at": __import__("datetime").datetime.now().isoformat(),
},
"sections": {},
}
for section_req in requirements.get("required_sections", []):
section_name = section_req["name"]
word_limit = section_req.get("max_words", 500)
# Находим релевантные материалы из архива
query = f"{section_name} {project_brief.get('title', '')} {project_brief.get('problem', '')}"
evidence = self.org_profile.find_relevant_evidence(
query,
doc_types=["previous_grant", "project_results", "publication"],
)
# Маппинг названий разделов на типы промптов
section_type = self._map_section_type(section_name)
content = self.writer.write_section(
section_name=section_type,
project_brief=project_brief,
requirements=requirements,
org_evidence=evidence,
word_limit=word_limit,
)
application["sections"][section_name] = {
"content": content,
"word_count": len(content.split()),
"word_limit": word_limit,
}
# 3. Проверяем соответствие критериям
application["compliance_check"] = self._check_compliance(
application["sections"],
requirements,
)
return application
def _map_section_type(self, section_name: str) -> str:
"""Маппинг произвольных названий разделов на типы"""
name_lower = section_name.lower()
mapping = {
"problem": "problem_statement",
"проблем": "problem_statement",
"цели": "objectives",
"задачи": "objectives",
"методол": "methodology",
"план": "methodology",
"результат": "expected_results",
"команд": "team",
"бюджет": "budget_justification",
"устойчив": "sustainability",
"нови": "innovation",
"инновац": "innovation",
}
for key, value in mapping.items():
if key in name_lower:
return value
return "problem_statement"
def _check_compliance(self, sections: dict, requirements: dict) -> dict:
"""Проверяет заявку на соответствие требованиям"""
issues = []
warnings = []
# Проверяем объёмы
for section_name, section_data in sections.items():
if section_data["word_count"] > section_data["word_limit"] * 1.1:
issues.append(f"Раздел '{section_name}': превышен лимит слов ({section_data['word_count']} > {section_data['word_limit']})")
# Проверяем наличие ключевых слов
all_text = " ".join(s["content"] for s in sections.values()).lower()
for keyword in requirements.get("key_keywords", [])[:5]:
if keyword.lower() not in all_text:
warnings.append(f"Отсутствует ключевое слово: '{keyword}'")
return {
"passed": len(issues) == 0,
"issues": issues,
"warnings": warnings,
}
Практический кейс: НКО, 12 заявок в год
Ситуация: некоммерческая организация подаёт 10–15 заявок в год в разные фонды (Президентский фонд культурных инициатив, Фонд «Сколково», региональные фонды). Каждая заявка требует переработки под требования конкретного фонда. 2–3 сотрудника тратили суммарно 600+ часов в год на написание.
Что сделали:
- Проиндексировали 35 предыдущих успешных заявок в org_profile
- Система генерирует черновые разделы за 15–20 минут
- Compliance-чекер проверяет соответствие лимитам и ключевым словам
- Финальная правка живым автором: 6–10 часов вместо 40–80
Результаты:
- Время подготовки заявки: 50–80 ч → 8–12 ч
- Число заявок в год: 12 → 22 (та же команда)
- Процент успешных заявок: без изменений (46% до и после) — AI не влияет на содержательное качество
Важное ограничение: AI-черновик требует тщательной проверки. В заявках для Сколково система включила формулировки из предыдущих заявок, которые противоречили новым требованиям программы. Compliance-чекер это не поймал — добавили шаг ручного просмотра критериев оценки перед финальной подачей.
Сроки
- Анализатор требований фонда: 3–5 дней
- Профиль организации + индексирование архива: 3–5 дней
- Генератор разделов + compliance-чекер: 1 неделя
- Полная система с веб-интерфейсом: 3–4 недели







