📊 Improve dashboard with detailed registry statistics

- Added detailed statistics from hotel registry
- Shows total hotels, active hotels, hotels with websites, accessible sites
- Dynamic section positioning (no more hardcoded row numbers)
- Fixed region name usage throughout (no more 'СПб' in Orel reports)
- Shows different stats for ONLY_ACTIVE mode

Stats now include:
- Registry data: Total/Active hotels, Hotels with websites, Accessible sites
- Audit data: Audits conducted, With/Without sites, RKN registry, Avg score, Compliant hotels (≥50%)

Tested on Orel region:
- 64 total active hotels in registry
- 35 with websites
- 29 audited
- Avg score: 44.1%
This commit is contained in:
Фёдор
2025-10-27 23:22:57 +03:00
parent 309de51744
commit 5e807fd7ce

View File

@@ -144,7 +144,9 @@ def get_region_data():
def create_dashboard_sheet(workbook, audit_data, criteria_stats):
"""Создать лист дашборда"""
ws = workbook.active
ws.title = "📊 Дашборд СПб"
# Название листа по региону
region_short = REGION.replace('г. ', '').replace('область', 'обл.')[:15]
ws.title = f"📊 {region_short}"
# Стили
header_font = Font(name='Arial', size=14, bold=True, color='FFFFFF')
@@ -163,7 +165,28 @@ def create_dashboard_sheet(workbook, audit_data, criteria_stats):
ws['A3'].font = subheader_font
ws['A3'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid')
# Подсчитываем статистику
# Получаем статистику из реестра (БД)
conn = psycopg2.connect(**DB_CONFIG, cursor_factory=RealDictCursor)
cur = conn.cursor()
# Статистика по региону из реестра
status_filter_sql = "AND status_name = 'Действует'" if ONLY_ACTIVE else ""
cur.execute(f"""
SELECT
COUNT(*) as total_in_registry,
COUNT(CASE WHEN status_name = 'Действует' THEN 1 END) as active_hotels,
COUNT(CASE WHEN website_address IS NOT NULL AND website_address != '' THEN 1 END) as with_websites,
COUNT(CASE WHEN website_status = 'accessible' THEN 1 END) as accessible_websites
FROM hotel_main
WHERE region_name = %s
{status_filter_sql}
""", (REGION,))
registry_stats = cur.fetchone()
cur.close()
conn.close()
# Подсчитываем статистику из аудита
total_hotels = len(audit_data)
total_with_website = sum(1 for h in audit_data if h['has_website'])
total_without_website = total_hotels - total_with_website
@@ -171,70 +194,83 @@ def create_dashboard_sheet(workbook, audit_data, criteria_stats):
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"Всего отелей в {REGION}: {total_hotels}"
ws['A5'] = f"С сайтами: {total_with_website}"
ws['A6'] = f"Без сайтов: {total_without_website}"
ws['A7'] = f"Сайты доступны для анализа: {total_with_website}"
ws['A8'] = f"Сайты недоступны: 0"
ws['A9'] = f"В реестре РКН: {total_in_rkn}"
ws['A10'] = f"Проведено аудитов: {total_hotels}"
ws['A11'] = f"Средний балл (аудит): {avg_score:.1f}%"
# Выводим детальную статистику
row = 4
ws[f'A{row}'] = f"📋 По данным реестра в {REGION}:"
ws[f'A{row}'].font = Font(name='Arial', size=10, bold=True)
row += 1
for cell in ['A4', 'A5', 'A6', 'A7', 'A8', 'A9', 'A10', 'A11']:
ws[cell].font = normal_font
if ONLY_ACTIVE:
ws[f'A{row}'] = f"Всего действующих отелей: {registry_stats['total_in_registry']}"
else:
ws[f'A{row}'] = f"Всего отелей в реестре: {registry_stats['total_in_registry']}"
row += 1
ws[f'A{row}'] = f" • Из них действующих: {registry_stats['active_hotels']}"
# Категория
ws['A13'] = "Категория"
ws['A13'].font = subheader_font
ws['A13'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid')
row += 1
ws[f'A{row}'] = f" • Отелей с указанными сайтами: {registry_stats['with_websites']}"
row += 1
ws[f'A{row}'] = f" • Доступных сайтов (на момент проверки): {registry_stats['accessible_websites']}"
ws['A14'] = f"Сайты доступны: {total_with_website}"
ws['B14'] = total_with_website
ws['A15'] = f"Сайты недоступны: 0"
ws['B15'] = 0
ws['A16'] = f"Без сайтов: {total_without_website}"
ws['B16'] = total_without_website
ws['A17'] = f"В реестре РКН: {total_in_rkn}"
ws['B17'] = total_in_rkn
row += 2
ws[f'A{row}'] = f"🔍 Проведено аудитов: {total_hotels}"
ws[f'A{row}'].font = Font(name='Arial', size=10, bold=True)
row += 1
ws[f'A{row}'] = f"С сайтами: {total_with_website}"
row += 1
ws[f'A{row}'] = f" • Без сайтов: {total_without_website}"
row += 1
ws[f'A{row}'] = f"В реестре РКН: {total_in_rkn}"
row += 1
ws[f'A{row}'] = f" • Средний балл соответствия: {avg_score:.1f}%"
row += 1
ws[f'A{row}'] = f" • Отелей с баллом ≥50%: {total_compliant}"
for cell in ['A14', 'A15', 'A16', 'A17']:
ws[cell].font = normal_font
# Форматирование
for r in range(4, row + 1):
ws[f'A{r}'].font = normal_font
# Статистика по критериям
ws['A19'] = "🎯 СТАТИСТИКА ПО КРИТЕРИЯМ"
ws['A19'].font = subheader_font
ws['A19'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid')
# Статистика по критериям (сдвигаем вниз)
criteria_start_row = row + 3
ws[f'A{criteria_start_row}'] = "🎯 СТАТИСТИКА ПО КРИТЕРИЯМ"
ws[f'A{criteria_start_row}'].font = subheader_font
ws[f'A{criteria_start_row}'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid')
# Заголовки таблицы критериев
criteria_headers = ['Критерий', 'Найдено', 'Не найдено']
header_row = criteria_start_row + 1
for i, header in enumerate(criteria_headers, 1):
cell = ws.cell(row=20, column=i, value=header)
cell = ws.cell(row=header_row, column=i, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal='center')
# Данные по критериям
for i, criterion in enumerate(criteria_stats, 21):
data_start_row = header_row + 1
for idx, criterion in enumerate(criteria_stats):
current_row = data_start_row + idx
not_found = criterion['total_checks'] - criterion['found_count']
ws.cell(row=i, column=1, value=criterion['criterion_name'])
ws.cell(row=i, column=2, value=criterion['found_count'])
ws.cell(row=i, column=3, value=not_found)
ws.cell(row=current_row, column=1, value=criterion['criterion_name'])
ws.cell(row=current_row, column=2, value=criterion['found_count'])
ws.cell(row=current_row, column=3, value=not_found)
# Форматирование
for col in range(1, 4):
ws.cell(row=i, column=col).font = normal_font
ws.cell(row=i, column=col).alignment = Alignment(horizontal='center')
ws.cell(row=current_row, column=col).font = normal_font
ws.cell(row=current_row, column=col).alignment = Alignment(horizontal='center')
# Распределение по баллам
ws['A40'] = "📊 РАСПРЕДЕЛЕНИЕ ПО БАЛЛАМ"
ws['A40'].font = subheader_font
ws['A40'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid')
scores_start_row = data_start_row + len(criteria_stats) + 2
ws[f'A{scores_start_row}'] = "📊 РАСПРЕДЕЛЕНИЕ ПО БАЛЛАМ"
ws[f'A{scores_start_row}'].font = subheader_font
ws[f'A{scores_start_row}'].fill = PatternFill(start_color='E7E6E6', end_color='E7E6E6', fill_type='solid')
# Заголовки
score_headers = ['Диапазон', 'Количество']
score_header_row = scores_start_row + 1
for i, header in enumerate(score_headers, 1):
cell = ws.cell(row=41, column=i, value=header)
cell = ws.cell(row=score_header_row, column=i, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal='center')
@@ -247,14 +283,16 @@ def create_dashboard_sheet(workbook, audit_data, criteria_stats):
('76-100%', sum(1 for h in audit_data if h['score_percentage'] >= 76))
]
for i, (range_name, count) in enumerate(score_ranges, 42):
ws.cell(row=i, column=1, value=range_name)
ws.cell(row=i, column=2, value=count)
score_data_start = score_header_row + 1
for idx, (range_name, count) in enumerate(score_ranges):
current_row = score_data_start + idx
ws.cell(row=current_row, column=1, value=range_name)
ws.cell(row=current_row, column=2, value=count)
# Форматирование
for col in range(1, 3):
ws.cell(row=i, column=col).font = normal_font
ws.cell(row=i, column=col).alignment = Alignment(horizontal='center')
ws.cell(row=current_row, column=col).font = normal_font
ws.cell(row=current_row, column=col).alignment = Alignment(horizontal='center')
# Графики
# Круговой график статуса сайтов