From 5e807fd7cec1c8e56b53435bfc5eeb33f3b27a95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A4=D1=91=D0=B4=D0=BE=D1=80?= Date: Mon, 27 Oct 2025 23:22:57 +0300 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=8A=20Improve=20dashboard=20with=20det?= =?UTF-8?q?ailed=20registry=20statistics?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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% --- create_horizontal_report.py | 130 +++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 46 deletions(-) diff --git a/create_horizontal_report.py b/create_horizontal_report.py index 610bcc8..885de82 100644 --- a/create_horizontal_report.py +++ b/create_horizontal_report.py @@ -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') # Графики # Круговой график статуса сайтов