import json import difflib import re from services.ai_verifier import ask_ollama from services.knowledge_base import load_knowledge_base def find_ai_identity_suggestions(unexplained_df, raw_staff_df): """ ⭐️ Теневой ИИ-арбитраж: ищет потенциальные опечатки операторов СКУД среди нераспознанных сотрудников и формирует рекомендации для администратора. """ if unexplained_df.empty or raw_staff_df is None or raw_staff_df.empty: return [] staff_fios = raw_staff_df['fio_clean'].dropna().unique().tolist() if 'fio_clean' in raw_staff_df.columns else [] unexp_fios = unexplained_df['fio_clean'].dropna().unique().tolist() if 'fio_clean' in unexplained_df.columns else [] suggestions = [] for scud_fio in unexp_fios: f_parts = scud_fio.split() if not f_parts: continue surname = f_parts[0].lower() for staff_fio in staff_fios: if staff_fio.lower().startswith(surname[:4]): ratio = difflib.SequenceMatcher(None, scud_fio.lower(), staff_fio.lower()).ratio() if 0.75 <= ratio < 1.0: suggestions.append({ "scud_fio": scud_fio, "suggested_zup_fio": staff_fio, "confidence": round(ratio, 2) }) break return suggestions def generate_markdown_report(merged_df, absent_explained, absent_unexplained, scud_present_but_absent_in_1c, anomalies_list=None, raw_scud_df=None, raw_staff_df=None, raw_absent_df=None, date_str="05.08.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 "Специфических правил компании пока нет." total_staff = len(merged_df) present_office_cnt = len(merged_df[merged_df['Пришел'] == True]) reason_series = merged_df['Вид_отсутствия'].astype(str).str.lower() is_remote_reason = reason_series.str.contains('удален|дистанцион', regex=True, na=False) remote_home_cnt = len(merged_df[(merged_df['Пришел'] == False) & is_remote_reason]) explained_cnt = len(merged_df[(merged_df['Пришел'] == False) & (merged_df['Вид_отсутствия'].notna()) & (~is_remote_reason) & (~merged_df['Вид_отсутствия'].astype(str).str.startswith('Исключение'))]) unexplained_cnt = len(absent_unexplained) anomalies_cnt = len(anomalies_list) if anomalies_list else 0 # ⭐️ Теневые подсказки ИИ ai_suggestions = find_ai_identity_suggestions(absent_unexplained, raw_staff_df) anomalies_formatted = [] if anomalies_list: for idx, a in enumerate(anomalies_list, 1): anomalies_formatted.append(f"{idx}. {a.get('fio', 'Сотрудник')}: {a.get('description', '')}") anomalies_text_block = "\n".join(anomalies_formatted) if anomalies_formatted else "Аномалий не обнаружено." unexplained_list = [] if not absent_unexplained.empty: for idx, r in absent_unexplained.iterrows(): fio = r.get('Сотрудник', r.get('fio_clean', '')) dept_scud = str(r.get('department_scud', '')).strip() dept_1c = str(r.get('Подразделение', '')).strip() dept = dept_scud if (dept_scud and dept_scud.lower() != 'nan' and dept_scud != '—') else dept_1c if not dept: dept = '—' pos = r.get('Должность', '—') unexplained_list.append(f"{fio} — {dept}, {pos}") prompt = f""" Ты — старший аудитор кадровой безопасности и контроллинга СКУД. Сформируй итоговую сводку кадрового контроля на {date_str}. 📌 БАЗА ЗНАНИЙ И ПРАВИЛА ПРЕДПРИЯТИЯ: {custom_rules_str} 🚨 ВХОДНЫЕ МЕТРИКИ: - Всего сотрудников: {total_staff} - Итого на работе (в офисе): {present_office_cnt} - В том числе на удаленной работе: {remote_home_cnt} - Официально отсутствуют: {explained_cnt} - Неизвестно (истинно неотмеченные): {unexplained_cnt} - Выявлено аномалий/конфликтов реестров: {anomalies_cnt} --- 🚨 ПОДТВЕРЖДЁННЫЕ АНОМАЛИИ ({anomalies_cnt} шт): {anomalies_text_block} --- 💡 ПРЕДЛОЖЕНИЯ ИИ ПО СОПОСТАВЛЕНИЮ ФИО ({len(ai_suggestions)} шт): {json.dumps(ai_suggestions, ensure_ascii=False, indent=2)} --- 📊 СПИСОК НЕИЗВЕСТНЫХ СЛУЧАЕВ ({unexplained_cnt} чел): {json.dumps(unexplained_list, ensure_ascii=False, indent=2)} СТРОГИЕ ИНСТРУКЦИИ: 1. КАТЕГОРИЧЕСКИ ЗАПРЕЩЕНО выводить JSON! В блоке "Выявленные ИИ аномалии" выведи читаемый нумерованный список строк ровно так, как передано в разделе "ПОДТВЕРЖДЁННЫЕ АНОМАЛИИ". 2. В разделе "Подозрения на ошибки сопоставления ФИО" выведи рекомендации по сопоставлению из массива "ПРЕДЛОЖЕНИЯ ИИ". Если массив пуст — напиши "Ошибок сопоставления ФИО не обнаружено." 3. В разделе "Неизвестные случаи" выведи нумерованный список всех {unexplained_cnt} человек. 4. В разделе "Рекомендации" дай 2-3 системные рекомендации. СТРОГИЙ ШАБЛОН ОТВЕТА: **Сводка контроллинга СКУД и 1С:ЗУП на {date_str}** - Всего офисных сотрудников: **{total_staff}** - Итого на работе (в офисе): **{present_office_cnt}** - В том числе на удаленной работе: **{remote_home_cnt}** - Официально отсутствуют: **{explained_cnt}** - Неизвестно (истинно неотмеченные): **{unexplained_cnt}** чел. - Выявлено аномалий/конфликтов реестров: **{anomalies_cnt}** шт. #### 🚨 Выявленные ИИ аномалии и конфликты источников ({anomalies_cnt}): (Список аномалий или 'Аномалий не обнаружено.') #### 💡 Подозрения на ошибки сопоставления ФИО и предложения ИИ: (Список предложений сопоставления или 'Ошибок сопоставления ФИО не обнаружено.') #### Неизвестные случаи: {unexplained_cnt} (Нумерованный список всех {unexplained_cnt} человек) ### Точечные рекомендации: (2-3 системные рекомендации) """ sys_prompt = "Ты — русскоязычный кадровый аудитор. Пиши СТРОГО на русском языке форматированным текстом. Запрещено выводить фигурные скобки JSON." return ask_ollama(prompt, system_prompt=sys_prompt)