промежуточный этап. Работает.
This commit is contained in:
+22
-7
@@ -1,7 +1,6 @@
|
||||
"""
|
||||
Модуль автоматического экспорта данных СКУД (Orion) из MS SQL Server в SQLite и Excel.
|
||||
Исправлена логика определения направления проходов (Mode = 2 -> OUT).
|
||||
Адаптирован для Linux (Ubuntu 24.04).
|
||||
Добавлена фиксация Первой Активности (без учета направления) и среза по времени.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -82,11 +81,11 @@ WITH DailyLogs AS (
|
||||
WHERE log.TimeVal BETWEEN @StartDate AND @EndDate
|
||||
AND log.HozOrgan IS NOT NULL
|
||||
AND log.HozOrgan > 0
|
||||
AND (log.Event IN (28, 29, 26, 27, 32, 33) OR log.Mode IN (1, 2))
|
||||
),
|
||||
Passages AS (
|
||||
SELECT
|
||||
EmployeeID,
|
||||
MIN(TimeVal) AS FirstRawEvent, -- ⭐️ Первое событие ДНЯ ВООБЩЕ (любая дверь/туалет/выход)
|
||||
MIN(CASE WHEN Direction = 'IN' THEN TimeVal END) AS FirstIn,
|
||||
MAX(CASE WHEN Direction = 'OUT' THEN TimeVal END) AS LastOut,
|
||||
MAX(CASE WHEN RowNumDesc = 1 THEN Direction END) AS LastEventType
|
||||
@@ -107,6 +106,14 @@ SELECT
|
||||
ISNULL(CAST(p.TabNumber AS NVARCHAR(50)), N'—') AS [Таб_№],
|
||||
CONVERT(VARCHAR(10), @TargetDate, 104) AS [Дата],
|
||||
ISNULL(CAST(CONVERT(VARCHAR(8), pass.FirstIn, 108) AS NVARCHAR(20)), N'Нет входа') AS [Начало_дня],
|
||||
|
||||
-- ⭐️ НОВАЯ КОЛОНКА: "Первая активность" (если нет входа, но есть события в здании)
|
||||
CASE
|
||||
WHEN pass.FirstIn IS NULL AND pass.FirstRawEvent IS NOT NULL
|
||||
THEN CAST(CONVERT(VARCHAR(8), pass.FirstRawEvent, 108) AS NVARCHAR(20))
|
||||
ELSE N'—'
|
||||
END AS [Первая_активность],
|
||||
|
||||
CASE
|
||||
WHEN @TargetDate = CAST(GETDATE() AS DATE) AND (pass.LastEventType = 'IN' OR pass.LastOut IS NULL OR pass.LastOut <= pass.FirstIn)
|
||||
THEN N'Нет выхода'
|
||||
@@ -204,6 +211,7 @@ def run_export(input_date: str | None = None, save_xlsx: bool = True, debug: boo
|
||||
os.makedirs(SCUD_DIR, exist_ok=True)
|
||||
|
||||
targets = get_targets(input_date)
|
||||
snapshot_time = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
conn_str = (
|
||||
f"DRIVER={{{ODBC_DRIVER}}};"
|
||||
@@ -235,11 +243,18 @@ def run_export(input_date: str | None = None, save_xlsx: bool = True, debug: boo
|
||||
if len(df) > 0:
|
||||
# 1. Готовим колонки для локальной БД
|
||||
df['fio_clean'] = df['Сотрудник'].apply(clean_scud_fio_light)
|
||||
df['Пришел'] = df['Статус'].str.contains('Присутствовал', case=False, na=False)
|
||||
|
||||
# Валидация прихода с учетом аномалии неявки на вход
|
||||
df['anomaly_flag'] = 'NONE'
|
||||
mask_anomaly = (df['Начало_дня'] == 'Нет входа') & (df['Первая_активность'] != '—')
|
||||
df.loc[mask_anomaly, 'anomaly_flag'] = 'ANOMALY_NO_IN_HAS_ACTIVITY'
|
||||
|
||||
# 2. ПРЯМОЕ СОХРАНЕНИЕ В SQLite (scud_orion_ai.db)
|
||||
save_scud_to_db(df, processing_date_str)
|
||||
log(f"[✓] Записи за {processing_date_str} успешно сохранены в SQLite!", "SUCCESS")
|
||||
# Флаг 'Пришел' ставится в True только если был вход
|
||||
df['Пришел'] = df['Статус'].str.contains('Присутствовал', case=False, na=False) & (~mask_anomaly)
|
||||
|
||||
# 2. ПРЯМОЕ СОХРАНЕНИЕ В SQLite (scud_orion_ai.db) С METКОЙ СРЕЗА snapshot_time
|
||||
save_scud_to_db(df, processing_date_str, snapshot_time=snapshot_time)
|
||||
log(f"[✓] Записи за {processing_date_str} успешно сохранены в SQLite с меткой {snapshot_time}!", "SUCCESS")
|
||||
|
||||
# 3. Сохранение в Excel (если включен флаг save_xlsx)
|
||||
if save_xlsx:
|
||||
|
||||
Reference in New Issue
Block a user