Files
scud_orion_ai_v2/modules/text_reporter.py
T

91 lines
5.5 KiB
Python

import json
from modules.ai_verifier import ask_ollama
from modules.knowledge_base import load_knowledge_base
def generate_markdown_report(merged_df, absent_explained, absent_unexplained, scud_present_but_absent_in_1c, raw_scud_df=None, raw_staff_df=None, date_str="27.07.2026"):
kb = load_knowledge_base()
custom_rules = kb.get("rules", [])
custom_rules_str = "\n".join([f"- {r}" for r in custom_rules]) if custom_rules else "Специфических правил компании пока нет."
detailed_cases_for_ai = []
unexplained_fios = absent_unexplained['fio_clean'].unique() if 'fio_clean' in absent_unexplained.columns else []
for fio_clean in unexplained_fios:
staff_matches = []
if raw_staff_df is not None and not raw_staff_df.empty and 'fio_clean' in raw_staff_df.columns:
st_m = raw_staff_df[raw_staff_df['fio_clean'] == fio_clean]
cols_staff = [c for c in ['ФИО', 'Подразделение', 'Должность', 'Таб_№'] if c in st_m.columns]
staff_matches = st_m[cols_staff].to_dict(orient='records')
scud_matches = []
if raw_scud_df is not None and not raw_scud_df.empty and 'fio_clean' in raw_scud_df.columns:
sc_m = raw_scud_df[raw_scud_df['fio_clean'] == fio_clean]
cols_scud = [c for c in ['Сотрудник', 'Подразделение', 'Должность', 'Начало_дня', 'Статус'] if c in sc_m.columns]
scud_matches = sc_m[cols_scud].to_dict(orient='records')
detailed_cases_for_ai.append({
"fio_clean": fio_clean,
"staff_records_count": len(staff_matches),
"staff_records_1C": staff_matches,
"scud_records_LOGS": scud_matches
})
cols_crit = [c for c in ['Сотрудник', 'Подразделение', 'Должность', 'Вид_отсутствия'] if c in scud_present_but_absent_in_1c.columns]
critical_list = scud_present_but_absent_in_1c[cols_crit].to_dict(orient='records')
# Исходные точные цифры Python
total_staff = len(merged_df)
present_cnt = len(merged_df[merged_df['Пришел'] == True])
explained_cnt = len(absent_explained)
unexplained_cnt = len(absent_unexplained)
prompt = f"""
Ты — старший аудитор кадровой безопасности и систем СКУД. Проанализируй данные за {date_str} и составь итоговый отчет.
📌 ДИНАМИЧЕСКИЕ ПРАВИЛА И НАКОПЛЕННЫЙ ОПЫТ ПРЕДПРИЯТИЯ:
{custom_rules_str}
🚨 СТРОГИЕ ПРАВИЛА МАТЕМАТИКИ И КАТЕГОРИЙ:
1. Цифры сводки ДОЛЖНЫ СТРОГО СОВПАДАТЬ с первично посчитанным массивом:
- Всего офисных сотрудников: {total_staff}
- Присутствуют на рабочем месте: {present_cnt}
- Официально отсутствуют: {explained_cnt}
- Неизвестно (истинно неотмеченные): {unexplained_cnt} чел.
2. Заголовок "#### Неизвестные случаи: {unexplained_cnt}" должен иметь ровно ту же цифру {unexplained_cnt}.
3. Нумерованный список ниже ДОЛЖЕН содержать ровно {unexplained_cnt} сотрудников из массива `detailed_cases_for_ai` без пропусков.
---
📊 ВХОДНЫЕ МИКРО-ДАННЫЕ НЕИЗВЕСТНЫХ ({unexplained_cnt} чел):
{json.dumps(detailed_cases_for_ai, ensure_ascii=False, indent=2)}
🚨 СПИСОК КРИТИЧЕСКИХ АНОМАЛИЙ:
{json.dumps(critical_list, ensure_ascii=False, indent=2)}
СТРОГИЙ ШАБЛОН ОТВЕТА:
**Сводка контроллинга СКУД и 1С:ЗУП на {date_str} в 12:00**
- Всего офисных сотрудников: **{total_staff}**
- Присутствуют на рабочем месте: **{present_cnt}**
- Официально отсутствуют: **{explained_cnt}**
- Неизвестно (истинно неотмеченные после проверки ИИ): **{unexplained_cnt}** чел.
#### ⚠️ Подозрения на ложные срабатывания СКУД / Совмещения:
(Укажи сотрудников с аномалиями/вторыми проходами, например Кондрашову Е.В., если у нее дубликат карты)
#### Критические аномалии (Работа в отпуске / на больничном):
(Детально опиши сотрудников из списка КРИТИЧЕСКИХ АНОМАЛИЙ. Если их нет — напиши "Критических нарушений не выявлено.")
#### Неизвестные случаи: {unexplained_cnt}
(Выведи НУМЕРОВАННЫЙ СПИСОК ВСЕХ {unexplained_cnt} человек строго в формате:
1. ФИО — [Аббревиатура], Должность
2. ФИО — [Аббревиатура], Должность
...
{unexplained_cnt}. ФИО — [Аббревиатура], Должность)
### Рекомендации:
...
"""
sys_prompt = "Ты — кадровый аудитор. Выводи ровно то количество людей в списке, которое указано в переменной unexplained_cnt."
return ask_ollama(prompt, system_prompt=sys_prompt)