278 lines
12 KiB
Python
278 lines
12 KiB
Python
|
|
#!/usr/bin/env python3
|
|||
|
|
"""
|
|||
|
|
Генерация Excel из JSON файла (экспортированного из n8n)
|
|||
|
|
"""
|
|||
|
|
|
|||
|
|
import json
|
|||
|
|
import openpyxl
|
|||
|
|
from openpyxl.styles import Font, PatternFill, Border, Side, Alignment
|
|||
|
|
from openpyxl.utils import get_column_letter
|
|||
|
|
from datetime import datetime
|
|||
|
|
|
|||
|
|
def create_excel_from_json(json_file):
|
|||
|
|
"""Создать Excel отчёт из JSON файла"""
|
|||
|
|
|
|||
|
|
# Читаем JSON
|
|||
|
|
with open(json_file, 'r', encoding='utf-8') as f:
|
|||
|
|
results = json.load(f)
|
|||
|
|
|
|||
|
|
print(f"📊 Найдено результатов аудита: {len(results)}")
|
|||
|
|
|
|||
|
|
wb = openpyxl.Workbook()
|
|||
|
|
ws = wb.active
|
|||
|
|
ws.title = "Аудит"
|
|||
|
|
|
|||
|
|
# Стили
|
|||
|
|
header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
|
|||
|
|
header_font = Font(color="FFFFFF", bold=True, size=10)
|
|||
|
|
found_fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid")
|
|||
|
|
not_found_fill = PatternFill(start_color="FFC7CE", end_color="FFC7CE", fill_type="solid")
|
|||
|
|
border = Border(
|
|||
|
|
left=Side(style='thin'),
|
|||
|
|
right=Side(style='thin'),
|
|||
|
|
top=Side(style='thin'),
|
|||
|
|
bottom=Side(style='thin')
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
# ЗАГОЛОВКИ (строка 1)
|
|||
|
|
col = 1
|
|||
|
|
base_headers = ['Отель', 'Сайт', 'Есть сайт', 'Балл', 'Процент']
|
|||
|
|
for header in base_headers:
|
|||
|
|
cell = ws.cell(row=1, column=col, value=header)
|
|||
|
|
cell.fill = header_fill
|
|||
|
|
cell.font = header_font
|
|||
|
|
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
|||
|
|
cell.border = border
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Заголовки критериев (включая РКН в правильном месте)
|
|||
|
|
if results and results[0].get('criteria_results'):
|
|||
|
|
criteria_results = results[0]['criteria_results']
|
|||
|
|
if isinstance(criteria_results, str):
|
|||
|
|
criteria_results = json.loads(criteria_results)
|
|||
|
|
|
|||
|
|
print(f"🔍 criteria_results type: {type(criteria_results)}")
|
|||
|
|
|
|||
|
|
# Если это список (из n8n) - используем напрямую
|
|||
|
|
if isinstance(criteria_results, list):
|
|||
|
|
criteria_list = criteria_results
|
|||
|
|
print(f"🔍 Найдено критериев (список): {len(criteria_list)}")
|
|||
|
|
# Если это словарь (из БД) - преобразуем
|
|||
|
|
elif isinstance(criteria_results, dict):
|
|||
|
|
criteria_list = []
|
|||
|
|
for i in range(1, 19): # критерии 1-18
|
|||
|
|
key = f'criterion_{i:02d}'
|
|||
|
|
if key in criteria_results:
|
|||
|
|
criterion_data = criteria_results[key]
|
|||
|
|
criteria_list.append({
|
|||
|
|
'criterion_id': i,
|
|||
|
|
'criterion_name': criterion_data.get('name', f'Критерий {i}'),
|
|||
|
|
})
|
|||
|
|
print(f"🔍 Найдено критериев (словарь): {len(criteria_list)}")
|
|||
|
|
else:
|
|||
|
|
criteria_list = []
|
|||
|
|
print(f"🔍 Неизвестный тип criteria_results")
|
|||
|
|
|
|||
|
|
for criterion_idx, criterion in enumerate(criteria_list):
|
|||
|
|
# Вставляем РКН заголовки после критерия 5 (индекс 5)
|
|||
|
|
if criterion_idx == 5:
|
|||
|
|
rkn_headers = ['6. РКН Реестр', '6. РКН Номер/Дата', '6. РКН Ссылка']
|
|||
|
|
for header in rkn_headers:
|
|||
|
|
cell = ws.cell(row=1, column=col, value=header)
|
|||
|
|
cell.fill = header_fill
|
|||
|
|
cell.font = header_font
|
|||
|
|
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
|||
|
|
cell.border = border
|
|||
|
|
ws.column_dimensions[get_column_letter(col)].width = 30
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
criterion_name = f"{criterion['criterion_id']}. {criterion['criterion_name']}"
|
|||
|
|
|
|||
|
|
# Колонка 1: Статус (ДА/НЕТ)
|
|||
|
|
cell = ws.cell(row=1, column=col, value=criterion_name)
|
|||
|
|
cell.fill = header_fill
|
|||
|
|
cell.font = header_font
|
|||
|
|
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
|||
|
|
cell.border = border
|
|||
|
|
ws.column_dimensions[get_column_letter(col)].width = 35
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Колонка 2: URL
|
|||
|
|
cell = ws.cell(row=1, column=col, value=f"{criterion['criterion_id']}. Апрув URL")
|
|||
|
|
cell.fill = header_fill
|
|||
|
|
cell.font = header_font
|
|||
|
|
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
|||
|
|
cell.border = border
|
|||
|
|
ws.column_dimensions[get_column_letter(col)].width = 40
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Колонка 3: Цитата/Детали
|
|||
|
|
cell = ws.cell(row=1, column=col, value=f"{criterion['criterion_id']}. Комментарий")
|
|||
|
|
cell.fill = header_fill
|
|||
|
|
cell.font = header_font
|
|||
|
|
cell.alignment = Alignment(horizontal='center', vertical='center', wrap_text=True)
|
|||
|
|
cell.border = border
|
|||
|
|
ws.column_dimensions[get_column_letter(col)].width = 50
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Высота строки заголовков
|
|||
|
|
ws.row_dimensions[1].height = 40
|
|||
|
|
|
|||
|
|
print(f"✅ Заголовки созданы, всего колонок: {col-1}")
|
|||
|
|
|
|||
|
|
# ДАННЫЕ (строки 2+)
|
|||
|
|
for row_idx, result in enumerate(results, 2):
|
|||
|
|
col = 1
|
|||
|
|
|
|||
|
|
# Базовые данные
|
|||
|
|
cell = ws.cell(row=row_idx, column=col, value=result['hotel_name'])
|
|||
|
|
cell.border = border
|
|||
|
|
cell.alignment = Alignment(vertical='top', wrap_text=True)
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
cell = ws.cell(row=row_idx, column=col, value=result.get('website', 'НЕТ САЙТА'))
|
|||
|
|
cell.border = border
|
|||
|
|
cell.alignment = Alignment(vertical='top')
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
has_website = "Да" if result.get('has_website') else "Нет"
|
|||
|
|
cell = ws.cell(row=row_idx, column=col, value=has_website)
|
|||
|
|
cell.border = border
|
|||
|
|
cell.alignment = Alignment(horizontal='center', vertical='center')
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
cell = ws.cell(row=row_idx, column=col, value=result['total_score'])
|
|||
|
|
cell.border = border
|
|||
|
|
cell.alignment = Alignment(horizontal='center', vertical='center')
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
perc_cell = ws.cell(row=row_idx, column=col, value=f"{result['score_percentage']:.1f}%")
|
|||
|
|
perc_cell.border = border
|
|||
|
|
perc_cell.alignment = Alignment(horizontal='center', vertical='center')
|
|||
|
|
if result['score_percentage'] >= 70:
|
|||
|
|
perc_cell.fill = found_fill
|
|||
|
|
elif result['score_percentage'] < 50:
|
|||
|
|
perc_cell.fill = not_found_fill
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Данные по критериям
|
|||
|
|
criteria_results = result['criteria_results']
|
|||
|
|
if isinstance(criteria_results, str):
|
|||
|
|
criteria_results = json.loads(criteria_results)
|
|||
|
|
|
|||
|
|
# Если это список (из n8n)
|
|||
|
|
if isinstance(criteria_results, list):
|
|||
|
|
criteria_list = criteria_results
|
|||
|
|
# Если это словарь (из БД)
|
|||
|
|
elif isinstance(criteria_results, dict):
|
|||
|
|
criteria_list = []
|
|||
|
|
for i in range(1, 19):
|
|||
|
|
key = f'criterion_{i:02d}'
|
|||
|
|
if key in criteria_results:
|
|||
|
|
criteria_list.append(criteria_results[key])
|
|||
|
|
else:
|
|||
|
|
criteria_list = []
|
|||
|
|
|
|||
|
|
for criterion_idx, criterion in enumerate(criteria_list):
|
|||
|
|
# Вставляем РКН колонки после критерия 5 (индекс 5)
|
|||
|
|
if criterion_idx == 5:
|
|||
|
|
# РКН данные
|
|||
|
|
rkn_status = criterion.get('rkn_status', '')
|
|||
|
|
rkn_in_registry = "ДА" if rkn_status and rkn_status.lower() == 'found' else "НЕТ"
|
|||
|
|
rkn_status_cell = ws.cell(row=row_idx, column=col, value=rkn_in_registry)
|
|||
|
|
rkn_status_cell.border = border
|
|||
|
|
rkn_status_cell.alignment = Alignment(horizontal='center', vertical='center')
|
|||
|
|
if rkn_in_registry == "ДА":
|
|||
|
|
rkn_status_cell.fill = found_fill # Зелёный - хорошо если в реестре
|
|||
|
|
else:
|
|||
|
|
rkn_status_cell.fill = not_found_fill # Красный - плохо если НЕ в реестре
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
rkn_number = criterion.get('rkn_number', '')
|
|||
|
|
rkn_date = criterion.get('rkn_date', '')
|
|||
|
|
rkn_info_text = f"{rkn_number}\n{rkn_date}" if rkn_number or rkn_date else "-"
|
|||
|
|
cell = ws.cell(row=row_idx, column=col, value=rkn_info_text)
|
|||
|
|
cell.border = border
|
|||
|
|
cell.alignment = Alignment(vertical='top', wrap_text=True)
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
rkn_url = f"https://rkn.gov.ru/mass-communications/reestr/search/?q={rkn_number}" if rkn_number else "-"
|
|||
|
|
cell = ws.cell(row=row_idx, column=col, value=rkn_url)
|
|||
|
|
cell.border = border
|
|||
|
|
cell.alignment = Alignment(vertical='top')
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Колонка 1: Статус (ДА/НЕТ)
|
|||
|
|
status = "ДА" if criterion.get('found') else "НЕТ"
|
|||
|
|
status_cell = ws.cell(row=row_idx, column=col, value=status)
|
|||
|
|
status_cell.border = border
|
|||
|
|
status_cell.alignment = Alignment(horizontal='center', vertical='center')
|
|||
|
|
if criterion.get('found'):
|
|||
|
|
status_cell.fill = found_fill
|
|||
|
|
else:
|
|||
|
|
status_cell.fill = not_found_fill
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Колонка 2: URL
|
|||
|
|
url = '-'
|
|||
|
|
if criterion.get('ai_agent', {}).get('url'):
|
|||
|
|
url = criterion['ai_agent']['url']
|
|||
|
|
cell = ws.cell(row=row_idx, column=col, value=url)
|
|||
|
|
cell.border = border
|
|||
|
|
cell.alignment = Alignment(vertical='top')
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Колонка 3: Комментарий/Цитата
|
|||
|
|
comment = "Не найдено"
|
|||
|
|
if criterion.get('found'):
|
|||
|
|
# Приоритет: ai_agent.details → ai_agent.quote → regex.extracted
|
|||
|
|
if criterion.get('ai_agent', {}).get('details'):
|
|||
|
|
comment = criterion['ai_agent']['details']
|
|||
|
|
elif criterion.get('ai_agent', {}).get('quote'):
|
|||
|
|
comment = criterion['ai_agent']['quote']
|
|||
|
|
elif criterion.get('regex', {}).get('extracted'):
|
|||
|
|
comment = f"[Regex] {criterion['regex']['extracted']}"
|
|||
|
|
else:
|
|||
|
|
comment = "Найдено"
|
|||
|
|
|
|||
|
|
# Ограничиваем длину
|
|||
|
|
comment = comment[:200] + "..." if len(comment) > 200 else comment
|
|||
|
|
|
|||
|
|
cell = ws.cell(row=row_idx, column=col, value=comment)
|
|||
|
|
cell.border = border
|
|||
|
|
cell.alignment = Alignment(vertical='top', wrap_text=True)
|
|||
|
|
col += 1
|
|||
|
|
|
|||
|
|
# Высота строки
|
|||
|
|
ws.row_dimensions[row_idx].height = 50
|
|||
|
|
|
|||
|
|
print(f"✅ Данные добавлены, всего строк: {len(results)}")
|
|||
|
|
|
|||
|
|
# Сохраняем файл
|
|||
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|||
|
|
filename = f"audit_from_json_{timestamp}.xlsx"
|
|||
|
|
wb.save(filename)
|
|||
|
|
|
|||
|
|
return filename
|
|||
|
|
|
|||
|
|
def main():
|
|||
|
|
"""Основная функция"""
|
|||
|
|
print("🚀 ГЕНЕРАЦИЯ EXCEL ИЗ JSON")
|
|||
|
|
print("=" * 50)
|
|||
|
|
|
|||
|
|
json_file = "audit_data.json"
|
|||
|
|
|
|||
|
|
print(f"📂 Читаю файл: {json_file}")
|
|||
|
|
|
|||
|
|
try:
|
|||
|
|
filename = create_excel_from_json(json_file)
|
|||
|
|
print(f"✅ Excel отчёт сохранён: {filename}")
|
|||
|
|
except FileNotFoundError:
|
|||
|
|
print(f"❌ Файл {json_file} не найден")
|
|||
|
|
print(f"📝 Создайте файл {json_file} с данными из n8n")
|
|||
|
|
except Exception as e:
|
|||
|
|
print(f"❌ Ошибка: {e}")
|
|||
|
|
|
|||
|
|
if __name__ == "__main__":
|
|||
|
|
main()
|