#!/usr/bin/env python3 """ Создание Excel отчета по Чукотке (версия v1.0_with_rkn) Лист 1: Дашборд с графиками и статистикой Лист 2: Детальная таблица аудита по отелям """ import psycopg2 from psycopg2.extras import RealDictCursor from urllib.parse import unquote import pandas as pd import openpyxl from openpyxl import Workbook from openpyxl.styles import Font, Alignment, PatternFill, Border, Side, NamedStyle from openpyxl.chart import BarChart, PieChart, LineChart, Reference from openpyxl.chart.label import DataLabelList from openpyxl.utils.dataframe import dataframe_to_rows from openpyxl.drawing.image import Image from datetime import datetime import json DB_CONFIG = { 'host': '147.45.189.234', 'port': 5432, 'database': 'default_db', 'user': 'gen_user', 'password': unquote('2~~9_%5EkVsU%3F2%5CS') } def get_chukotka_data(): """Получить данные аудита Чукотки версии v1.0_with_rkn""" conn = psycopg2.connect(**DB_CONFIG, cursor_factory=RealDictCursor) cur = conn.cursor() # Получаем данные аудита Чукотки с информацией об отелях cur.execute(""" SELECT har.hotel_id, har.hotel_name, har.region_name, har.website, har.has_website, har.total_score, har.max_score, har.score_percentage, har.audit_date, har.audit_version, har.criteria_results, hm.full_name, hm.website_address, hm.owner_inn, hm.owner_ogrn, hm.addresses, hm.phone, hm.email, hm.website_status, hm.rkn_registry_status, hm.rkn_registry_number, hm.rkn_registry_date, hm.rkn_checked_at FROM hotel_audit_results har LEFT JOIN hotel_main hm ON hm.id = har.hotel_id WHERE har.region_name = 'Чукотский автономный округ' AND har.audit_version = 'v1.0_with_rkn' ORDER BY har.score_percentage DESC """) audit_data = cur.fetchall() # Статистика по критериям (анализируем criteria_results) criteria_stats = [] if audit_data: # Собираем статистику по критериям из всех отелей criteria_counts = {} total_hotels = len(audit_data) for hotel in audit_data: if hotel['criteria_results']: criteria = hotel['criteria_results'] for criterion in criteria: name = criterion.get('criterion_name', 'Неизвестно') found = criterion.get('found', False) if name not in criteria_counts: criteria_counts[name] = {'total': 0, 'found': 0} criteria_counts[name]['total'] += 1 if found: criteria_counts[name]['found'] += 1 # Преобразуем в список for name, counts in criteria_counts.items(): percentage = (counts['found'] / counts['total'] * 100) if counts['total'] > 0 else 0 criteria_stats.append({ 'criterion_name': name, 'total_checks': counts['total'], 'found_count': counts['found'], 'percentage': percentage }) # Сортируем по проценту выполнения criteria_stats.sort(key=lambda x: x['percentage'], reverse=True) cur.close() conn.close() return audit_data, criteria_stats def create_dashboard_sheet(workbook, audit_data, criteria_stats): """Создать лист дашборда""" ws = workbook.active ws.title = "📊 Дашборд Чукотка" # Стили header_font = Font(name='Arial', size=14, bold=True, color='FFFFFF') header_fill = PatternFill(start_color='366092', end_color='366092', fill_type='solid') subheader_font = Font(name='Arial', size=12, bold=True) normal_font = Font(name='Arial', size=10) # Заголовок ws['A1'] = "🏔️ ДАШБОРД АУДИТА ОТЕЛЕЙ ЧУКОТКИ" ws['A1'].font = Font(name='Arial', size=16, bold=True, color='366092') ws['A1'].alignment = Alignment(horizontal='center') ws.merge_cells('A1:H1') # Общая статистика ws['A3'] = "📈 ОБЩАЯ СТАТИСТИКА" ws['A3'].font = subheader_font ws['A3'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid') # Подсчитываем статистику total_hotels = len(audit_data) total_with_website = sum(1 for h in audit_data if h['has_website']) total_compliant = sum(1 for h in audit_data if h['score_percentage'] >= 50) avg_score = sum(h['score_percentage'] for h in audit_data) / total_hotels if total_hotels > 0 else 0 ws['A4'] = f"Всего отелей проаудировано: {total_hotels}" ws['A5'] = f"Отелей с сайтами: {total_with_website} ({total_with_website/total_hotels*100:.1f}%)" ws['A6'] = f"Соответствующих требованиям (≥50%): {total_compliant} ({total_compliant/total_hotels*100:.1f}%)" ws['A7'] = f"Средний балл соответствия: {avg_score:.1f}%" ws['A8'] = f"Версия аудита: v1.0_with_rkn (с РКН проверкой)" for cell in ['A4', 'A5', 'A6', 'A7', 'A8']: ws[cell].font = normal_font # Статистика по отелям ws['A10'] = "🏨 РЕЗУЛЬТАТЫ ПО ОТЕЛЯМ" ws['A10'].font = subheader_font ws['A10'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid') # Заголовки таблицы отелей headers = ['Отель', 'Сайт', 'Балл', 'Процент', 'Статус', 'Дата аудита'] for i, header in enumerate(headers, 1): cell = ws.cell(row=11, column=i, value=header) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Данные по отелям for i, hotel in enumerate(audit_data, 12): status = '✅ Соответствует' if hotel['score_percentage'] >= 50 else '⚠️ Частично' if hotel['score_percentage'] >= 30 else '❌ Не соответствует' ws.cell(row=i, column=1, value=hotel['hotel_name']) ws.cell(row=i, column=2, value=hotel['website']) ws.cell(row=i, column=3, value=f"{hotel['total_score']}/{hotel['max_score']}") ws.cell(row=i, column=4, value=f"{hotel['score_percentage']:.1f}%") ws.cell(row=i, column=5, value=status) ws.cell(row=i, column=6, value=hotel['audit_date'].strftime('%d.%m.%Y %H:%M')) # Цветовое кодирование процента percentage = hotel['score_percentage'] if percentage >= 50: fill_color = 'C6EFCE' # Зеленый elif percentage >= 30: fill_color = 'FFEB9C' # Желтый else: fill_color = 'FFC7CE' # Красный ws.cell(row=i, column=4).fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type='solid') # Форматирование for col in range(1, 7): ws.cell(row=i, column=col).font = normal_font ws.cell(row=i, column=col).alignment = Alignment(horizontal='center') # График по отелям chart1 = BarChart() chart1.title = "Баллы соответствия отелей Чукотки" chart1.x_axis.title = "Отели" chart1.y_axis.title = "Процент соответствия" data = Reference(ws, min_col=4, min_row=11, max_row=11+len(audit_data), max_col=4) cats = Reference(ws, min_col=1, min_row=12, max_row=11+len(audit_data)) chart1.add_data(data, titles_from_data=False) chart1.set_categories(cats) chart1.height = 10 chart1.width = 15 ws.add_chart(chart1, "A15") # Статистика по критериям if criteria_stats: ws['A30'] = "🎯 ВЫПОЛНЕНИЕ КРИТЕРИЕВ" ws['A30'].font = subheader_font ws['A30'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid') # Заголовки таблицы критериев criteria_headers = ['Критерий', 'Проверено', 'Найдено', 'Процент выполнения'] for i, header in enumerate(criteria_headers, 1): cell = ws.cell(row=31, column=i, value=header) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Данные по критериям for i, criterion in enumerate(criteria_stats, 32): ws.cell(row=i, column=1, value=criterion['criterion_name']) ws.cell(row=i, column=2, value=criterion['total_checks']) ws.cell(row=i, column=3, value=criterion['found_count']) ws.cell(row=i, column=4, value=f"{criterion['percentage']:.1f}%") # Цветовое кодирование процента percentage = criterion['percentage'] if percentage >= 70: fill_color = 'C6EFCE' # Зеленый elif percentage >= 40: fill_color = 'FFEB9C' # Желтый else: fill_color = 'FFC7CE' # Красный ws.cell(row=i, column=4).fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type='solid') # Форматирование for col in range(1, 5): ws.cell(row=i, column=col).font = normal_font ws.cell(row=i, column=col).alignment = Alignment(horizontal='center') # График по критериям chart2 = BarChart() chart2.title = "Выполнение критериев (%)" chart2.x_axis.title = "Критерии" chart2.y_axis.title = "Процент выполнения" data2 = Reference(ws, min_col=4, min_row=31, max_row=31+len(criteria_stats), max_col=4) cats2 = Reference(ws, min_col=1, min_row=32, max_row=31+len(criteria_stats)) chart2.add_data(data2, titles_from_data=False) chart2.set_categories(cats2) chart2.height = 10 chart2.width = 20 ws.add_chart(chart2, "F30") # Информация о дате создания ws['A50'] = f"📅 Отчет создан: {datetime.now().strftime('%d.%m.%Y %H:%M')}" ws['A50'].font = Font(name='Arial', size=10, italic=True, color='666666') # Настройка ширины колонок column_widths = [30, 25, 10, 12, 20, 15] for i, width in enumerate(column_widths, 1): ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = width def create_audit_sheet(workbook, audit_data): """Создать лист детального аудита""" ws = workbook.create_sheet("🏨 Детальный аудит") # Стили header_font = Font(name='Arial', size=12, bold=True, color='FFFFFF') header_fill = PatternFill(start_color='366092', end_color='366092', fill_type='solid') normal_font = Font(name='Arial', size=9) # Заголовки headers = [ 'Отель', 'Сайт', 'Есть сайт', 'Балл', 'Процент', 'ИНН', 'ОГРН', 'Адрес', 'Телефон', 'Email', 'Статус сайта', 'РКН статус', 'Дата аудита' ] for i, header in enumerate(headers, 1): cell = ws.cell(row=1, column=i, value=header) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') # Данные for i, hotel in enumerate(audit_data, 2): ws.cell(row=i, column=1, value=hotel['hotel_name'] or hotel['full_name']) ws.cell(row=i, column=2, value=hotel['website'] or hotel['website_address']) ws.cell(row=i, column=3, value='Да' if hotel['has_website'] else 'Нет') ws.cell(row=i, column=4, value=f"{hotel['total_score']}/{hotel['max_score']}") ws.cell(row=i, column=5, value=f"{hotel['score_percentage']:.1f}%") ws.cell(row=i, column=6, value=hotel['owner_inn']) ws.cell(row=i, column=7, value=hotel['owner_ogrn']) ws.cell(row=i, column=8, value=str(hotel['addresses']) if hotel['addresses'] else '') ws.cell(row=i, column=9, value=hotel['phone']) ws.cell(row=i, column=10, value=hotel['email']) ws.cell(row=i, column=11, value=hotel['website_status']) ws.cell(row=i, column=12, value=hotel['rkn_registry_status']) ws.cell(row=i, column=13, value=hotel['audit_date'].strftime('%d.%m.%Y %H:%M') if hotel['audit_date'] else '') # Цветовое кодирование процента percentage = hotel['score_percentage'] or 0 if percentage >= 50: fill_color = 'C6EFCE' # Зеленый elif percentage >= 30: fill_color = 'FFEB9C' # Желтый else: fill_color = 'FFC7CE' # Красный ws.cell(row=i, column=5).fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type='solid') # Форматирование for col in range(1, 14): ws.cell(row=i, column=col).font = normal_font ws.cell(row=i, column=col).alignment = Alignment(horizontal='center') # Настройка ширины колонок column_widths = [30, 25, 10, 10, 10, 15, 15, 30, 15, 20, 15, 15, 15] for i, width in enumerate(column_widths, 1): ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = width # Фильтры ws.auto_filter.ref = f"A1:{openpyxl.utils.get_column_letter(len(headers))}{len(audit_data)+1}" # Заморозка заголовков ws.freeze_panes = "A2" def create_criteria_sheet(workbook, audit_data): """Создать лист с детальными результатами по критериям""" ws = workbook.create_sheet("🎯 Критерии") # Стили header_font = Font(name='Arial', size=10, bold=True, color='FFFFFF') header_fill = PatternFill(start_color='366092', end_color='366092', fill_type='solid') normal_font = Font(name='Arial', size=8) # Заголовки headers = ['Отель', 'Критерий', 'Найдено', 'Балл', 'Уверенность', 'URL', 'Цитата'] for i, header in enumerate(headers, 1): cell = ws.cell(row=1, column=i, value=header) cell.font = header_font cell.fill = header_fill cell.alignment = Alignment(horizontal='center') row = 2 for hotel in audit_data: if hotel['criteria_results']: criteria = hotel['criteria_results'] for criterion in criteria: ws.cell(row=row, column=1, value=hotel['hotel_name']) ws.cell(row=row, column=2, value=criterion.get('criterion_name', '')) ws.cell(row=row, column=3, value='Да' if criterion.get('found', False) else 'Нет') ws.cell(row=row, column=4, value=criterion.get('score', 0)) ws.cell(row=row, column=5, value=criterion.get('final_confidence', '')) ws.cell(row=row, column=6, value=criterion.get('ai_agent', {}).get('url', '')) ws.cell(row=row, column=7, value=criterion.get('ai_agent', {}).get('quote', '')[:100] + '...' if criterion.get('ai_agent', {}).get('quote', '') else '') # Цветовое кодирование if criterion.get('found', False): fill_color = 'C6EFCE' # Зеленый else: fill_color = 'FFC7CE' # Красный ws.cell(row=row, column=3).fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type='solid') # Форматирование for col in range(1, 8): ws.cell(row=row, column=col).font = normal_font ws.cell(row=row, column=col).alignment = Alignment(horizontal='center') row += 1 # Настройка ширины колонок column_widths = [25, 30, 8, 8, 12, 25, 50] for i, width in enumerate(column_widths, 1): ws.column_dimensions[openpyxl.utils.get_column_letter(i)].width = width # Фильтры ws.auto_filter.ref = f"A1:{openpyxl.utils.get_column_letter(len(headers))}{row-1}" # Заморозка заголовков ws.freeze_panes = "A2" def main(): """Основная функция""" print("🏔️ СОЗДАНИЕ ОТЧЕТА ПО ЧУКОТКЕ") print("=" * 50) # Получаем данные print("📊 Загружаем данные из БД...") audit_data, criteria_stats = get_chukotka_data() print(f"✅ Загружено:") print(f" 🏨 Отелей: {len(audit_data)}") print(f" 🎯 Критериев: {len(criteria_stats)}") # Создаем Excel файл print("\n📝 Создаем Excel файл...") workbook = Workbook() # Лист дашборда print("📊 Создаем дашборд...") create_dashboard_sheet(workbook, audit_data, criteria_stats) # Лист аудита print("🏨 Создаем таблицу аудита...") create_audit_sheet(workbook, audit_data) # Лист критериев print("🎯 Создаем детальные критерии...") create_criteria_sheet(workbook, audit_data) # Сохраняем файл timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") filename = f"chukotka_audit_report_{timestamp}.xlsx" workbook.save(filename) print(f"\n✅ Отчет сохранен: {filename}") print(f"📊 Листы:") print(f" 📈 Дашборд Чукотка - графики и статистика") print(f" 🏨 Детальный аудит - таблица отелей") print(f" 🎯 Критерии - детальные результаты по критериям") if __name__ == "__main__": main()