Проект аудита отелей: основные скрипты и документация
- Краулеры: 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
This commit is contained in:
427
create_chukotka_report.py
Normal file
427
create_chukotka_report.py
Normal file
@@ -0,0 +1,427 @@
|
||||
#!/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()
|
||||
|
||||
Reference in New Issue
Block a user