Nefertury commited on
Commit
d60faab
·
verified ·
1 Parent(s): 97b78ec

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +424 -425
app.py CHANGED
@@ -1,426 +1,425 @@
1
- import os
2
- import requests
3
- import json
4
- import gradio as gr
5
- import pandas as pd
6
- import matplotlib.pyplot as plt
7
- import pypdf
8
- import warnings
9
- from google.colab import userdata
10
- from langchain_google_genai import ChatGoogleGenerativeAI
11
-
12
- warnings.filterwarnings("ignore", category=UserWarning)
13
- print("--- Библиотеки установлены и импортированы ---")
14
-
15
-
16
- # --- 2. Настройка API и LLM ---
17
- print("--- Настройка API и LLM ---")
18
- try:
19
- GEMINI_API_KEY = userdata.get('GEMINI_API_KEY')
20
- API_BASE_URL = "http://194.113.209.48:8000"
21
- llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro-latest", google_api_key=GEMINI_API_KEY, temperature=0.1)
22
- print("✅ LLM настроена.")
23
- except Exception as e:
24
- print(f"❌ Ошибка настройки LLM: {e}")
25
-
26
-
27
- # --- 3. Загрузка данных и контекста ---
28
- # 3.1. Загружаем заранее проанализированный портфель из CSV
29
- print("--- Загрузка данных портфеля ---")
30
- try:
31
- tagged_portfolio_df = pd.read_csv('sber_portfolio_analyzed5.csv')
32
- tagged_portfolio_df['region_name'] = tagged_portfolio_df['region_name'].str.strip()
33
- print(f"✅ Успешно загружен проанализированный портфель из {len(tagged_portfolio_df)} проектов.")
34
- except FileNotFoundError:
35
- print("❌ Ошибка: Файл 'sber_portfolio_analyzed5.csv' не найден. Возможно, нужно сначала запустить ячейку для генерации этого файла.")
36
- tagged_portfolio_df = pd.DataFrame()
37
-
38
- # 3.2. Извлекаем текст из PDF-отчета
39
- print("--- Извлечение текста из PDF-отчета ---")
40
- full_report_text = ""
41
- try:
42
- # Убедитесь, что имя файла верное и он загружен
43
- with open('f356d44202ac9361579f509ebf965950.pdf', 'rb') as f:
44
- reader = pypdf.PdfReader(f)
45
- for page in reader.pages:
46
- full_report_text += page.extract_text() + "\n"
47
- print("✅ Текст из PDF-файла успешно извлечен.")
48
- except Exception as e:
49
- print(f"❌ Ошибка при чтении PDF-файла: {e}")
50
-
51
- # 3.3. Загружаем справочники
52
- print("--- Загрузка справочников ---")
53
- try:
54
- regions_response = requests.get(f"{API_BASE_URL}/api/regions")
55
- regions_data = regions_response.json()
56
- regions_list = sorted([item['name'] for item in regions_data])
57
- print("✅ Справочник регионов загружен.")
58
- except Exception as e:
59
- print(f"❌ Не удалось загрузить справочник регионов: {e}")
60
- regions_list = sorted(list(tagged_portfolio_df['region_name'].unique()))
61
-
62
- # 3.4. Определяем константы (структура Наццелей) - ПОЛНАЯ ВЕРСИЯ
63
- NATIONAL_GOALS_STRUCTURE = {
64
- "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи": [
65
- "2.а) повышение суммарного коэффициента рождаемости до 1,6 к 2030 году и до 1,8 к 2036 году",
66
- "2.б) увеличение ожидаемой продолжительности жизни до 78 лет к 2030 году и до 81 года к 2036 году",
67
- "2.в) обеспечение не ниже среднероссийских темпов повышения к 2030 году суммарного коэффициента рождаемости в отстающих субъектах РФ",
68
- "2.г) снижение к 2036 году дифференциации показателей ожидаемой продолжительности жизни не менее чем на 25%",
69
- "2.д) снижение к 2030 году суммарной продолжительности временной нетрудоспособности граждан",
70
- "2.е) повышение к 2030 году уровня удовлетворенности граждан условиями для занятий физкультурой и спортом",
71
- "2.ж) увеличение к 2030 году численности граждан, получающих услуги долговременного ухода, до 500 тыс. человек",
72
- "2.з) повышение к 2030 году уровня удовлетворенности участников СВО условиями для медицинской реабилитации и трудоустройства",
73
- "2.и) создание и запуск к 2030 году цифровой платформы по управлению здоровьем человека",
74
- "2.к) снижение уров��я бедности ниже 7% к 2030 году и ниже 5% к 2036 году, в т.ч. среди многодетных семей",
75
- "2.л) снижение коэффициента Джини до 0,37 к 2030 году и до 0,33 к 2036 году",
76
- "2.м) рост МРОТ к 2030 году более чем в два раза, не менее 35 тыс. рублей в месяц",
77
- "2.н) утверждение в 2026 году новых систем оплаты труда работников бюджетной сферы"
78
- ],
79
- "Реализация потенциала каждого человека, развитие его талантов, воспитание патриотичной и социально ответственной личности": [
80
- "3.а) создание к 2030 году условий для воспитания гармонично развитой личности на основе традиционных ценностей",
81
- "3.б) увеличение к 2030 году численности иностранных студентов до 500 тыс. человек",
82
- "3.в) увеличение к 2030 году доли молодых людей, участвующих в проектах развития и воспитания, до 75%",
83
- "3.г) увеличение к 2030 году доли молодых людей, верящих в самореализацию в России, до 85%",
84
- "3.д) увеличение к 2030 году доли молодых людей в добровольческой деятельности до 45%",
85
- "3.е) обеспечение к 2030 году функционирования эффективной системы выявления талантов у 100% обучающихся",
86
- "3.ж) обеспечение продвижения традиционных ценностей в не менее чем 70% проектов в сфере культуры к 2030 году",
87
- "3.з) повышение к 2030 году удовлетворенности граждан работой организаций культуры",
88
- "3.и) формирование к 2030 году современной системы профразвития педагогических работников"
89
- ],
90
- "Комфортная и безопасная среда для жизни": [
91
- "4.а) улучшение качества среды для жизни в опорных населенных пунктах на 30% к 2030 году",
92
- "4.б) обеспечение граждан жильем общей площадью не менее 33 кв. метров на человека к 2030 году",
93
- "4.в) обновление к 2030 году жилищного фонда не менее чем на 20% по сравнению с 2019 годом",
94
- "4.г) устойчивое сокращение непригодного для проживания жилищного фонда",
95
- "4.д) повышение доступности жилья на первичном рынке",
96
- "4.е) благоустройство не менее чем 30 тыс. общественных территорий к 2030 году",
97
- "4.ж) реализация программы модернизации коммунальной инфраструктуры для 20 млн. человек к 2030 году",
98
- "4.з) строительство и реконструкция не менее чем 2 тыс. объектов питьевого водоснабжения к 2030 году",
99
- "4.и) рост энергоэффективности в ЖКХ и строительстве",
100
- "4.к) обновление парка общественного транспорта до 85% от норматива к 2030 году",
101
- "4.л) приведение в нормативное состояние 85% дорог агломераций и 60% региональных дорог к 2030 году",
102
- "4.м) снижение смертности в ДТП в 1.5 раза к 2030 году",
103
- "4.н) увеличение авиационной подвижности населения на 50% к 2030 году",
104
- "4.о) капитальный ремонт всех нуждающихся зданий школ и детсадов до конца 2030 года",
105
- "4.п) подключение к сетевому газу не менее 1,6 млн домовладений к 2030 году",
106
- "4.р) оснащение 900 центров кинопоказа в малых населенных пунктах к 2030 году"
107
- ],
108
- "Экологическое благополучие": [
109
- "5.а) формирование экономики замкнутого цикла: 100% сортировка ТКО, захоронение не более 50%, вовлечение 25% отходов в оборот к 2030 году",
110
- "5.б) снижение в два раза выбросов опасных загрязняющих веществ в городах с высоким уровнем загрязнения к 2036 году",
111
- "5.в) ликвидация до конца 2030 года не менее 50 опасных объектов накопленного вреда",
112
- "5.г) снижение к 2036 году в два раза объема неочищенных сточных вод",
113
- "5.д) сохранение лесов, биоразнообразия и создание условий для экологического туризма"
114
- ],
115
- "Устойчивая и динамичная экономика": [
116
- "6.а) темп роста ВВП выше среднемирового, 4-е место в мире по ВВП по ППС к 2030 году",
117
- "6.б) снижение доли импорта до 17% ВВП к 2030 году",
118
- "6.в) увеличение объема инвестиций в основной капитал на 60% к 2030 году (относительно 2020)",
119
- "6.г) рост доходов населения и пенсий не ниже уровня инфляции",
120
- "6.д) реальный рост дохода на работника МСП в 1,2 раза выше роста ВВП",
121
- "6.ж) вхождение в топ-25 стран мира по плотности роботизации к 2030 году",
122
- "6.н) увеличение доли туристской отрасли в ВВП до 5% к 2030 году",
123
- "6.о) прирост экспорта несырьевых неэнергетических товаров на две трети к 2030 году",
124
- "6.п) увеличение производства продукции АПК на 25% к 2030 году (относительно 2021)",
125
- "6.р) увеличение экспорта продукции АПК в 1.5 раза к 2030 году (относительно 2021)"
126
- ],
127
- "Технологическое лидерство": [
128
- "7.а) обеспечение технологической независимости (биоэкономика, БАС, ИИ, новые материалы и др.)",
129
- "7.б) увеличение индекса производства в обрабатывающей промышленности на 40% к 2030 году (относительно 2022)",
130
- "7.в) вхождение в топ-10 стран мира по объему научных исследований к 2030 году",
131
- "7.г) увеличение внутренних затрат на исследования до 2% ВВП к 2030 году",
132
- "7.д) увеличение доли отечественных высокотехнологичных товаров в 1.5 раза к 2030 году",
133
- "7.е) увеличение выручки малых технологических компаний в 7 раз к 2030 году"
134
- ],
135
- "Цифровая трансформация": [
136
- "8.а) достижение 'цифровой зрелости' госуправления и ключевых отраслей экономики к 2030 году",
137
- "8.б) формирование рынка данных",
138
- "8.в) доступ к высокоскоростному интернету для 97% домохозяйств к 2030 году",
139
- "8.г) рост инвестиций в отечественные ИТ-решения вдвое выше темпа роста ВВП",
140
- "8.д) переход 80% организаций на российское ПО к 2030 году"
141
- ]
142
- }
143
- print("✅ Словарь НАЦЦЕЛЕЙ успешно загружен.")
144
-
145
- # --- 4. Определение функций приложения ---
146
- print("--- Определение функций приложения ---")
147
- # (Здесь идут полные определения функций generate_regional_briefing, _find_relevant_goals, analyze_new_project)
148
- # --- ФИНАЛЬНАЯ ВЕРСИЯ С ТАБЛИЧНЫМ ПРЕДСТАВЛЕНИЕМ АНАЛИЗА ---
149
-
150
- def generate_regional_briefing(region_name, year):
151
- if not region_name or not year:
152
- yield "Пожалуйста, выберите регион и год.", None, None
153
- return
154
-
155
- if not 'full_report_text' in globals() or not full_report_text:
156
- yield "Ошибка: Текст отчета ESG-индекса не был загружен.", None, None
157
- return
158
-
159
- yield "<h3 style='margin-top: 20px;'>⏳ Готовлю финальный отчет, это может занять до 30 секунд...</h3>", None, None
160
-
161
- try:
162
- region_id = next((item['id'] for item in regions_data if item['name'] == region_name), None)
163
- params = {"year": int(year)}
164
- response = requests.get(f"{API_BASE_URL}/api/region-performance/{region_id}/all", params=params)
165
- response.raise_for_status()
166
- urfu_data_raw = response.json()
167
- urfu_data_simplified = {indicator.get('name'): indicator.get('region_value') for indicator in urfu_data_raw.get('indicators', []) if indicator.get('name') and indicator.get('region_value') is not None}
168
- except Exception as e:
169
- yield f"Не удалось получить данные от API УрФУ. Ошибка: {e}", None, None
170
- return
171
-
172
- region_portfolio = tagged_portfolio_df[tagged_portfolio_df['region_name'] == region_name]
173
-
174
- portfolio_summary = "В данном регионе у Сбера отсутствуют проекты из анализируемого портфеля."
175
- fig = None
176
-
177
- if not region_portfolio.empty:
178
- total_investment = region_portfolio['amount_bln_rub'].sum()
179
- project_count = len(region_portfolio)
180
- SUMMARIZATION_THRESHOLD = 5
181
-
182
- if 'mapped_goal' in region_portfolio.columns and not region_portfolio['mapped_goal'].isnull().all():
183
- if project_count > SUMMARIZATION_THRESHOLD:
184
- summary_parts = []
185
- goal_groups = region_portfolio.groupby('mapped_goal')['amount_bln_rub'].sum().sort_values(ascending=False).reset_index()
186
-
187
- for _, row in goal_groups.iterrows():
188
- goal_name = row['mapped_goal']
189
- goal_sum = row['amount_bln_rub']
190
- project_examples = region_portfolio[region_portfolio['mapped_goal'] == goal_name]['project_goal'].head(2).tolist()
191
- examples_text = ", ".join(project_examples)
192
- # ИЗМЕНЕНИЕ: Формируем HTML-элемент списка <li> и используем <strong> для жирности
193
- summary_parts.append(
194
- f"<li><strong>{goal_name}</strong> ({goal_sum:.1f} млрд руб.), ключевые проекты: {examples_text}</li>"
195
- )
196
-
197
- # ИЗМЕНЕНИЕ: Собираем полный HTML-список <ul>...</ul>
198
- summary_text = "<ul>" + "".join(summary_parts) + "</ul>"
199
- portfolio_summary = (
200
- f"Портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. и включает проекты, распределенные по следующим национальным целям:{summary_text}"
201
- )
202
- else:
203
- projects_list = [
204
- f"'{row['project_goal']}' ({row['amount_bln_rub']:.1f} млрд руб.), направленный на достижение цели \"{row.get('mapped_goal', 'не определена')}\""
205
- for _, row in region_portfolio.iterrows()
206
- ]
207
- projects_text = " и ".join(projects_list)
208
- portfolio_summary = (
209
- f"Текущий портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. "
210
- f"и включает следующие ключевые проекты: {projects_text}."
211
- )
212
- else:
213
- projects_list = [
214
- f"'{row['project_goal']}' ({row['amount_bln_rub']:.1f} млрд руб.)"
215
- for _, row in region_portfolio.iterrows()
216
- ]
217
- projects_text = ", ".join(projects_list)
218
- portfolio_summary = (
219
- f"Текущий портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. и включает следующие проекты: {projects_text}."
220
- )
221
-
222
- if 'mapped_goal' in region_portfolio.columns and not region_portfolio['mapped_goal'].isnull().all():
223
- goal_distribution = region_portfolio.groupby('mapped_goal')['amount_bln_rub'].sum()
224
- plot_data = goal_distribution.reset_index()
225
- if plot_data is not None and not plot_data.empty:
226
- fig, ax = plt.subplots(figsize=(10, 6)); bars = ax.bar(plot_data['mapped_goal'], plot_data['amount_bln_rub'], color='#4CAF50'); ax.bar_label(bars, fmt='%.0f', padding=3)
227
- plt.xticks(rotation=45, ha="right", fontsize=9); plt.title(f"Распределение портфеля Сбера по Наццелям\nв регионе: {region_name}", fontsize=12)
228
- plt.ylabel("Сумма, млрд руб.", fontsize=10); plt.grid(axis='y', linestyle='--', alpha=0.7); plt.tight_layout()
229
-
230
- prompt = f"""
231
- Твоя роль: Ведущий аналитик-стратег ESG-дирекции Сбера.
232
- Твоя задача: Подготовить исче��пывающий и СТРУКТУРИРОВАННЫЙ аналитический отчет по региону, синтезируя информацию из предоставленных источников.
233
-
234
- ### ИСТОЧНИК 1: Статистические показатели по региону (от УрФУ)
235
- Регион анализа: {region_name}
236
- {json.dumps(urfu_data_simplified, ensure_ascii=False, indent=2)}
237
-
238
- ### ИСТОЧНИК 2: Полный текст отчета "ESG-индекс городов и регионов" (Сбер, ВЭБ.РФ)
239
- ```text
240
- {full_report_text}
241
- ```
242
-
243
- ### ИСТОЧНИК 3: Данные по проектам в регионе
244
- {portfolio_summary}
245
-
246
- ### ЗАДАЧА И СТРОГИЕ ПРАВИЛА:
247
- 1. Подготовь отчет в формате Markdown.
248
- 2. Сфокусируй детальный анализ ТОЛЬКО на двух Национальных целях: "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи" и "Комфортная и безопасная среда для жизни".
249
- 3. Относись ко всем данным как к реальным, не добавляй дисклеймеров.
250
- 4. Для раздела "Детальный анализ" **сгенерируй Markdown-таблицы**, как показано в шаблоне.
251
-
252
- ---
253
-
254
- ### Аналитический отчет: {region_name}, {year} год
255
-
256
- **1. Обзор проектов в регионе**
257
- * На основе ИСТОЧНИКА 3, опиши состав и объем проектов. Начинай с новой строки каждую новую Наццель и по ней проекты.
258
-
259
- * **Задача для следующей строки:** На основе ИСТОЧНИКА 2, найди конкретные факты о регионе `{region_name}` (место в рейтинге, баллы, и т.д.).
260
- * **Требование к форматированию:** Начни строку с "ESG-индекс городов и регионов Сбер - ВЭБ.РФ:". Сразу после этого, обычным черным текстом, продолжи своим аналитическим выводом.
261
-
262
- **2. Детальный анализ по приоритетным Национальным целям**
263
- * Для каждой из двух приоритетных Наццелей, создай подзаголовок и Markdown-таблицу по следующему образцу. Заполни 2-3 строки для каждой таблицы наиболее показательными данными.
264
-
265
- **Национальная цель: "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи"**
266
- | Показатель (данные УрФУ) | Значение в регионе | Влияние проектов Сбера |
267
- | :--- | :--- | :--- |
268
- | *[название показателя из ИСТОЧНИКА 1]* | *[значение из ИСТОЧНИКА 1]* | *[твой анализ ИСТОЧНИКА 3]* |
269
- | *[название второго показателя]* | *[его значение]* | *[твой анализ]* |
270
-
271
- **Национальная цель: "Комфортная и безопасная среда для жизни"**
272
- | Показатель (данные УрФУ) | Значение в регионе | Влияние проектов Сбера |
273
- | :--- | :--- | :--- |
274
- | *[название показателя из ИСТОЧНИКА 1]* | *[значение из ИСТОЧНИКА 1]* | *[твой анализ ИСТОЧНИКА 3]* |
275
- | *[название второго показателя]* | *[его значение]* | *[твой анализ]* |
276
-
277
- **3. Стратегические рекомендации**
278
- * На основе табличного анализа, сформулируй 2-3 ключевых вывода и предложи конкретные типы проектов для инвестиций.
279
- """
280
-
281
- final_report = llm.invoke(prompt).content
282
- yield final_report, fig, region_portfolio.drop(columns=['project_goal'], errors='ignore')
283
-
284
- def _find_relevant_goals(project_description: str) -> list:
285
- top_level_goals = list(NATIONAL_GOALS_STRUCTURE.keys())
286
- prompt = f"""
287
- Определи 1-3 наиболее релевантных национальных цели из списка для проекта. Верни ТОЛЬКО JSON-массив строк.
288
- СПИСОК НАЦЦЕЛЕЙ: {json.dumps(top_level_goals, ensure_ascii=False)}
289
- ОПИСАНИЕ ПРОЕКТА: "{project_description}"
290
- Твой JSON-ответ:
291
- """
292
- try:
293
- response = llm.invoke(prompt)
294
- return json.loads(response.content.strip().replace("```json", "").replace("```", ""))
295
- except Exception:
296
- return top_level_goals
297
-
298
-
299
- def analyze_new_project(region_name, project_description):
300
- if not all([region_name, project_description]):
301
- yield "Пожалуйста, выберите регион и опишите проект."
302
- return
303
-
304
- yield "<h3 style='margin-top: 20px;'>⏳ Этап 1/2: Определяю релевантные наццели...</h3>"
305
- relevant_goals_names = _find_relevant_goals(project_description)
306
- relevant_goals_structure = {goal: NATIONAL_GOALS_STRUCTURE.get(goal, []) for goal in relevant_goals_names}
307
-
308
- yield "<h3 style='margin-top: 20px;'>⏳ Этап 2/2: Готовлю детальный анализ, это может занять до 30 секунд...</h3>"
309
-
310
- try:
311
- region_id = next((item['id'] for item in regions_data if item['name'] == region_name), None)
312
- params = {"year": 2024} # Можно использовать актуальный год
313
- response = requests.get(f"{API_BASE_URL}/api/region-performance/{region_id}/all", params=params)
314
- response.raise_for_status()
315
- urfu_data_raw = response.json()
316
- urfu_data_simplified = {indicator.get('name'): indicator.get('region_value') for indicator in urfu_data_raw.get('indicators', []) if indicator.get('name') and indicator.get('region_value') is not None}
317
- except Exception as e:
318
- yield f"Не удалось получить данные от API УрФУ. Ошибка: {e}"
319
- return
320
-
321
- goals_structure_str = json.dumps(relevant_goals_structure, ensure_ascii=False, indent=2)
322
-
323
- # --- НАЧАЛО НОВОГО, РАСШИРЕННОГО ПРОМПТА ---
324
- prompt = f"""
325
- Твоя роль: Ведущий аналитик ESG-дирекции Сбера, обладающий глубокими знаниями в области национальных целей развития РФ.
326
-
327
- Твоя задача: Подготовить ДЕТАЛЬНЫЙ и СТРУКТУРИРОВАННЫЙ аналитический отчет по новому инвестиционному проекту. Отчет должен быть выполнен в формате Markdown и содержать глубокий анализ, а не поверхностное описание.
328
-
329
- ### ВХОДНЫЕ ДАННЫЕ:
330
- 1. **Регион реализации:** {region_name}
331
- 2. **Описание проекта:** "{project_description}"
332
- 3. **Статистический контекст по региону (данные УрФУ):** {json.dumps(urfu_data_simplified, ensure_ascii=False, indent=2)}
333
- 4. **Структура релевантных Национальных целей и их задач:** {goals_structure_str}
334
-
335
- ### СТРОГИЕ ТРЕБОВАНИЯ К СТРУКТУРЕ И СОДЕРЖАНИЮ ОТЧЕТА:
336
-
337
- **1. Краткое резюме проекта**
338
- * Опиши суть проекта, его масштаб и основные цели.
339
-
340
- **2. Анализ соответствия Национальным целям**
341
- * Для КАЖДОЙ релевантной наццели из предоставленного списка:
342
- * **Обоснуй, почему проект соответствует данной цели.** Четко свяжи деятельность по проекту с конкретными задачами (подпунктами) этой наццели.
343
- * **Используй данные из статистического контекста (ВХОДНЫЕ ДАННЫЕ №3)**, чтобы показать АКТУАЛЬНОСТЬ проекта для региона. Например, если проект влияет на здравоохранение, приведи текущие показатели по ожидаемой продолжительности жизни или заболеваемости в регионе, чтобы подчеркнуть важность инвестиций.
344
-
345
- **3. Оценка по ESG-критериям**
346
- * **E (Environmental / Экология):** Оцени потенциальное положительное и отрицательное воздействие проекта на окружающую среду. Какие экологические риски и возможности он несет? (например, снижение выбросов, образование отходов, использование еленых" технологий).
347
- * **S (Social / Социальная сфера):** Проанализируй социальный эффект. Создание рабочих мест (сколько, каких?), влияние на к��чество жизни, доступность услуг, поддержка местных сообществ.
348
- * **G (Governance / Управление):** Опиши потенциальные управленческие аспекты. Необходимость взаимодействия с региональными властями, прозрачность реализации, работа с заинтересованными сторонами.
349
-
350
- **4. Предварительные риски и рекомендации**
351
- * На основе всего анализа, выдели 2-3 ключевых риска (технологических, социальных, регуляторных), на которые стоит обратить внимание.
352
- * Дай 1-2 стратегические рекомендации по усилению положительного эффекта от проекта.
353
-
354
- **5. Итоговое заключение**
355
- * Сделай общий вывод о стратегической привлекательности проекта для банка с точки зрения вклада в устойчивое развитие и национальные приоритеты.
356
-
357
- ---
358
- **Начинай твой отчет.**
359
- """
360
- # --- КОНЕЦ НОВОГО ПРОМПТА ---
361
-
362
- analysis_report = llm.invoke(prompt).content
363
- yield analysis_report
364
-
365
-
366
- # --- 5. Запуск веб-интерфейса Gradio ---
367
- print("🚀 Запускаем Gradio-приложение...")
368
- with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime"), css=".gradio-container {background-color: #f5f5f5} th { white-space: nowrap; }") as demo:
369
- gr.Markdown(
370
- """<div style="text-align: center;"><img src="https://www.sberbank.com/common/img/uploaded/logo/logo_sber_main_2020_ru.svg" width="200">
371
- <h1>Интерактивный дашборд "Горизонт PRO"</h1><p>Анализ вклада в достижение Национальных целей РФ 2030/2036</p></div>"""
372
- )
373
- with gr.Tabs():
374
- with gr.TabItem("📈 Стратегический обзор региона"):
375
- with gr.Row():
376
- region_input_1 = gr.Dropdown(regions_list, label="Выберите регион")
377
- year_input_1 = gr.Dropdown([2024, 2023, 2022], label="Выберите год", value=2024)
378
- submit_button_1 = gr.Button("Сформировать стратегическую справку", variant="primary")
379
- gr.Markdown("---")
380
- output_report_1 = gr.Markdown(label="Аналитическая справка")
381
-
382
- # Элементы создаются, но не отображаются в интерфейсе
383
- # Это необходимо, чтобы функция click могла в них вернуть значения без ошибки.
384
- output_plot_1 = gr.Plot(visible=False)
385
- output_table_1 = gr.DataFrame(label="Проекты в портфеле", visible=False)
386
-
387
-
388
- with gr.TabItem("🔎 Экспресс-оценка нового проекта"):
389
- # --- ИЗМЕНЕНИЕ №1: Выносим текст примера в отдельную переменную ---
390
- example_text = "Мусоросортировочный завод на 150 000 тонн стоимостью 3 млрд руб"
391
-
392
- gr.Markdown("Введите описание нового инвестиционного проекта для анализа его соответствия Национальным целям и потребностям региона.")
393
- region_input_2 = gr.Dropdown(regions_list, label="Выберите регион реализации проекта")
394
- project_input_2 = gr.Textbox(lines=4, label="Опишите проект", placeholder=f"Пример: {example_text}")
395
-
396
- # --- ИЗМЕНЕНИЕ №2: Добавляем две кнопки в одну строку ---
397
- with gr.Row():
398
- # Кнопка для вставки примера
399
- example_button = gr.Button("✍️ Вставить пример", variant="secondary")
400
- # Основная кнопка для анализа
401
- submit_button_2 = gr.Button("Провести экспресс-оценку", variant="primary", scale=2) # scale=2 делает ее в 2 раза шире
402
-
403
- gr.Markdown("---")
404
- output_report_2 = gr.Markdown(label="Результат оценки")
405
-
406
- # --- Привязка функций к кнопкам (остается без изменений) ---
407
- submit_button_1.click(
408
- fn=generate_regional_briefing,
409
- inputs=[region_input_1, year_input_1],
410
- outputs=[output_report_1, output_plot_1, output_table_1]
411
- )
412
-
413
- # --- ИЗМЕНЕНИЕ №3: Добавляем обработчик для новой кнопки ---
414
- example_button.click(
415
- fn=lambda: example_text, # Простая функция, которая просто возвращает текст примера
416
- inputs=None, # У нее нет входных данных
417
- outputs=project_input_2 # Результат (текст) отправляется в наше поле для ввода
418
- )
419
-
420
- submit_button_2.click(
421
- fn=analyze_new_project,
422
- inputs=[region_input_2, project_input_2],
423
- outputs=[output_report_2]
424
- )
425
-
426
  demo.launch(share=True, debug=True)
 
1
+ import os
2
+ import requests
3
+ import json
4
+ import gradio as gr
5
+ import pandas as pd
6
+ import matplotlib.pyplot as plt
7
+ import pypdf
8
+ import warnings
9
+ from langchain_google_genai import ChatGoogleGenerativeAI
10
+
11
+ warnings.filterwarnings("ignore", category=UserWarning)
12
+ print("--- Библиотеки установлены и импортированы ---")
13
+
14
+
15
+ # --- 2. Настройка API и LLM ---
16
+ print("--- Настройка API и LLM ---")
17
+ try:
18
+ GEMINI_API_KEY = userdata.get('GEMINI_API_KEY')
19
+ API_BASE_URL = "http://194.113.209.48:8000"
20
+ llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro-latest", google_api_key=GEMINI_API_KEY, temperature=0.1)
21
+ print(" LLM настроена.")
22
+ except Exception as e:
23
+ print(f"❌ Ошибка настройки LLM: {e}")
24
+
25
+
26
+ # --- 3. Загрузка данных и контекста ---
27
+ # 3.1. Загружаем заранее проанализированный портфель из CSV
28
+ print("--- Загрузка данных портфеля ---")
29
+ try:
30
+ tagged_portfolio_df = pd.read_csv('sber_portfolio_analyzed5.csv')
31
+ tagged_portfolio_df['region_name'] = tagged_portfolio_df['region_name'].str.strip()
32
+ print(f"✅ Успешно загружен проанализированный портфель из {len(tagged_portfolio_df)} проектов.")
33
+ except FileNotFoundError:
34
+ print("❌ Ошибка: Файл 'sber_portfolio_analyzed5.csv' не найден. Возможно, нужно сначала запустить ячейку для генерации этого файла.")
35
+ tagged_portfolio_df = pd.DataFrame()
36
+
37
+ # 3.2. Извлекаем текст из PDF-отчета
38
+ print("--- Извлечение текста из PDF-отчета ---")
39
+ full_report_text = ""
40
+ try:
41
+ # Убедитесь, что имя файла верное и он загружен
42
+ with open('f356d44202ac9361579f509ebf965950.pdf', 'rb') as f:
43
+ reader = pypdf.PdfReader(f)
44
+ for page in reader.pages:
45
+ full_report_text += page.extract_text() + "\n"
46
+ print("✅ Текст из PDF-файла успешно извлечен.")
47
+ except Exception as e:
48
+ print(f"❌ Ошибка при чтении PDF-файла: {e}")
49
+
50
+ # 3.3. Загружаем справочники
51
+ print("--- Загрузка справочников ---")
52
+ try:
53
+ regions_response = requests.get(f"{API_BASE_URL}/api/regions")
54
+ regions_data = regions_response.json()
55
+ regions_list = sorted([item['name'] for item in regions_data])
56
+ print("✅ Справочник регионов загружен.")
57
+ except Exception as e:
58
+ print(f"❌ Не удалось загрузить справочник регионов: {e}")
59
+ regions_list = sorted(list(tagged_portfolio_df['region_name'].unique()))
60
+
61
+ # 3.4. Определяем константы (структура Наццелей) - ПОЛНАЯ ВЕРСИЯ
62
+ NATIONAL_GOALS_STRUCTURE = {
63
+ "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи": [
64
+ "2.а) повышение суммарного коэффициента рождаемости до 1,6 к 2030 году и до 1,8 к 2036 году",
65
+ "2.б) увеличение ожидаемой продолжительности жизни до 78 лет к 2030 году и до 81 года к 2036 году",
66
+ "2.в) обеспечение не ниже среднероссийских темпов повышения к 2030 году суммарного коэффициента рождаемости в отстающих субъектах РФ",
67
+ "2.г) снижение к 2036 году дифференциации показателей ожидаемой продолжительности жизни не менее чем на 25%",
68
+ "2.д) снижение к 2030 году суммарной продолжительности временной нетрудоспособности граждан",
69
+ "2.е) повышение к 2030 году уровня удовлетворенности граждан условиями для занятий физкультурой и спортом",
70
+ "2.ж) увеличение к 2030 году численности граждан, получающих ��слуги долговременного ухода, до 500 тыс. человек",
71
+ "2.з) повышение к 2030 году уровня удовлетворенности участников СВО условиями для медицинской реабилитации и трудоустройства",
72
+ "2.и) создание и запуск к 2030 году цифровой платформы по управлению здоровьем человека",
73
+ "2.к) снижение уровня бедности ниже 7% к 2030 году и ниже 5% к 2036 году, в т.ч. среди многодетных семей",
74
+ "2.л) снижение коэффициента Джини до 0,37 к 2030 году и до 0,33 к 2036 году",
75
+ "2.м) рост МРОТ к 2030 году более чем в два раза, не менее 35 тыс. рублей в месяц",
76
+ "2.н) утверждение в 2026 году новых систем оплаты труда работников бюджетной сферы"
77
+ ],
78
+ "Реализация потенциала каждого человека, развитие его талантов, воспитание патриотичной и социально ответственной личности": [
79
+ "3.а) создание к 2030 году условий для воспитания гармонично развитой личности на основе традиционных ценностей",
80
+ "3.б) увеличение к 2030 году численности иностранных студентов до 500 тыс. человек",
81
+ "3.в) увеличение к 2030 году доли молодых людей, участвующих в проектах развития и воспитания, до 75%",
82
+ "3.г) увеличение к 2030 году доли молодых людей, верящих в самореализацию в России, до 85%",
83
+ "3.д) увеличение к 2030 году доли молодых людей в добровольческой деятельности до 45%",
84
+ "3.е) обеспечение к 2030 году функционирования эффективной системы выявления талантов у 100% обучающихся",
85
+ "3.ж) обеспечение продвижения традиционных ценностей в не менее чем 70% проектов в сфере культуры к 2030 году",
86
+ "3.з) повышение к 2030 году удовлетворенности граждан работой организаций культуры",
87
+ "3.и) формирование к 2030 году современной системы профразвития педагогических работников"
88
+ ],
89
+ "Комфортная и безопасная среда для жизни": [
90
+ "4.а) улучшение качества среды для жизни в опорных населенных пунктах на 30% к 2030 году",
91
+ "4.б) обеспечение граждан жильем общей площадью не менее 33 кв. метров на человека к 2030 году",
92
+ "4.в) обновление к 2030 году жилищного фонда не менее чем на 20% по сравнению с 2019 годом",
93
+ "4.г) устойчивое сокращение непригодного для проживания жилищного фонда",
94
+ "4.д) повышение доступности жилья на первичном рынке",
95
+ "4.е) благоустройство не менее чем 30 тыс. общественных территорий к 2030 году",
96
+ "4.ж) реализация программы модернизации коммунальной инфраструктуры для 20 млн. человек к 2030 году",
97
+ "4.з) строительство и реконструкция не менее чем 2 тыс. объектов питьевого водоснабжения к 2030 году",
98
+ "4.и) рост энергоэффективности в ЖКХ и строительстве",
99
+ "4.к) обновление парка общественного транспорта до 85% от норматива к 2030 году",
100
+ "4.л) приведение в нормативное состояние 85% дорог агломераций и 60% региональных дорог к 2030 году",
101
+ "4.м) снижение смертности в ДТП в 1.5 раза к 2030 году",
102
+ "4.н) увеличение авиационной подвижности населения на 50% к 2030 году",
103
+ "4.о) капитальный ремонт всех нуждающихся зданий школ и детсадов до конца 2030 года",
104
+ "4.п) подключение к сетевому газу не менее 1,6 млн домовладений к 2030 году",
105
+ "4.р) оснащение 900 центров кинопоказа в малых населенных пунктах к 2030 году"
106
+ ],
107
+ "Экологическое благополучие": [
108
+ "5.а) формирование экономики замкнутого цикла: 100% сортировка ТКО, захоронение не более 50%, вовлечение 25% отходов в оборот к 2030 году",
109
+ "5.б) снижение в два раза выбросов опасных загрязняющих веществ в городах с высоким уровнем загрязнения к 2036 году",
110
+ "5.в) ликвидация до конца 2030 года не менее 50 опасных объектов накопленного вреда",
111
+ "5.г) снижение к 2036 году в два раза объема неочищенных сточных вод",
112
+ "5.д) сохранение лесов, биоразнообразия и создание условий для экологического туризма"
113
+ ],
114
+ "Устойчивая и динамичная экономика": [
115
+ "6.а) темп роста ВВП выше среднемирового, 4-е место в мире по ВВП по ППС к 2030 году",
116
+ "6.б) снижение доли импорта до 17% ВВП к 2030 году",
117
+ "6.в) увеличение объема инвестиций в основной капитал на 60% к 2030 году (относительно 2020)",
118
+ "6.г) рост доходов населения и пенсий не ниже уровня инфляции",
119
+ "6.д) реальный рост дохода на работника МСП в 1,2 раза выше роста ВВП",
120
+ "6.ж) вхождение в топ-25 стран мира по плотности роботизации к 2030 году",
121
+ "6.н) увеличение доли туристской отрасли в ВВП до 5% к 2030 году",
122
+ "6.о) прирост экспорта несырьевых неэнергетических товаров на две трети к 2030 году",
123
+ "6.п) увеличение производства продукции АПК на 25% к 2030 году (относительно 2021)",
124
+ "6.р) увеличение экспорта продукции АПК в 1.5 раза к 2030 году (относительно 2021)"
125
+ ],
126
+ "Технологическое лидерство": [
127
+ "7.а) обеспечение технологической независимости (биоэкономика, БАС, ИИ, новые материалы и др.)",
128
+ "7.б) увеличение индекса производства в обрабатывающей промышленности на 40% к 2030 году тносительно 2022)",
129
+ "7.в) вхождение в топ-10 стран мира по объему научных исследований к 2030 году",
130
+ "7.г) увеличение внутренних затрат на исследования до 2% ВВП к 2030 году",
131
+ "7.д) увеличение доли отечественных высокотехнологичных товаров в 1.5 раза к 2030 году",
132
+ "7.е) увеличение выручки малых технологических компаний в 7 раз к 2030 году"
133
+ ],
134
+ "Цифровая трансформация": [
135
+ "8.а) достижение 'цифровой зрелости' госуправления и ключевых отраслей экономики к 2030 году",
136
+ "8.б) формирование рынка данных",
137
+ "8.в) доступ к высокоскоростному интернету для 97% домохозяйств к 2030 году",
138
+ "8.г) рост инвестиций в отечественные ИТ-решения вдвое выше темпа роста ВВП",
139
+ "8.д) переход 80% организаций на российское ПО к 2030 году"
140
+ ]
141
+ }
142
+ print("✅ Словарь НАЦЦЕЛЕЙ успешно загружен.")
143
+
144
+ # --- 4. Определение функций приложения ---
145
+ print("--- Определение функций приложения ---")
146
+ # (Здесь идут полные определения функций generate_regional_briefing, _find_relevant_goals, analyze_new_project)
147
+ # --- ФИНАЛЬНАЯ ВЕРСИЯ С ТАБЛИЧНЫМ ПРЕДСТАВЛЕНИЕМ АНАЛИЗА ---
148
+
149
+ def generate_regional_briefing(region_name, year):
150
+ if not region_name or not year:
151
+ yield "Пожалуйста, выберите регион и год.", None, None
152
+ return
153
+
154
+ if not 'full_report_text' in globals() or not full_report_text:
155
+ yield "Ошибка: Текст отчета ESG-индекса не был загружен.", None, None
156
+ return
157
+
158
+ yield "<h3 style='margin-top: 20px;'>⏳ Готовлю финальный отчет, это может занять до 30 секунд...</h3>", None, None
159
+
160
+ try:
161
+ region_id = next((item['id'] for item in regions_data if item['name'] == region_name), None)
162
+ params = {"year": int(year)}
163
+ response = requests.get(f"{API_BASE_URL}/api/region-performance/{region_id}/all", params=params)
164
+ response.raise_for_status()
165
+ urfu_data_raw = response.json()
166
+ urfu_data_simplified = {indicator.get('name'): indicator.get('region_value') for indicator in urfu_data_raw.get('indicators', []) if indicator.get('name') and indicator.get('region_value') is not None}
167
+ except Exception as e:
168
+ yield f"Не удалось получить данные от API УрФУ. Ошибка: {e}", None, None
169
+ return
170
+
171
+ region_portfolio = tagged_portfolio_df[tagged_portfolio_df['region_name'] == region_name]
172
+
173
+ portfolio_summary = "В данном регионе у Сбера отсутствуют проекты из анализируемого портфеля."
174
+ fig = None
175
+
176
+ if not region_portfolio.empty:
177
+ total_investment = region_portfolio['amount_bln_rub'].sum()
178
+ project_count = len(region_portfolio)
179
+ SUMMARIZATION_THRESHOLD = 5
180
+
181
+ if 'mapped_goal' in region_portfolio.columns and not region_portfolio['mapped_goal'].isnull().all():
182
+ if project_count > SUMMARIZATION_THRESHOLD:
183
+ summary_parts = []
184
+ goal_groups = region_portfolio.groupby('mapped_goal')['amount_bln_rub'].sum().sort_values(ascending=False).reset_index()
185
+
186
+ for _, row in goal_groups.iterrows():
187
+ goal_name = row['mapped_goal']
188
+ goal_sum = row['amount_bln_rub']
189
+ project_examples = region_portfolio[region_portfolio['mapped_goal'] == goal_name]['project_goal'].head(2).tolist()
190
+ examples_text = ", ".join(project_examples)
191
+ # ИЗМЕНЕНИЕ: Формируем HTML-элемент списка <li> и используем <strong> для жирности
192
+ summary_parts.append(
193
+ f"<li><strong>{goal_name}</strong> ({goal_sum:.1f} млрд руб.), ключевые проекты: {examples_text}</li>"
194
+ )
195
+
196
+ # ИЗМЕНЕНИЕ: Собираем полный HTML-список <ul>...</ul>
197
+ summary_text = "<ul>" + "".join(summary_parts) + "</ul>"
198
+ portfolio_summary = (
199
+ f"Портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. и включает проекты, распределенные по следующим национальным целям:{summary_text}"
200
+ )
201
+ else:
202
+ projects_list = [
203
+ f"'{row['project_goal']}' ({row['amount_bln_rub']:.1f} млрд руб.), направленный на достижение цели \"{row.get('mapped_goal', 'не определена')}\""
204
+ for _, row in region_portfolio.iterrows()
205
+ ]
206
+ projects_text = " и ".join(projects_list)
207
+ portfolio_summary = (
208
+ f"Текущий портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. "
209
+ f"и включает следующие ключевые проекты: {projects_text}."
210
+ )
211
+ else:
212
+ projects_list = [
213
+ f"'{row['project_goal']}' ({row['amount_bln_rub']:.1f} млрд руб.)"
214
+ for _, row in region_portfolio.iterrows()
215
+ ]
216
+ projects_text = ", ".join(projects_list)
217
+ portfolio_summary = (
218
+ f"Текущий портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. и включает следующие проекты: {projects_text}."
219
+ )
220
+
221
+ if 'mapped_goal' in region_portfolio.columns and not region_portfolio['mapped_goal'].isnull().all():
222
+ goal_distribution = region_portfolio.groupby('mapped_goal')['amount_bln_rub'].sum()
223
+ plot_data = goal_distribution.reset_index()
224
+ if plot_data is not None and not plot_data.empty:
225
+ fig, ax = plt.subplots(figsize=(10, 6)); bars = ax.bar(plot_data['mapped_goal'], plot_data['amount_bln_rub'], color='#4CAF50'); ax.bar_label(bars, fmt='%.0f', padding=3)
226
+ plt.xticks(rotation=45, ha="right", fontsize=9); plt.title(f"Распределение портфеля Сбера по Наццелям\nв регионе: {region_name}", fontsize=12)
227
+ plt.ylabel("Сумма, млрд руб.", fontsize=10); plt.grid(axis='y', linestyle='--', alpha=0.7); plt.tight_layout()
228
+
229
+ prompt = f"""
230
+ Твоя роль: Ведущий аналитик-стратег ESG-дирекции Сбера.
231
+ Твоя задача: Подготовить исчерпывающий и СТРУКТУРИРОВАННЫЙ аналитический отчет по региону, синтезируя информацию из предоставленных источников.
232
+
233
+ ### ИСТОЧНИК 1: Статистические показатели по региону (от УрФУ)
234
+ Регион анализа: {region_name}
235
+ {json.dumps(urfu_data_simplified, ensure_ascii=False, indent=2)}
236
+
237
+ ### ИСТОЧНИК 2: Полный текст отчета "ESG-индекс городов и регионов" (Сбер, ВЭБ.РФ)
238
+ ```text
239
+ {full_report_text}
240
+ ```
241
+
242
+ ### ИСТОЧНИК 3: Данные по проектам в регионе
243
+ {portfolio_summary}
244
+
245
+ ### ЗАДАЧА И СТРОГИЕ ПРАВИЛА:
246
+ 1. Подготовь отчет в формате Markdown.
247
+ 2. Сфокусируй детальный анализ ТОЛЬКО на двух Национальных целях: "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи" и "Комфортная и безопасная среда для жизни".
248
+ 3. Относись ко всем данным как к реальным, не добавляй дисклеймеров.
249
+ 4. Для раздела "Детальный анализ" **сгенерируй Markdown-таблицы**, как показано в шаблоне.
250
+
251
+ ---
252
+
253
+ ### Аналитический отчет: {region_name}, {year} год
254
+
255
+ **1. Обзор проектов в регионе**
256
+ * На основе ИСТОЧНИКА 3, опиши состав и объем проектов. Начинай с новой строки каждую новую Наццель и по ней проекты.
257
+
258
+ * **Задача для следующей строки:** На основе ИСТОЧНИКА 2, найди конкретные факты о регионе `{region_name}` (место в рейтинге, баллы, и т.д.).
259
+ * **Требование к форматированию:** Начни строку с "ESG-индекс городов и регионов Сбер - ВЭБ.РФ:". Сразу после этого, обычным черным текстом, продолжи своим аналитическим выводом.
260
+
261
+ **2. Детальный анализ по приоритетным Национальным целям**
262
+ * Для каждой из двух приоритетных Наццелей, создай подзаголовок и Markdown-таблицу по следующему образцу. Заполни 2-3 строки для каждой таблицы наиболее показательными данными.
263
+
264
+ **Национальная цель: "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи"**
265
+ | Показатель анные УрФУ) | Значение в регионе | Влияние проектов Сбера |
266
+ | :--- | :--- | :--- |
267
+ | *[название показателя из ИСТОЧНИКА 1]* | *[значение из ИСТОЧНИКА 1]* | *[твой анализ ИСТОЧНИКА 3]* |
268
+ | *[название второго показателя]* | *[его значение]* | *[твой анализ]* |
269
+
270
+ **Национальная цель: "Комфортная и безопасная среда для жизни"**
271
+ | Показатель (данные УрФУ) | Значение в регионе | Влияние проектов Сбера |
272
+ | :--- | :--- | :--- |
273
+ | *[название показателя из ИСТОЧНИКА 1]* | *[значение из ИСТОЧНИКА 1]* | *[твой анализ ИСТОЧНИКА 3]* |
274
+ | *[название второго показателя]* | *[его значение]* | *[твой анализ]* |
275
+
276
+ **3. Стратегические рекомендации**
277
+ * На основе табличного анализа, сформулируй 2-3 ключевых вывода и предложи конкретные типы проектов для инвестиций.
278
+ """
279
+
280
+ final_report = llm.invoke(prompt).content
281
+ yield final_report, fig, region_portfolio.drop(columns=['project_goal'], errors='ignore')
282
+
283
+ def _find_relevant_goals(project_description: str) -> list:
284
+ top_level_goals = list(NATIONAL_GOALS_STRUCTURE.keys())
285
+ prompt = f"""
286
+ Определи 1-3 наиболее релевантных национальных цели из списка для проекта. Верни ТОЛЬКО JSON-массив строк.
287
+ СПИСОК НАЦЦЕЛЕЙ: {json.dumps(top_level_goals, ensure_ascii=False)}
288
+ ОПИСАНИЕ ПРОЕКТА: "{project_description}"
289
+ Твой JSON-ответ:
290
+ """
291
+ try:
292
+ response = llm.invoke(prompt)
293
+ return json.loads(response.content.strip().replace("```json", "").replace("```", ""))
294
+ except Exception:
295
+ return top_level_goals
296
+
297
+
298
+ def analyze_new_project(region_name, project_description):
299
+ if not all([region_name, project_description]):
300
+ yield "Пожалуйста, выберите регион и опишите проект."
301
+ return
302
+
303
+ yield "<h3 style='margin-top: 20px;'>⏳ Этап 1/2: Определяю релевантные наццели...</h3>"
304
+ relevant_goals_names = _find_relevant_goals(project_description)
305
+ relevant_goals_structure = {goal: NATIONAL_GOALS_STRUCTURE.get(goal, []) for goal in relevant_goals_names}
306
+
307
+ yield "<h3 style='margin-top: 20px;'>⏳ Этап 2/2: Готовлю детальный анализ, это может занять до 30 секунд...</h3>"
308
+
309
+ try:
310
+ region_id = next((item['id'] for item in regions_data if item['name'] == region_name), None)
311
+ params = {"year": 2024} # Можно использовать актуальный год
312
+ response = requests.get(f"{API_BASE_URL}/api/region-performance/{region_id}/all", params=params)
313
+ response.raise_for_status()
314
+ urfu_data_raw = response.json()
315
+ urfu_data_simplified = {indicator.get('name'): indicator.get('region_value') for indicator in urfu_data_raw.get('indicators', []) if indicator.get('name') and indicator.get('region_value') is not None}
316
+ except Exception as e:
317
+ yield f"Не удалось получить данные от API УрФУ. Ошибка: {e}"
318
+ return
319
+
320
+ goals_structure_str = json.dumps(relevant_goals_structure, ensure_ascii=False, indent=2)
321
+
322
+ # --- НАЧАЛО НОВОГО, РАСШИРЕННОГО ПРОМПТА ---
323
+ prompt = f"""
324
+ Твоя роль: Ведущий аналитик ESG-дирекции Сбера, обладающий глубокими знаниями в области национальных целей развития РФ.
325
+
326
+ Твоя задача: Подготовить ДЕТАЛЬНЫЙ и СТРУКТУРИРОВАННЫЙ аналитический отчет по новому инвестиционному проекту. Отчет должен быть выполнен в формате Markdown и содержать глубокий анализ, а не поверхностное описание.
327
+
328
+ ### ВХОДНЫЕ ДАННЫЕ:
329
+ 1. **Регион реализации:** {region_name}
330
+ 2. **Описание проекта:** "{project_description}"
331
+ 3. **Статистический контекст по региону (данные УрФУ):** {json.dumps(urfu_data_simplified, ensure_ascii=False, indent=2)}
332
+ 4. **Структура релевантных Национальных целей и их задач:** {goals_structure_str}
333
+
334
+ ### СТРОГИЕ ТРЕБОВАНИЯ К СТРУКТУРЕ И СОДЕРЖАНИЮ ОТЧЕТА:
335
+
336
+ **1. Краткое резюме проекта**
337
+ * Опиши суть проекта, его масштаб и основные цели.
338
+
339
+ **2. Анализ соответствия Национальным целям**
340
+ * Для КАЖДОЙ релевантной наццели из предоставленного списка:
341
+ * **Обоснуй, почему проект соответствует данной цели.** Четко свяжи деятельность по проекту с конкретными задачами (подпунктами) этой наццели.
342
+ * **Используй данные из статистического контекста (ВХОДНЫЕ ДАННЫЕ №3)**, чтобы показать АКТУАЛЬНОСТЬ проекта для региона. Например, если проект влияет на здравоохранение, приведи текущие показатели по ожидаемой продолжительности жизни или заболеваемости в регионе, чтобы подчеркнуть важность инвестиций.
343
+
344
+ **3. Оценка по ESG-критериям**
345
+ * **E (Environmental / Экология):** Оцени потенциальное положительное и отрицательное воздействие проекта на окружающую среду. Какие экологические риски и возможности он несет? (например, снижение выбросов, образование отходов, использов��ние "зеленых" технологий).
346
+ * **S (Social / Социальная сфера):** Проанализируй социальный эффект. Создание рабочих мест (сколько, каких?), влияние на качество жизни, доступность услуг, поддержка местных сообществ.
347
+ * **G (Governance / Управление):** Опиши потенциальные управленческие аспекты. Необходимость взаимодействия с региональными властями, прозрачность реализации, работа с заинтересованными сторонами.
348
+
349
+ **4. Предварительные риски и рекомендации**
350
+ * На основе всего анализа, выдели 2-3 ключевых риска (технологических, социальных, регуляторных), на которые стоит обратить внимание.
351
+ * Дай 1-2 стратегические рекомендации по усилению положительного эффекта от проекта.
352
+
353
+ **5. Итоговое заключение**
354
+ * Сделай общий вывод о стратегической привлекательности проекта для банка с точки зрения вклада в устойчивое развитие и национальные приоритеты.
355
+
356
+ ---
357
+ **Начинай твой отчет.**
358
+ """
359
+ # --- КОНЕЦ НОВОГО ПРОМПТА ---
360
+
361
+ analysis_report = llm.invoke(prompt).content
362
+ yield analysis_report
363
+
364
+
365
+ # --- 5. Запуск веб-интерфейса Gradio ---
366
+ print("🚀 Запускаем Gradio-приложение...")
367
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="green", secondary_hue="lime"), css=".gradio-container {background-color: #f5f5f5} th { white-space: nowrap; }") as demo:
368
+ gr.Markdown(
369
+ """<div style="text-align: center;"><img src="https://www.sberbank.com/common/img/uploaded/logo/logo_sber_main_2020_ru.svg" width="200">
370
+ <h1>Интерактивный дашборд "Горизонт PRO"</h1><p>Анализ вклада в достижение Национальных целей РФ 2030/2036</p></div>"""
371
+ )
372
+ with gr.Tabs():
373
+ with gr.TabItem("📈 Стратегический обзор региона"):
374
+ with gr.Row():
375
+ region_input_1 = gr.Dropdown(regions_list, label="Выберите регион")
376
+ year_input_1 = gr.Dropdown([2024, 2023, 2022], label="Выберите год", value=2024)
377
+ submit_button_1 = gr.Button("Сформировать стратегическую справку", variant="primary")
378
+ gr.Markdown("---")
379
+ output_report_1 = gr.Markdown(label="Аналитическая справка")
380
+
381
+ # Элементы создаются, но не отображаются в интерфейсе
382
+ # Это необходимо, чтобы функция click могла в них вернуть значения без ошибки.
383
+ output_plot_1 = gr.Plot(visible=False)
384
+ output_table_1 = gr.DataFrame(label="Проекты в портфеле", visible=False)
385
+
386
+
387
+ with gr.TabItem("🔎 Экспресс-оценка нового проекта"):
388
+ # --- ИЗМЕНЕНИЕ №1: Выносим текст примера в отдельную переменную ---
389
+ example_text = "Мусоросортировочный завод на 150 000 тонн стоимостью 3 млрд руб"
390
+
391
+ gr.Markdown("Введите описание нового инвестиционного проекта для анализа его соответствия Национальным целям и потребностям региона.")
392
+ region_input_2 = gr.Dropdown(regions_list, label=ыберите регион реализации проекта")
393
+ project_input_2 = gr.Textbox(lines=4, label="Опишите проект", placeholder=f"Пример: {example_text}")
394
+
395
+ # --- ИЗМЕНЕНИЕ №2: Добавляем две кнопки в одну строку ---
396
+ with gr.Row():
397
+ # Кнопка для вставки примера
398
+ example_button = gr.Button("✍️ Вставить пример", variant="secondary")
399
+ # Основная кнопка для анализа
400
+ submit_button_2 = gr.Button("Провести экспресс-оценку", variant="primary", scale=2) # scale=2 делает ее в 2 раза шире
401
+
402
+ gr.Markdown("---")
403
+ output_report_2 = gr.Markdown(label="Результат оценки")
404
+
405
+ # --- Привязка функций к кнопкам (остается без изменений) ---
406
+ submit_button_1.click(
407
+ fn=generate_regional_briefing,
408
+ inputs=[region_input_1, year_input_1],
409
+ outputs=[output_report_1, output_plot_1, output_table_1]
410
+ )
411
+
412
+ # --- ИЗМЕНЕНИЕ №3: Добавляем обработчик для новой кнопки ---
413
+ example_button.click(
414
+ fn=lambda: example_text, # Простая функция, которая просто возвращает текст примера
415
+ inputs=None, # У нее нет входных данных
416
+ outputs=project_input_2 # Результат екст) отправляется в наше поле для ввода
417
+ )
418
+
419
+ submit_button_2.click(
420
+ fn=analyze_new_project,
421
+ inputs=[region_input_2, project_input_2],
422
+ outputs=[output_report_2]
423
+ )
424
+
 
425
  demo.launch(share=True, debug=True)