- Краулеры: smart_crawler.py, regional_crawler.py - Аудит: audit_orel_to_excel.py, audit_chukotka_to_excel.py - РКН проверка: check_rkn_registry.py, recheck_unclear_rkn.py - Отчёты: create_orel_horizontal_report.py - Обработка: process_all_hotels_embeddings.py - Документация: README.md, DB_SCHEMA_REFERENCE.md
277 lines
11 KiB
Python
277 lines
11 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
Создание Excel дашборда с аудитом отелей
|
||
Лист 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_audit_data():
|
||
"""Получить данные аудита из БД"""
|
||
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
|
||
FROM hotel_audit_results har
|
||
LEFT JOIN hotel_main hm ON hm.id = har.hotel_id
|
||
ORDER BY har.audit_date DESC
|
||
""")
|
||
|
||
audit_data = cur.fetchall()
|
||
|
||
# Статистика по регионам
|
||
cur.execute("""
|
||
SELECT
|
||
region_name,
|
||
COUNT(*) as total_hotels,
|
||
AVG(score_percentage) as avg_score,
|
||
MAX(audit_date) as last_audit,
|
||
COUNT(CASE WHEN has_website = true THEN 1 END) as with_website,
|
||
COUNT(CASE WHEN score_percentage >= 50 THEN 1 END) as compliant_hotels
|
||
FROM hotel_audit_results
|
||
WHERE region_name IS NOT NULL
|
||
GROUP BY region_name
|
||
ORDER BY total_hotels DESC
|
||
""")
|
||
|
||
region_stats = cur.fetchall()
|
||
|
||
# Статистика по критериям (пропускаем пока - сложная структура JSONB)
|
||
criteria_stats = []
|
||
|
||
cur.close()
|
||
conn.close()
|
||
|
||
return audit_data, region_stats, criteria_stats
|
||
|
||
def create_dashboard_sheet(workbook, region_stats, 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 = sum(row['total_hotels'] for row in region_stats)
|
||
total_with_website = sum(row['with_website'] for row in region_stats)
|
||
total_compliant = sum(row['compliant_hotels'] for row in region_stats)
|
||
avg_score = sum(row['avg_score'] * row['total_hotels'] for row in region_stats) / 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}%"
|
||
|
||
for cell in ['A4', 'A5', 'A6', 'A7']:
|
||
ws[cell].font = normal_font
|
||
|
||
# Статистика по регионам
|
||
ws['A9'] = "📍 СТАТИСТИКА ПО РЕГИОНАМ"
|
||
ws['A9'].font = subheader_font
|
||
ws['A9'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid')
|
||
|
||
# Заголовки таблицы регионов
|
||
headers = ['Регион', 'Отелей', 'С сайтами', 'Соответствуют', 'Средний балл', 'Последний аудит']
|
||
for i, header in enumerate(headers, 1):
|
||
cell = ws.cell(row=10, column=i, value=header)
|
||
cell.font = header_font
|
||
cell.fill = header_fill
|
||
cell.alignment = Alignment(horizontal='center')
|
||
|
||
# Данные по регионам
|
||
for i, region in enumerate(region_stats, 11):
|
||
ws.cell(row=i, column=1, value=region['region_name'])
|
||
ws.cell(row=i, column=2, value=region['total_hotels'])
|
||
ws.cell(row=i, column=3, value=region['with_website'])
|
||
ws.cell(row=i, column=4, value=region['compliant_hotels'])
|
||
ws.cell(row=i, column=5, value=f"{region['avg_score']:.1f}%")
|
||
ws.cell(row=i, column=6, value=region['last_audit'].strftime('%d.%m.%Y') if region['last_audit'] else '')
|
||
|
||
# Форматирование
|
||
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=2, min_row=10, max_row=10+len(region_stats), max_col=2)
|
||
cats = Reference(ws, min_col=1, min_row=11, max_row=10+len(region_stats))
|
||
chart1.add_data(data, titles_from_data=False)
|
||
chart1.set_categories(cats)
|
||
chart1.height = 10
|
||
chart1.width = 15
|
||
|
||
ws.add_chart(chart1, "A15")
|
||
|
||
# Информация о дате создания
|
||
ws['A30'] = f"📅 Отчет создан: {datetime.now().strftime('%d.%m.%Y %H:%M')}"
|
||
ws['A30'].font = Font(name='Arial', size=10, italic=True, color='666666')
|
||
|
||
# Настройка ширины колонок
|
||
column_widths = [25, 10, 12, 15, 12, 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['region_name'])
|
||
ws.cell(row=i, column=3, value=hotel['website'] or hotel['website_address'])
|
||
ws.cell(row=i, column=4, value='Да' if hotel['has_website'] else 'Нет')
|
||
ws.cell(row=i, column=5, value=f"{hotel['total_score']}/{hotel['max_score']}")
|
||
ws.cell(row=i, column=6, value=f"{hotel['score_percentage']:.1f}%")
|
||
ws.cell(row=i, column=7, value=hotel['owner_inn'])
|
||
ws.cell(row=i, column=8, value=hotel['owner_ogrn'])
|
||
ws.cell(row=i, column=9, value=str(hotel['addresses']) if hotel['addresses'] else '')
|
||
ws.cell(row=i, column=10, value=hotel['phone'])
|
||
ws.cell(row=i, column=11, value=hotel['email'])
|
||
ws.cell(row=i, column=12, value=hotel['audit_date'].strftime('%d.%m.%Y %H:%M') if hotel['audit_date'] else '')
|
||
|
||
# Цветовое кодирование процента
|
||
percentage = hotel['score_percentage'] or 0
|
||
if percentage >= 70:
|
||
fill_color = 'C6EFCE' # Зеленый
|
||
elif percentage >= 40:
|
||
fill_color = 'FFEB9C' # Желтый
|
||
else:
|
||
fill_color = 'FFC7CE' # Красный
|
||
|
||
ws.cell(row=i, column=6).fill = PatternFill(start_color=fill_color, end_color=fill_color, fill_type='solid')
|
||
|
||
# Форматирование
|
||
for col in range(1, 13):
|
||
ws.cell(row=i, column=col).font = normal_font
|
||
ws.cell(row=i, column=col).alignment = Alignment(horizontal='center')
|
||
|
||
# Настройка ширины колонок
|
||
column_widths = [30, 20, 25, 10, 10, 10, 15, 15, 30, 15, 20, 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 main():
|
||
"""Основная функция"""
|
||
print("🚀 СОЗДАНИЕ EXCEL ДАШБОРДА")
|
||
print("=" * 50)
|
||
|
||
# Получаем данные
|
||
print("📊 Загружаем данные из БД...")
|
||
audit_data, region_stats, criteria_stats = get_audit_data()
|
||
|
||
print(f"✅ Загружено:")
|
||
print(f" 🏨 Отелей: {len(audit_data)}")
|
||
print(f" 📍 Регионов: {len(region_stats)}")
|
||
print(f" 🎯 Критериев: {len(criteria_stats)}")
|
||
|
||
# Создаем Excel файл
|
||
print("\n📝 Создаем Excel файл...")
|
||
workbook = Workbook()
|
||
|
||
# Лист дашборда
|
||
print("📊 Создаем дашборд...")
|
||
create_dashboard_sheet(workbook, region_stats, criteria_stats)
|
||
|
||
# Лист аудита
|
||
print("🏨 Создаем таблицу аудита...")
|
||
create_audit_sheet(workbook, audit_data)
|
||
|
||
# Сохраняем файл
|
||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||
filename = f"hotel_audit_dashboard_{timestamp}.xlsx"
|
||
|
||
workbook.save(filename)
|
||
|
||
print(f"\n✅ Дашборд сохранен: {filename}")
|
||
print(f"📊 Листы:")
|
||
print(f" 📈 Дашборд - графики и статистика")
|
||
print(f" 🏨 Аудит отелей - детальная таблица")
|
||
|
||
if __name__ == "__main__":
|
||
main()
|