import os import requests import json import gradio as gr import pandas as pd import matplotlib.pyplot as plt import pypdf import warnings from langchain_google_genai import ChatGoogleGenerativeAI warnings.filterwarnings("ignore", category=UserWarning) # --- 1. Настройка констант, API и LLM --- print("--- Настройка API и LLM ---") # Определяем константу здесь, чтобы она была доступна всегда API_BASE_URL = "http://194.113.209.48:8000" llm = None # Инициализируем llm как None на случай ошибки try: # Ключ считывается из "секретов", которые вы задали в настройках Space GEMINI_API_KEY = os.environ.get('GEMINI_API_KEY') if GEMINI_API_KEY: llm = ChatGoogleGenerativeAI(model="gemini-1.5-pro-latest", google_api_key=GEMINI_API_KEY, temperature=0.1) print("✅ LLM настроена.") else: print("❌ ОШИБКА: Секрет 'GEMINI_API_KEY' не найден. Убедитесь, что он добавлен в настройках Space.") except Exception as e: print(f"❌ Произошла ошибка при настройке LLM: {e}") # --- 3. Загрузка данных и контекста --- # 3.1. Загружаем заранее проанализированный портфель из CSV print("--- Загрузка данных портфеля ---") try: tagged_portfolio_df = pd.read_csv('sber_portfolio_analyzed5.csv') tagged_portfolio_df['region_name'] = tagged_portfolio_df['region_name'].str.strip() print(f"✅ Успешно загружен проанализированный портфель из {len(tagged_portfolio_df)} проектов.") except FileNotFoundError: print("❌ Ошибка: Файл 'sber_portfolio_analyzed5.csv' не найден. Возможно, нужно сначала запустить ячейку для генерации этого файла.") tagged_portfolio_df = pd.DataFrame() # 3.2. Извлекаем текст из PDF-отчета print("--- Извлечение текста из PDF-отчета ---") full_report_text = "" try: # Убедитесь, что имя файла верное и он загружен with open('f356d44202ac9361579f509ebf965950.pdf', 'rb') as f: reader = pypdf.PdfReader(f) for page in reader.pages: full_report_text += page.extract_text() + "\n" print("✅ Текст из PDF-файла успешно извлечен.") except Exception as e: print(f"❌ Ошибка при чтении PDF-файла: {e}") # 3.3. Загружаем справочники print("--- Загрузка справочников ---") try: regions_response = requests.get(f"{API_BASE_URL}/api/regions") regions_data = regions_response.json() regions_list = sorted([item['name'] for item in regions_data]) print("✅ Справочник регионов загружен.") except Exception as e: print(f"❌ Не удалось загрузить справочник регионов: {e}") regions_list = sorted(list(tagged_portfolio_df['region_name'].unique())) # 3.4. Определяем константы (структура Наццелей) - ПОЛНАЯ ВЕРСИЯ NATIONAL_GOALS_STRUCTURE = { "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи": [ "2.а) повышение суммарного коэффициента рождаемости до 1,6 к 2030 году и до 1,8 к 2036 году", "2.б) увеличение ожидаемой продолжительности жизни до 78 лет к 2030 году и до 81 года к 2036 году", "2.в) обеспечение не ниже среднероссийских темпов повышения к 2030 году суммарного коэффициента рождаемости в отстающих субъектах РФ", "2.г) снижение к 2036 году дифференциации показателей ожидаемой продолжительности жизни не менее чем на 25%", "2.д) снижение к 2030 году суммарной продолжительности временной нетрудоспособности граждан", "2.е) повышение к 2030 году уровня удовлетворенности граждан условиями для занятий физкультурой и спортом", "2.ж) увеличение к 2030 году численности граждан, получающих услуги долговременного ухода, до 500 тыс. человек", "2.з) повышение к 2030 году уровня удовлетворенности участников СВО условиями для медицинской реабилитации и трудоустройства", "2.и) создание и запуск к 2030 году цифровой платформы по управлению здоровьем человека", "2.к) снижение уровня бедности ниже 7% к 2030 году и ниже 5% к 2036 году, в т.ч. среди многодетных семей", "2.л) снижение коэффициента Джини до 0,37 к 2030 году и до 0,33 к 2036 году", "2.м) рост МРОТ к 2030 году более чем в два раза, не менее 35 тыс. рублей в месяц", "2.н) утверждение в 2026 году новых систем оплаты труда работников бюджетной сферы" ], "Реализация потенциала каждого человека, развитие его талантов, воспитание патриотичной и социально ответственной личности": [ "3.а) создание к 2030 году условий для воспитания гармонично развитой личности на основе традиционных ценностей", "3.б) увеличение к 2030 году численности иностранных студентов до 500 тыс. человек", "3.в) увеличение к 2030 году доли молодых людей, участвующих в проектах развития и воспитания, до 75%", "3.г) увеличение к 2030 году доли молодых людей, верящих в самореализацию в России, до 85%", "3.д) увеличение к 2030 году доли молодых людей в добровольческой деятельности до 45%", "3.е) обеспечение к 2030 году функционирования эффективной системы выявления талантов у 100% обучающихся", "3.ж) обеспечение продвижения традиционных ценностей в не менее чем 70% проектов в сфере культуры к 2030 году", "3.з) повышение к 2030 году удовлетворенности граждан работой организаций культуры", "3.и) формирование к 2030 году современной системы профразвития педагогических работников" ], "Комфортная и безопасная среда для жизни": [ "4.а) улучшение качества среды для жизни в опорных населенных пунктах на 30% к 2030 году", "4.б) обеспечение граждан жильем общей площадью не менее 33 кв. метров на человека к 2030 году", "4.в) обновление к 2030 году жилищного фонда не менее чем на 20% по сравнению с 2019 годом", "4.г) устойчивое сокращение непригодного для проживания жилищного фонда", "4.д) повышение доступности жилья на первичном рынке", "4.е) благоустройство не менее чем 30 тыс. общественных территорий к 2030 году", "4.ж) реализация программы модернизации коммунальной инфраструктуры для 20 млн. человек к 2030 году", "4.з) строительство и реконструкция не менее чем 2 тыс. объектов питьевого водоснабжения к 2030 году", "4.и) рост энергоэффективности в ЖКХ и строительстве", "4.к) обновление парка общественного транспорта до 85% от норматива к 2030 году", "4.л) приведение в нормативное состояние 85% дорог агломераций и 60% региональных дорог к 2030 году", "4.м) снижение смертности в ДТП в 1.5 раза к 2030 году", "4.н) увеличение авиационной подвижности населения на 50% к 2030 году", "4.о) капитальный ремонт всех нуждающихся зданий школ и детсадов до конца 2030 года", "4.п) подключение к сетевому газу не менее 1,6 млн домовладений к 2030 году", "4.р) оснащение 900 центров кинопоказа в малых населенных пунктах к 2030 году" ], "Экологическое благополучие": [ "5.а) формирование экономики замкнутого цикла: 100% сортировка ТКО, захоронение не более 50%, вовлечение 25% отходов в оборот к 2030 году", "5.б) снижение в два раза выбросов опасных загрязняющих веществ в городах с высоким уровнем загрязнения к 2036 году", "5.в) ликвидация до конца 2030 года не менее 50 опасных объектов накопленного вреда", "5.г) снижение к 2036 году в два раза объема неочищенных сточных вод", "5.д) сохранение лесов, биоразнообразия и создание условий для экологического туризма" ], "Устойчивая и динамичная экономика": [ "6.а) темп роста ВВП выше среднемирового, 4-е место в мире по ВВП по ППС к 2030 году", "6.б) снижение доли импорта до 17% ВВП к 2030 году", "6.в) увеличение объема инвестиций в основной капитал на 60% к 2030 году (относительно 2020)", "6.г) рост доходов населения и пенсий не ниже уровня инфляции", "6.д) реальный рост дохода на работника МСП в 1,2 раза выше роста ВВП", "6.ж) вхождение в топ-25 стран мира по плотности роботизации к 2030 году", "6.н) увеличение доли туристской отрасли в ВВП до 5% к 2030 году", "6.о) прирост экспорта несырьевых неэнергетических товаров на две трети к 2030 году", "6.п) увеличение производства продукции АПК на 25% к 2030 году (относительно 2021)", "6.р) увеличение экспорта продукции АПК в 1.5 раза к 2030 году (относительно 2021)" ], "Технологическое лидерство": [ "7.а) обеспечение технологической независимости (биоэкономика, БАС, ИИ, новые материалы и др.)", "7.б) увеличение индекса производства в обрабатывающей промышленности на 40% к 2030 году (относительно 2022)", "7.в) вхождение в топ-10 стран мира по объему научных исследований к 2030 году", "7.г) увеличение внутренних затрат на исследования до 2% ВВП к 2030 году", "7.д) увеличение доли отечественных высокотехнологичных товаров в 1.5 раза к 2030 году", "7.е) увеличение выручки малых технологических компаний в 7 раз к 2030 году" ], "Цифровая трансформация": [ "8.а) достижение 'цифровой зрелости' госуправления и ключевых отраслей экономики к 2030 году", "8.б) формирование рынка данных", "8.в) доступ к высокоскоростному интернету для 97% домохозяйств к 2030 году", "8.г) рост инвестиций в отечественные ИТ-решения вдвое выше темпа роста ВВП", "8.д) переход 80% организаций на российское ПО к 2030 году" ] } print("✅ Словарь НАЦЦЕЛЕЙ успешно загружен.") # --- 4. Определение функций приложения --- print("--- Определение функций приложения ---") # (Здесь идут полные определения функций generate_regional_briefing, _find_relevant_goals, analyze_new_project) # --- ФИНАЛЬНАЯ ВЕРСИЯ С ТАБЛИЧНЫМ ПРЕДСТАВЛЕНИЕМ АНАЛИЗА --- def generate_regional_briefing(region_name, year): print("---") print(f"НОВЫЙ ЗАПРОС [Стратегический обзор]:") print(f"Регион: {region_name}, Год: {year}") print("---") if not region_name or not year: yield "Пожалуйста, выберите регион и год.", None, None return if not 'full_report_text' in globals() or not full_report_text: yield "Ошибка: Текст отчета ESG-индекса не был загружен.", None, None return yield "

⏳ Готовлю финальный отчет, это может занять до 30 секунд...

", None, None try: region_id = next((item['id'] for item in regions_data if item['name'] == region_name), None) params = {"year": int(year)} response = requests.get(f"{API_BASE_URL}/api/region-performance/{region_id}/all", params=params) response.raise_for_status() urfu_data_raw = response.json() 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} except Exception as e: yield f"Не удалось получить данные от API УрФУ. Ошибка: {e}", None, None return region_portfolio = tagged_portfolio_df[tagged_portfolio_df['region_name'] == region_name] portfolio_summary = "В данном регионе у Сбера отсутствуют проекты из анализируемого портфеля." fig = None if not region_portfolio.empty: total_investment = region_portfolio['amount_bln_rub'].sum() project_count = len(region_portfolio) SUMMARIZATION_THRESHOLD = 5 if 'mapped_goal' in region_portfolio.columns and not region_portfolio['mapped_goal'].isnull().all(): if project_count > SUMMARIZATION_THRESHOLD: summary_parts = [] goal_groups = region_portfolio.groupby('mapped_goal')['amount_bln_rub'].sum().sort_values(ascending=False).reset_index() for _, row in goal_groups.iterrows(): goal_name = row['mapped_goal'] goal_sum = row['amount_bln_rub'] project_examples = region_portfolio[region_portfolio['mapped_goal'] == goal_name]['project_goal'].head(2).tolist() examples_text = ", ".join(project_examples) # ИЗМЕНЕНИЕ: Формируем HTML-элемент списка
  • и используем для жирности summary_parts.append( f"
  • {goal_name} ({goal_sum:.1f} млрд руб.), ключевые проекты: {examples_text}
  • " ) # ИЗМЕНЕНИЕ: Собираем полный HTML-список summary_text = "" portfolio_summary = ( f"Портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. и включает проекты, распределенные по следующим национальным целям:{summary_text}" ) else: projects_list = [ f"'{row['project_goal']}' ({row['amount_bln_rub']:.1f} млрд руб.), направленный на достижение цели \"{row.get('mapped_goal', 'не определена')}\"" for _, row in region_portfolio.iterrows() ] projects_text = " и ".join(projects_list) portfolio_summary = ( f"Текущий портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. " f"и включает следующие ключевые проекты: {projects_text}." ) else: projects_list = [ f"'{row['project_goal']}' ({row['amount_bln_rub']:.1f} млрд руб.)" for _, row in region_portfolio.iterrows() ] projects_text = ", ".join(projects_list) portfolio_summary = ( f"Текущий портфель Сбера в регионе составляет {total_investment:.1f} млрд руб. и включает следующие проекты: {projects_text}." ) if 'mapped_goal' in region_portfolio.columns and not region_portfolio['mapped_goal'].isnull().all(): goal_distribution = region_portfolio.groupby('mapped_goal')['amount_bln_rub'].sum() plot_data = goal_distribution.reset_index() if plot_data is not None and not plot_data.empty: 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) plt.xticks(rotation=45, ha="right", fontsize=9); plt.title(f"Распределение портфеля Сбера по Наццелям\nв регионе: {region_name}", fontsize=12) plt.ylabel("Сумма, млрд руб.", fontsize=10); plt.grid(axis='y', linestyle='--', alpha=0.7); plt.tight_layout() prompt = f""" Твоя роль: Ведущий аналитик-стратег ESG-дирекции Сбера. Твоя задача: Подготовить исчерпывающий и СТРУКТУРИРОВАННЫЙ аналитический отчет по региону, синтезируя информацию из предоставленных источников. ### ИСТОЧНИК 1: Статистические показатели по региону (от УрФУ) Регион анализа: {region_name} {json.dumps(urfu_data_simplified, ensure_ascii=False, indent=2)} ### ИСТОЧНИК 2: Полный текст отчета "ESG-индекс городов и регионов" (Сбер, ВЭБ.РФ) ```text {full_report_text} ``` ### ИСТОЧНИК 3: Данные по проектам в регионе {portfolio_summary} ### ЗАДАЧА И СТРОГИЕ ПРАВИЛА: 1. Подготовь отчет в формате Markdown. 2. Сфокусируй детальный анализ ТОЛЬКО на двух Национальных целях: "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи" и "Комфортная и безопасная среда для жизни". 3. Относись ко всем данным как к реальным, не добавляй дисклеймеров. 4. Для раздела "Детальный анализ" **сгенерируй Markdown-таблицы**, как показано в шаблоне. --- ### Аналитический отчет: {region_name}, {year} год **1. Обзор проектов в регионе** * На основе ИСТОЧНИКА 3, опиши состав и объем проектов. Начинай с новой строки каждую новую Наццель и по ней проекты. * **Задача для следующей строки:** На основе ИСТОЧНИКА 2, найди конкретные факты о регионе `{region_name}` (место в рейтинге, баллы, и т.д.). * **Требование к форматированию:** Начни строку с "ESG-индекс городов и регионов Сбер - ВЭБ.РФ:". Сразу после этого, обычным черным текстом, продолжи своим аналитическим выводом. **2. Детальный анализ по приоритетным Национальным целям** * Для каждой из двух приоритетных Наццелей, создай подзаголовок и Markdown-таблицу по следующему образцу. Заполни 2-3 строки для каждой таблицы наиболее показательными данными. **Национальная цель: "Сохранение населения, укрепление здоровья и повышение благополучия людей, поддержка семьи"** | Показатель (данные УрФУ) | Значение в регионе | Влияние проектов Сбера | | :--- | :--- | :--- | | *[название показателя из ИСТОЧНИКА 1]* | *[значение из ИСТОЧНИКА 1]* | *[твой анализ ИСТОЧНИКА 3]* | | *[название второго показателя]* | *[его значение]* | *[твой анализ]* | **Национальная цель: "Комфортная и безопасная среда для жизни"** | Показатель (данные УрФУ) | Значение в регионе | Влияние проектов Сбера | | :--- | :--- | :--- | | *[название показателя из ИСТОЧНИКА 1]* | *[значение из ИСТОЧНИКА 1]* | *[твой анализ ИСТОЧНИКА 3]* | | *[название второго показателя]* | *[его значение]* | *[твой анализ]* | **3. Стратегические рекомендации** * На основе табличного анализа, сформулируй 2-3 ключевых вывода и предложи конкретные типы проектов для инвестиций. """ final_report = llm.invoke(prompt).content yield final_report, fig, region_portfolio.drop(columns=['project_goal'], errors='ignore') def _find_relevant_goals(project_description: str) -> list: top_level_goals = list(NATIONAL_GOALS_STRUCTURE.keys()) prompt = f""" Определи 1-3 наиболее релевантных национальных цели из списка для проекта. Верни ТОЛЬКО JSON-массив строк. СПИСОК НАЦЦЕЛЕЙ: {json.dumps(top_level_goals, ensure_ascii=False)} ОПИСАНИЕ ПРОЕКТА: "{project_description}" Твой JSON-ответ: """ try: response = llm.invoke(prompt) return json.loads(response.content.strip().replace("```json", "").replace("```", "")) except Exception: return top_level_goals def analyze_new_project(region_name, project_description): print("---") print(f"НОВЫЙ ЗАПРОС [Экспресс-оценка]:") print(f"Регион: {region_name}") print(f"Описание проекта: {project_description}") print("---") if not all([region_name, project_description]): yield "Пожалуйста, выберите регион и опишите проект." return yield "

    ⏳ Этап 1/2: Определяю релевантные Наццели...

    " relevant_goals_names = _find_relevant_goals(project_description) relevant_goals_structure = {goal: NATIONAL_GOALS_STRUCTURE.get(goal, []) for goal in relevant_goals_names} yield "

    ⏳ Этап 2/2: Готовлю детальный анализ, это может занять до 30 секунд...

    " try: region_id = next((item['id'] for item in regions_data if item['name'] == region_name), None) params = {"year": 2024} # Можно использовать актуальный год response = requests.get(f"{API_BASE_URL}/api/region-performance/{region_id}/all", params=params) response.raise_for_status() urfu_data_raw = response.json() 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} except Exception as e: yield f"Не удалось получить данные от API УрФУ. Ошибка: {e}" return goals_structure_str = json.dumps(relevant_goals_structure, ensure_ascii=False, indent=2) # --- НАЧАЛО НОВОГО, РАСШИРЕННОГО ПРОМПТА --- prompt = f""" Твоя роль: Ведущий аналитик ESG-дирекции Сбера, обладающий глубокими знаниями в области национальных целей развития РФ. Твоя задача: Подготовить ДЕТАЛЬНЫЙ и СТРУКТУРИРОВАННЫЙ аналитический отчет по новому инвестиционному проекту. Отчет должен быть выполнен в формате Markdown и содержать глубокий анализ, а не поверхностное описание. ### ВХОДНЫЕ ДАННЫЕ: 1. **Регион реализации:** {region_name} 2. **Описание проекта:** "{project_description}" 3. **Статистический контекст по региону (данные УрФУ):** {json.dumps(urfu_data_simplified, ensure_ascii=False, indent=2)} 4. **Структура релевантных Национальных целей и их задач:** {goals_structure_str} ### СТРОГИЕ ТРЕБОВАНИЯ К СТРУКТУРЕ И СОДЕРЖАНИЮ ОТЧЕТА: **1. Краткое резюме проекта** * Опиши суть проекта, его масштаб и основные цели. **2. Анализ соответствия Национальным целям** * Для КАЖДОЙ релевантной наццели из предоставленного списка: * **Обоснуй, почему проект соответствует данной цели.** Четко свяжи деятельность по проекту с конкретными задачами (подпунктами) этой наццели. * **Используй данные из статистического контекста (ВХОДНЫЕ ДАННЫЕ №3)**, чтобы показать АКТУАЛЬНОСТЬ проекта для региона. Например, если проект влияет на здравоохранение, приведи текущие показатели по ожидаемой продолжительности жизни или заболеваемости в регионе, чтобы подчеркнуть важность инвестиций. **3. Оценка по ESG-критериям** * **E (Environmental / Экология):** Оцени потенциальное положительное и отрицательное воздействие проекта на окружающую среду. Какие экологические риски и возможности он несет? (например, снижение выбросов, образование отходов, использование "зеленых" технологий). * **S (Social / Социальная сфера):** Проанализируй социальный эффект. Создание рабочих мест (сколько, каких?), влияние на качество жизни, доступность услуг, поддержка местных сообществ. * **G (Governance / Управление):** Опиши потенциальные управленческие аспекты. Необходимость взаимодействия с региональными властями, прозрачность реализации, работа с заинтересованными сторонами. **4. Предварительные риски и рекомендации** * На основе всего анализа, выдели 2-3 ключевых риска (технологических, социальных, регуляторных), на которые стоит обратить внимание. * Дай 1-2 стратегические рекомендации по усилению положительного эффекта от проекта. **5. Итоговое заключение** * Сделай общий вывод о стратегической привлекательности проекта для банка с точки зрения вклада в устойчивое развитие и национальные приоритеты. --- **Начинай твой отчет.** """ # --- КОНЕЦ НОВОГО ПРОМПТА --- analysis_report = llm.invoke(prompt).content yield analysis_report # --- 5. Запуск веб-интерфейса Gradio --- print("🚀 Запускаем Gradio-приложение...") 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: gr.Markdown( """

    Интерактивный дашборд "Горизонт PRO"

    Анализ вклада в достижение Национальных целей РФ 2030/2036

    """ ) with gr.Tabs(): with gr.TabItem("📈 Стратегический обзор региона"): with gr.Row(): region_input_1 = gr.Dropdown(regions_list, label="Выберите регион") year_input_1 = gr.Dropdown([2025, 2024, 2023, 2022], label="Выберите год", value=2024) submit_button_1 = gr.Button("Сформировать стратегическую справку", variant="primary") gr.Markdown("---") output_report_1 = gr.Markdown(label="Аналитическая справка") # Элементы создаются, но не отображаются в интерфейсе # Это необходимо, чтобы функция click могла в них вернуть значения без ошибки. output_plot_1 = gr.Plot(visible=False) output_table_1 = gr.DataFrame(label="Проекты в портфеле", visible=False) with gr.TabItem("🔎 Экспресс-оценка нового проекта"): # --- ИЗМЕНЕНИЕ №1: Выносим текст примера в отдельную переменную --- example_text = "Мусоросортировочный завод на 150 000 тонн стоимостью 3 млрд руб" gr.Markdown("Введите описание нового инвестиционного проекта для анализа его соответствия Национальным целям и потребностям региона.") region_input_2 = gr.Dropdown(regions_list, label="Выберите регион реализации проекта") project_input_2 = gr.Textbox(lines=4, label="Опишите проект", placeholder=f"Пример: {example_text}") # --- ИЗМЕНЕНИЕ №2: Добавляем две кнопки в одну строку --- with gr.Row(): # Кнопка для вставки примера example_button = gr.Button("✍️ Вставить пример", variant="secondary") # Основная кнопка для анализа submit_button_2 = gr.Button("Провести экспресс-оценку", variant="primary", scale=2) # scale=2 делает ее в 2 раза шире gr.Markdown("---") output_report_2 = gr.Markdown(label="Результат оценки") # --- Привязка функций к кнопкам (остается без изменений) --- submit_button_1.click( fn=generate_regional_briefing, inputs=[region_input_1, year_input_1], outputs=[output_report_1, output_plot_1, output_table_1] ) # --- ИЗМЕНЕНИЕ №3: Добавляем обработчик для новой кнопки --- example_button.click( fn=lambda: example_text, # Простая функция, которая просто возвращает текст примера inputs=None, # У нее нет входных данных outputs=project_input_2 # Результат (текст) отправляется в наше поле для ввода ) submit_button_2.click( fn=analyze_new_project, inputs=[region_input_2, project_input_2], outputs=[output_report_2] ) demo.launch(share=True, debug=True)