Перед тем, как исправлять выгрузку скуд с глупым Gemini-Lite
This commit is contained in:
@@ -3,70 +3,49 @@
|
||||
FILE: services/scud_etl/anomaly_detector.py
|
||||
PROJECT: SCUD Orion AI (Unified Architecture)
|
||||
MODULE: services / scud_etl
|
||||
ROLE: Автоматическое выявление аномалий и конфликтов между 1С:ЗУП и СКУД.
|
||||
|
||||
AI-CONTEXT-ANCHORS:
|
||||
- ANCHOR[ANOMALY_DETECTOR_CORE]: Проверка физического присутствия в отпуске и перемещений без входа.
|
||||
ROLE: Детектирование истинных аномалий и конфликтов реестров.
|
||||
(Приход удаленщиков и командированных в офис аномалией НЕ является).
|
||||
===============================================================================
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Any
|
||||
import pandas as pd
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
ALLOWED_WORK_TRIP_KEYWORDS = ['командировк', 'разъездн', 'поездк']
|
||||
logger = logging.getLogger("SCUD_ANOMALY")
|
||||
|
||||
|
||||
# ANCHOR[ANOMALY_DETECTOR_CORE]
|
||||
def detect_all_anomalies(
|
||||
merged_df: pd.DataFrame,
|
||||
static_reasons_dict: dict,
|
||||
kb_rules: List[str],
|
||||
scud_fios_set: Optional[set] = None
|
||||
) -> List[Dict[str, Any]]:
|
||||
"""Выявляет конфликты и аномалии между источниками СКУД и 1С."""
|
||||
def detect_registry_anomalies(df_merged: pd.DataFrame) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Выявляет реальные аномалии:
|
||||
- Приход в офис во время отпуска или больничного листа.
|
||||
- Ошибки считывателей СКУД (наличие выхода при отсутствии отметки входа).
|
||||
"""
|
||||
anomalies = []
|
||||
kb_rules_text = " ".join(kb_rules).lower() if kb_rules else ""
|
||||
|
||||
for idx, row in merged_df.iterrows():
|
||||
fio = row.get('Сотрудник', row.get('fio_clean', ''))
|
||||
fio_clean = row.get('fio_clean', '')
|
||||
is_present = row.get('Пришел', False)
|
||||
is_exc = row.get('is_excluded', False)
|
||||
reason_1c = str(row.get('Вид_отсутствия', '')).strip()
|
||||
has_1c_reason = pd.notna(row.get('Вид_отсутствия')) and reason_1c != '' and not reason_1c.startswith('Исключение')
|
||||
anom_flag = row.get('anomaly_flag', 'NONE')
|
||||
for _, row in df_merged.iterrows():
|
||||
fio = row.get("fio_clean") or row.get("Сотрудник", "")
|
||||
start_day = str(row.get("Начало_дня", "")).strip()
|
||||
end_day = str(row.get("Конец_дня", "")).strip()
|
||||
reason = str(row.get("причина отсутствия", "")).strip()
|
||||
reason_lower = reason.lower()
|
||||
|
||||
is_fio_whitelisted = fio_clean.lower() in kb_rules_text
|
||||
|
||||
# 1. Присутствие при официальном отсутствии
|
||||
if is_present and has_1c_reason:
|
||||
is_allowed_trip = any(kw in reason_1c.lower() for kw in ALLOWED_WORK_TRIP_KEYWORDS)
|
||||
if not is_allowed_trip and not is_fio_whitelisted:
|
||||
# 1. Приход в офис при отпуске / больничном
|
||||
if start_day != "Нет входа" and reason and reason != "nan":
|
||||
# Удаленная работа и командировки разрешены для работы в офисе
|
||||
if not ("удален" in reason_lower or "дистанцион" in reason_lower or "командировк" in reason_lower or "поездк" in reason_lower):
|
||||
anomalies.append({
|
||||
"type": "ФИЗИЧЕСКОЕ ПРИСУТСТВИЕ ПРИ ОФИЦИАЛЬНОМ ОТСУТСТВИИ",
|
||||
"fio": fio,
|
||||
"details": f"Сотрудник пришел по СКУД, но в 1С оформлен документ: '{reason_1c}'"
|
||||
"type": "PHYSICAL_PRESENCE_DURING_ABSENCE",
|
||||
"description": f"Сотрудник пришел по СКУД ({start_day}), но в 1С оформлен документ: '{reason}'."
|
||||
})
|
||||
|
||||
if is_exc and not has_1c_reason:
|
||||
continue
|
||||
|
||||
# 2. Перемещение внутри здания без отметки входа на КПП
|
||||
if anom_flag == 'ANOMALY_NO_IN_HAS_ACTIVITY':
|
||||
first_act = row.get('Первая_активность', '—')
|
||||
# 2. Аномалия оборудования (есть выход без входа)
|
||||
if start_day == "Нет входа" and end_day != "Нет выхода":
|
||||
anomalies.append({
|
||||
"type": "АНОМАЛИЯ СКУД: ПЕРЕМЕЩЕНИЕ БЕЗ ВХОДА",
|
||||
"fio": fio,
|
||||
"details": f"Отсутствует регистрация входа на КПП при зафиксированной первой активности в {first_act}"
|
||||
"type": "SCUD_EQUIPMENT_ANOMALY",
|
||||
"description": f"Отсутствует отметка утреннего входа при наличии выхода ({end_day})."
|
||||
})
|
||||
|
||||
# 3. Сотрудник в штате 1С, но карты/профиля в СКУД нет
|
||||
if scud_fios_set is not None:
|
||||
if fio_clean not in scud_fios_set and not has_1c_reason:
|
||||
anomalies.append({
|
||||
"type": "АНОМАЛИЯ УЧЕТА: СОТРУДНИК ОТСУТСТВУЕТ В СКУД ОРИОН PRO",
|
||||
"fio": fio,
|
||||
"details": f"Сотрудник числится в Штатном расписании 1С ({row.get('Подразделение', '—')}), но отсутствует в СКУД"
|
||||
})
|
||||
|
||||
return anomalies
|
||||
Reference in New Issue
Block a user