feat(portal): harden readiness and explain workforce KPI

This commit is contained in:
igor04091968
2026-06-07 14:12:32 +03:00
parent 7df665a457
commit 07dfb95b6b
9 changed files with 2458 additions and 47 deletions
+111
View File
@@ -0,0 +1,111 @@
# Explainable Workforce KPI
Explainable Workforce KPI отвечает на вопрос: почему получился такой индекс
активности. Слой предназначен для руководителя, ИБ и администратора, но не
является HR-оценкой сотрудника и не использует ML/LLM.
## API
Endpoint:
```http
GET /api/workforce/kpi/explain
```
Поддерживаемые параметры:
- `date`;
- `department`;
- `owner`;
- `role`.
`employee_id` намеренно не добавлен: отдельная безопасная модель доступа к
персональному explainability-контракту в Pilot v1 не утверждена.
## Модель ответа
Ответ содержит:
- `kpi_score`: итоговый индекс 0-100;
- `confidence`: `high`, `medium` или `low`;
- `coverage`: покрытие агента, свежесть данных, отсутствующие источники;
- `factors`: детерминированные факторы с вкладом и объяснением;
- `top_applications`: агрегированные приложения, влияющие на индекс;
- `warnings`: предупреждения о качестве KPI;
- `recommendations`: действия для проверки или улучшения данных.
## Факторы
Минимальный набор факторов:
| Factor | Смысл |
| --- | --- |
| `productive_activity` | Доля активности относительно планового рабочего времени |
| `business_app_usage` | Наличие рабочих приложений и правил весов |
| `idle_time` | Простой в рабочее время |
| `afterhours_activity` | Активность вне рабочего окна |
| `remote_session_activity` | Подтверждение активности через удаленные сессии |
| `data_coverage` | Полнота агентских данных |
| `missing_data` | Пропущенные источники |
| `trend_change` | Наличие дневной/недельной/месячной истории |
Факторы rule-based, порядок стабильный, объяснения детерминированные.
## Confidence
`high`:
- хорошее покрытие;
- свежие данные;
- нет критичных пропусков.
`medium`:
- есть частичные пропуски;
- свежесть или покрытие требуют проверки.
`low`:
- нет worktime-данных;
- мало данных;
- слабое покрытие;
- источник отсутствует или недоступен.
## Роли
| Роль | Видимость |
| --- | --- |
| `executive` | Агрегированный KPI, без персональных деталей |
| `manager` | Workforce KPI по доступному управленческому срезу |
| `security` | Только факторы, релевантные ИБ и надежности данных |
| `forensics` | Контекст расследования: временные отклонения и пропуски данных |
| `admin` | Техническое покрытие и состояние источников |
Security и Forensics не получают Workforce Dashboard через `/api/reports` по
умолчанию. Для explainability используется отдельный endpoint с серверной
фильтрацией.
## UI и Markdown
Портал показывает блок:
```text
Почему такой индекс активности?
```
В Markdown-отчет добавлен раздел:
```markdown
## Объяснение индекса активности
```
Раздел содержит KPI score, confidence, coverage, факторы, warnings и
рекомендации.
## Ограничения Pilot v1
- Это не ML и не LLM.
- Это не predictive scoring.
- Это не дисциплинарная HR-оценка.
- Персональные выводы не формируются.
- Качество KPI зависит от свежести ActivityWatch/worktime/agent data.
+109
View File
@@ -10,6 +10,115 @@
- реальную запись в InfluxDB;
- health Grafana datasource.
## Portal production hardening
Портал AWatch-rus дополнен отдельным production-hardening слоем. Он не заменяет
`detmir-readiness`, а закрывает HTTP/API надежность портала: liveness,
readiness, version metadata, Prometheus metrics, request id/correlation id,
bounded payload/query limits и role-gate smoke.
### HTTP endpoints
| Endpoint | Назначение | Внешние зависимости |
| --- | --- | --- |
| `GET /healthz` | Liveness процесса; возвращает `200 OK`, если процесс отвечает. | Не проверяет |
| `GET /readyz` | Готовность приложения обслуживать запросы. | Только реально настроенные локальные зависимости |
| `GET /version` | Версия приложения, schema version, build metadata. | Не проверяет |
| `GET /metrics` | Prometheus text format. | Не проверяет |
`/readyz` не заявляет SIEM, DLP ingestion или pfSense ingestion. pfSense
отображается как `contract_only`: контрактная готовность, не реальный
полноценный сборщик.
### Конфигурация и лимиты
Портал валидирует конфигурацию при старте и завершает работу с понятной
ошибкой, если значение небезопасно или некорректно. Секреты в ошибку не
попадают.
| Параметр | Env | Назначение |
| --- | --- | --- |
| `--bind` | `DETMIR_PORTAL_BIND` | `host:port` HTTP-сервера |
| `--max-page-size` | `AWATCH_PORTAL_MAX_PAGE_SIZE` | Верхний предел `page_size`/`limit` |
| `--default-page-size` | `AWATCH_PORTAL_DEFAULT_PAGE_SIZE` | Значение по умолчанию для страниц |
| `--max-report-date-range-days` | `AWATCH_PORTAL_MAX_REPORT_DATE_RANGE_DAYS` | Максимальный диапазон отчетов |
| `--request-timeout-seconds` | `AWATCH_PORTAL_REQUEST_TIMEOUT_SECONDS` | Целевой timeout запроса/операции |
| `--max-request-body-bytes` | `AWATCH_PORTAL_MAX_REQUEST_BODY_BYTES` | Общий лимит тела запроса |
| `--slow-request-log-ms` | `AWATCH_PORTAL_SLOW_REQUEST_LOG_MS` | Порог медленного запроса для логов |
| `--environment` | `AWATCH_PORTAL_ENVIRONMENT` | Безопасное имя окружения |
| `--enabled-modules` | `AWATCH_PORTAL_ENABLED_MODULES` | Разрешенные модули портала |
Ограничения применяются к тяжелым API:
- `/api/reports`;
- `/api/executive`;
- `/api/workforce`;
- `/api/security`;
- `/api/forensics`;
- `/api/ueba`;
- `/api/pfsense`;
- `/api/workforce/kpi/explain`.
Поведение:
- слишком большой `page_size` или `limit` возвращает `400`;
- слишком широкий диапазон `date_from/date_to`, `from/to`, `start/end`
возвращает `400`;
- слишком большое тело запроса возвращает `413`;
- role gate возвращает `403`.
### Request ID, logs и metrics
Портал принимает `X-Request-Id` и `X-Correlation-Id`. Если заголовки не
переданы, `X-Request-Id` генерируется сервером, а `X-Correlation-Id` получает
то же значение. Оба заголовка возвращаются в ответе.
HTTP-ответы пишутся в stderr как JSON-строки с полями:
- `timestamp`;
- `level`;
- `request_id`;
- `correlation_id`;
- `method`;
- `path` без query params;
- `route`;
- `status`;
- `latency_ms`;
- `user_role`;
- `module`;
- `error_code`;
- `response_bytes`.
В логах не должно быть токенов, тел запросов, IP-адресов клиента,
`employee_id`, сырых query params или персональных данных.
`GET /metrics` возвращает:
- `awatch_http_requests_total`;
- `awatch_http_request_duration_seconds`;
- `awatch_reports_generated_total`;
- `awatch_ingestion_records_total`;
- `awatch_ingestion_rejected_total`;
- `awatch_role_denied_total`;
- `awatch_readyz_status`.
Labels ограничены низкой кардинальностью: `method`, `route`, `status`,
`module`. Запрещены high-cardinality labels: `user_id`, `employee_id`, IP, raw
URL, query params.
### Portal smoke
Минимальный smoke:
```bash
AWATCH_PORTAL_SMOKE_URL=http://127.0.0.1:8720 \
node scripts/awatch-production-hardening-smoke.mjs
```
Smoke проверяет `/healthz`, `/readyz`, `/version`, `/metrics`, возврат
`X-Request-Id`, reject слишком большого `page_size`, reject слишком широкого
report range, role gates и `/api/workforce/kpi/explain`.
## Базовый запуск
На AW server: