Files
EventHubBack/include/records.hrl
T
aleksey f4d89a262c
CI / test (push) Successful in 7m53s
CI / deploy-ift (push) Successful in 2m41s
CI / e2e-ift (push) Successful in 1m20s
CI / deploy-stage (push) Successful in 2m16s
CI / e2e-stage (push) Successful in 1m13s
feat(ai): пилотные метрики ИИ-подсказок — POST /v1/ai/hint-metrics + админ-сводка
Канон PRODUCT-AI (Spec#22): пилотные метрики L1-подсказок для расчёта
голосового гейта L2 (>=20% активных пользователей с >=2 подтверждённых
ghost-бронирований в месяц). Локальные счётчики в Mnesia, без LLM.

- таблица ai_hint_metric (миграция 20260820180000): счётчики
  {user, день UTC, pattern, kind}; ленивый prune при >500 строк на
  пользователя, retention 92 дня
- POST /v1/ai/hint-metrics (Bearer): батч <=20 событий shown/confirm/dismiss,
  202 {accepted:N}; ETS rate limit 30 req/min на пользователя
- GET /v1/admin/ai-metrics/stats (админ-порт): by_kind, active_users_30d,
  users_with_confirms_ge2_30d, gate_ratio_30d, top_patterns_by_confirm
- eunit logic_ai_metrics_tests (5/5); синхронизация списка миграций
  в migration_engine_tests

Refs EventHub/EventHubBack#78
2026-08-20 18:35:29 +03:00

432 lines
15 KiB
Erlang
Executable File

%% ===================================================================
%% EventHub Records Definition
%% ===================================================================
%% ------------------- Пользователи и аутентификация -------------------
-record(user, {
id :: binary(),
email :: binary(),
password_hash :: binary(),
role :: user | bot,
status :: pending | active | frozen | deleted,
reason :: binary(),
nickname :: binary(),
avatar_url :: binary() | default,
timezone :: binary(),
language :: binary(),
social_links :: [binary()],
phone :: binary(),
preferences :: map(),
last_login :: calendar:datetime(),
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
-record(session, {
token :: binary(), % JWT или refresh токен
user_id :: binary(),
expires_at :: calendar:datetime(),
type :: access | refresh
}).
-record(verification, {
token :: binary(),
user_id :: binary(),
expires_at :: calendar:datetime()
}).
-record(password_reset, {
token :: binary(),
user_id :: binary(),
expires_at :: calendar:datetime()
}).
%% ------------------- АДМИНИСТРАТОРЫ ------------------------------------
-record(admin, {
id :: binary(),
email :: binary(),
password_hash :: binary(),
role :: superadmin | admin | moderator | support,
status :: active | blocked,
nickname :: binary(),
avatar_url :: binary() | default,
timezone :: binary(),
language :: binary(),
phone :: binary(),
preferences :: map(),
last_login :: calendar:datetime(),
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
%% ------------------- СЕССИИ АДМИНИСТРАТОРОВ ---------------------------
-record(admin_session, {
token :: binary(),
admin_id :: binary(),
expires_at :: calendar:datetime(),
type :: refresh
}).
%% ------------------- ЕДИНАЯ МОДЕЛЬ СЕССИЙ (refresh JWT) ----------------
-record(auth_session, {
session_id :: binary(),
family_id :: binary(),
subject_id :: binary(),
subject_type :: user | admin,
client_type :: binary(),
current_jti :: binary(),
expires_at :: calendar:datetime(),
revoked :: boolean(),
created_at :: calendar:datetime(),
updated_at :: calendar:datetime(),
device_name :: binary(),
user_agent :: binary()
}).
%% ------------------- Календари ---------------------------------------
-record(calendar, {
id :: binary(),
owner_id :: binary(),
title :: binary(),
description :: binary(),
short_name :: binary(),
category :: binary(),
color :: binary(),
image_url :: binary(),
settings :: map(),
tags :: [binary()],
type :: personal | commercial,
confirmation :: auto | manual | {timeout, integer()}, % секунд
rating_avg :: float(),
rating_count :: non_neg_integer(),
status :: active | frozen | deleted,
reason :: binary(),
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
%% PK = id. Уникальность пары calendar_id+user_id — в logic.
-record(calendar_share, {
id :: binary(),
calendar_id :: binary(),
user_id :: binary(),
rights :: read | write | admin,
mirror_to_default :: boolean()
}).
%% Приглашения к calendar_share (соредактор / заместитель).
-record(calendar_share_invite, {
id :: binary(),
calendar_id :: binary(),
inviter_id :: binary(),
invitee_user_id :: binary(), % <<>> если ещё неизвестен
invitee_email :: binary(), % <<>> если только user_id
rights :: read | write | admin,
status :: pending | accepted | declined | expired | cancelled,
token :: binary(),
created_at :: calendar:datetime(),
expires_at :: calendar:datetime()
}).
%% Follow чужого календаря (не путать с платной subscription / calendar_share)
-record(calendar_follow, {
id :: binary(),
calendar_id :: binary(),
user_id :: binary(),
created_at :: calendar:datetime()
}).
%% ------------------- Специалисты календаря ---------------------------
%% PK = id (несколько специалистов на календарь). Уникальность пары
%% calendar_id+user_id — на уровне logic.
-record(calendar_specialist, {
id :: binary(),
calendar_id :: binary(),
user_id :: binary(), % id пользователя-специалиста
name :: binary(), % отображаемое имя в этом календаре
specialization :: [binary()], % список специализаций (услуг)
status :: active | inactive,
added_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
%% ------------------- Приглашения специалистов ------------------------
%% PK = id. Pending-уникальность (calendar + user|email) — в logic.
-record(specialist_invite, {
id :: binary(),
calendar_id :: binary(),
inviter_id :: binary(),
invitee_user_id :: binary(), % <<>> если ещё неизвестен
invitee_email :: binary(), % <<>> если только user_id
name :: binary(),
specialization :: [binary()],
status :: pending | accepted | declined | expired | cancelled,
token :: binary(),
created_at :: calendar:datetime(),
expires_at :: calendar:datetime()
}).
-record(location, {
address :: binary(),
lat :: float(),
lon :: float()
}).
%% ------------------- События (включая повторяющиеся) ----------------
-record(event, {
id :: binary(),
calendar_id :: binary(),
title :: binary(),
description :: binary(),
event_type :: single | recurring,
start_time :: calendar:datetime(),
duration :: integer(), % минуты
recurrence_rule :: binary(),
master_id :: binary(),
is_instance :: boolean(),
specialist_id :: binary(),
location :: #location{},
tags :: [binary()],
capacity :: integer(),
online_link :: binary(),
status :: active | cancelled | completed | frozen | deleted,
reason :: binary(),
rating_avg :: float(),
rating_count :: non_neg_integer(),
attachments :: [binary()],
edit_history :: [map()],
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
-record(recurrence_exception, {
master_id :: binary(),
original_start :: calendar:datetime(),
action :: cancel | reschedule,
new_start :: calendar:datetime()
}).
%% ------------------- Бронирования ------------------------------------
-record(booking, {
id :: binary(),
event_id :: binary(), % ссылка на конкретный экземпляр события
user_id :: binary(),
status :: pending | confirmed | cancelled | expired,
notes :: binary(),
reminder_sent :: boolean(),
confirmed_at :: calendar:datetime(),
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
%% ------------------- Лист ожидания (commercial, settings.waitlist_enabled) ----
-record(waitlist_entry, {
id :: binary(),
event_id :: binary(),
user_id :: binary(),
status :: waiting | promoted | left,
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
%% ------------------- Отзывы ------------------------------------------
-record(review, {
id :: binary(),
user_id :: binary(),
target_type :: calendar | event,
target_id :: binary(),
rating :: 1..5,
comment :: binary(),
status :: visible | hidden | deleted,
reason :: binary(),
likes :: non_neg_integer(),
dislikes :: non_neg_integer(),
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
%% Голос пользователя за отзыв (уникальность review_id+user_id — в транзакции)
-record(review_vote, {
id :: binary(),
review_id :: binary(),
user_id :: binary(),
value :: like | dislike,
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
%% ------------------- Жалобы и модерация ------------------------------
-record(report, {
id :: binary(),
reporter_id :: binary(),
target_type :: calendar | event | review,
target_id :: binary(),
reason :: binary(),
status :: pending | reviewed | dismissed,
created_at :: calendar:datetime(),
resolved_at :: calendar:datetime(),
resolved_by :: binary()
}).
-record(banned_word, {
id :: binary(),
word :: binary(),
added_by :: binary(), % id администратора, добавившего слово
added_at :: calendar:datetime()
}).
%% ------------------- Автомодерация -----------------------------------
-record(automod_settings, {
id :: binary(), % singleton <<"default">>
keyword_action :: reject | flag | censor,
match_mode :: word_boundary | substring,
report_threshold :: pos_integer(),
updated_at :: calendar:datetime(),
updated_by :: binary()
}).
-record(automod_hit, {
id :: binary(),
entity_type :: calendar | event | review,
entity_id :: binary(),
trigger :: keyword | report_threshold,
matched_words :: [binary()],
action_taken :: reject | flag | censor | freeze | hide,
status :: open | approved | rejected,
created_at :: calendar:datetime(),
resolved_at :: calendar:datetime() | undefined,
resolved_by :: binary()
}).
%% ------------------- Баг-трекер --------------------------------------
-record(ticket, {
id :: binary(),
reporter_id :: binary(),
error_hash :: binary(),
error_message :: binary(),
stacktrace :: binary(),
context :: binary(),
count :: non_neg_integer(),
first_seen :: calendar:datetime(),
last_seen :: calendar:datetime(),
status :: open | in_progress | resolved | closed,
assigned_to :: binary(),
resolution_note :: binary(),
closed_at :: calendar:datetime() | undefined,
source :: binary() % <<"backend">> | <<"frontend">> | <<"manual">>
}).
%% ------------------- Подписки ----------------------------------------
-record(subscription, {
id :: binary(),
user_id :: binary(),
plan :: monthly | quarterly | biannual | annual,
status :: active | expired | cancelled,
trial_used :: boolean(),
started_at :: calendar:datetime(),
expires_at :: calendar:datetime(),
created_at :: calendar:datetime(),
updated_at :: calendar:datetime()
}).
%% ------------------- Аудит -------------------------------------------
-record(admin_audit, {
id :: binary(),
admin_id :: binary(),
email :: binary(),
role :: atom(),
action :: binary(),
entity_type :: binary(),
entity_id :: binary(),
timestamp :: calendar:datetime(),
ip :: binary(),
reason :: binary()
}).
%% ------------------- Уведомления ------------------------
-record(notification, {
id :: binary(),
user_id :: binary(),
type :: booking_confirmed | event_reminder | event_cancelled |
specialist_invite | waitlist_promoted | custom,
title :: binary(),
body :: binary(),
is_read :: boolean(),
created_at :: calendar:datetime()
}).
%% Web Push subscription (Back#75). Prefs live in user.preferences:
%% notify_email / notify_push (boolean, default true).
-record(push_subscription, {
id :: binary(),
user_id :: binary(),
endpoint :: binary(),
p256dh :: binary(),
auth :: binary(),
created_at :: calendar:datetime()
}).
%% Upsert-счётчики админ-статистики (абсолютные значения).
%% key: atom() | {atom(), atom()} | {atom(), atom(), atom()}
%% users_total-подобные totals не храним (table_info),
%% размерности: {user_by_status, pending}, {event_by_type, single}, ...
-record(stats_counter, {
key :: term(),
value :: integer(),
updated_at :: calendar:datetime()
}).
%% Дневные бакеты: key = {Metric :: atom(), Date :: calendar:date()}.
-record(stats_daily, {
key :: {atom(), calendar:date()},
value :: integer(),
updated_at :: calendar:datetime()
}).
-record(node_metric, {
timestamp :: calendar:datetime(),
node :: atom(),
memory_total :: non_neg_integer(),
memory_processes :: non_neg_integer(),
memory_atom :: non_neg_integer(),
memory_binary :: non_neg_integer(),
memory_ets :: non_neg_integer(),
process_count :: non_neg_integer(),
run_queue :: non_neg_integer(),
mnesia_commits :: non_neg_integer(),
mnesia_failures :: non_neg_integer(),
table_sizes :: map(),
active_sessions :: non_neg_integer(),
ws_connections :: non_neg_integer(),
io_in :: non_neg_integer(),
io_out :: non_neg_integer(),
memory_available :: non_neg_integer(),
cpu_utilization :: float()
}).
-record(month_snapshot, {
id :: {binary(), binary()},
calendar_id :: binary(),
year_month :: binary(),
payload :: binary(),
storage :: mnesia | file,
file_rel :: binary(),
created_at :: calendar:datetime()
}).
-record(schema_migration, {
version :: string(),
applied_at :: calendar:datetime()
}).
%% Пилотные метрики ИИ-подсказок (PRODUCT-AI, Spec#22): дневные счётчики
%% shown/confirm/dismiss по паттерну. Id = {user_id, day, pattern_key, kind}.
-record(ai_hint_metric, {
id :: {binary(), binary(), binary(), binary()},
user_id :: binary(),
day :: binary(),
pattern_key :: binary(),
kind :: binary(),
count :: non_neg_integer(),
updated_at :: calendar:datetime()
}).