LogLady (лог рольових подій TU: supervisor / operator / client manager)

#draft Модель (chathouse): LogLadyModel.ts (log_lady). Family-специфічні реалізації того самого концепту:

  • ChathouseLogLadyModel.ts, локальна колекція log_lady, пишеться напряму (LogLadyModel.create(...)).
  • UdateUdateLogLadyModel.ts, локальна колекція udate_log_lady, пишеться через UdateLogLadyService.ts.
  • GoldenGoldenLogLadyService.ts, зберігається у зовнішньому мікросервісі, пишеться через RMQ (namespace.GoldenLogLadyCreate/namespace.GoldenLog).
  • Prime — той самий підхід, що й Golden (зовнішній мікросервіс через RMQ, namespace.PrimeLog); TYPES.PrimeLogLadyService заявлений у DI, але локальної реалізації в цьому репо немає.

Пов’язані, але окремі колекції (не входять у порівняння нижче — інша семантика):

  • LogLadyEventsModel.ts (log_lady_events) — lifecycle TU (LadyEvent: created/deleted/activated/deactivated), вже уніфікована колекція для всіх family.
  • LogLadyCardModel.ts (log_lady_card) — події на рівні картки TU (create/set_cm/off_cm/set_lady/off_lady), теж вже уніфікована.

Формат рольових подій (supervisor/operator/client manager) описаних нижче — НЕ уніфікований: однакові за змістом поля мають різні назви та типи в кожній family. Це заважає читати логи по всіх family одним запитом (Golden/Prime — взагалі RMQ, не queryable локально).

Зведена таблиця полів (порівняння форматів + пропозиція перейменування)

#КонцепціяПропоную полеПропоную типChathouse log_ladyGolden (RMQ)Prime (RMQ)Udate udate_log_lady
1Час події, defaulttimestampnumber (default: Date.now)timestamp: numbertimestamp: numbertimestamp: number (за аналогією з golden)timestamp: number (default: Date.now)
3Хто ініціював діюinitiatorIdstring, userIdinitiatorId: stringбага: фактично хардкод директора / familyId ініціатора / 'system', не реальний actorinitiatorId: string — та сама бага (напр. operator_login пише operatorFamilyId замість реального actor’а)initiatorId: string — та сама багаinitiatorId: string — та сама бага
4Тип ініціатораinitiatorTypeLogInitiatorType (string enum)initiatorType: LogInitiatorTypenumeric enum, відрізняється від рештиinitiatorType: StackInitiatorLogType (string)initiatorType: StackInitiatorLogType (string)initiatorType: StackInitiatorLogType (string)
5Ід TU (lady) в stack-базіtuIdstringladyDbId: stringladyMongoId: stringladyMongoId: stringladyMongoId: string
6Зовнішній (family-side) ід TU(іде в data)number | stringapi_id: Mixed — по факту завжди хардкод 0, не використовуєтьсяladyId_api: numberprime_id (тип не задокументовано в схемі, за використанням — number)ladyId_api: number
7Ід запису supervisor’а (family-scope)assignmentFamilyId (+ role: StackRoles.supervisor)stringsupervisorFamilyId: stringsupervisorFamilyId: stringsupervisorFamilyId: stringsupervisorFamilyId: string (default: null)
8Ід запису operator’а (family-scope)assignmentFamilyId (+ role: StackRoles.operator)stringoperatorFamilyId: stringoperatorFamilyId: stringoperatorFamilyId: stringoperatorFamilyId: string (default: null)
9Ід запису client manager’а (family-scope)assignmentFamilyId (+ role: StackRoles.client_manager)stringcmFamilyId: stringє в реальній схемі, хоч і не згадана в нотатках по set_supervisor/operator (використовується лише для set_cm/off_cm, які нотатки для chathouse взагалі не описували)clManFamilyId: stringclManFamilyId: stringclManFamilyId: string (default: null)
10Скільки бонусів на момент подіїbonuses (лишається в data, бо реалізовано по-різному)family-specificbonuses: numberbonuses: { [key in GoldenStatisticsTypes]: number }об’єкт, а не числоbonuses: numberbonuses: number (default: 0, required)
11Позначка адмінаadminIdstring (⚠️ крім golden)admin: Mixed — по факту adminId (string), в реальному коді не заповнюється (лишається undefined)golden_admin: numberчисло, зовнішній api-ід адміна, НЕ adminId з базиprime_admin: stringudate_admin: string (default: null)
12Причина переміщення TU(планується видалити)StackResonMoveLady❌ відсутнєresonMoveLady: StackResonMoveLadyresonMoveLady: StackResonMoveLadyсам автор нотаток планує видалитиresonMoveLady: StackResonMoveLady (default: not_move)
13Кількість існуючих supervisor’ів на момент події(планується видалити)number❌ відсутнє❌ відсутнєexistSupervisorsпланується видалитиexistSupervisors: number (default: null)
14Тип подіїeventLogLadyEventTypeV2 (numeric, привести всіх до цього) і спростити, вказано нижчеevent: LogLadyEventType (numeric)event: StackLadyLogEventType (string)event: StackLadyLogEventType (string)event: StackLadyLogEventType (string)
15Family-приналежність записуfamilyFamilies (required)family: Families (в схемі required)не зберігається явно — визначається джерелом (RMQ namespace); додати при міграціїне зберігається явно; додати при міграціїне зберігається явно (колекція udate_log_lady вже сама по собі = udate); додати при міграції

existSupervisors - перепровірю

Старий event: export enum LogLadyEventType {   set_supervisor = 1,   off_supervisor = 2,   set_operator = 3,   off_operator = 4,   set_cm = 5,   off_cm = 6,   operator_login = 7, } Новий event: export enum LogLadyEventTypeV2 {   set = 1,   off = 2,   operator_login = 3, }

Поля за подіями (event) для кожної family

Той самий матеріал, що й вище, але розрізаний по конкретних подіях — корисно як чеклист при написанні конвертера/міграції.

Chathouse (log_lady)

ПодіяПоля, що записуютьсяПримітка
set_supervisor / off_supervisorinitiatorId, initiatorType, ladyDbId, supervisorFamilyId, bonuses, event, family, adminadmin варто заповнювати chathouseFamilySupervisor.admin, зараз не покращено
set_operator / off_operator / operator_logininitiatorId, initiatorType, ladyDbId, operatorFamilyId, supervisorFamilyId, bonuses, event, family, admininitiatorId/initiatorType потребують переробки (реальний actor не прокидається)
set_cm / off_cminitiatorId (хардкод '61d4457949e705e11376aea6', director), initiatorType (хардкод director), ladyDbId, cmFamilyId, bonuses: 0, api_id: 0, event, familyЦі поля не потрапили в оригінальні нотатки автора (top-of-file коментар описував лише supervisor/operator для chathouse) — виявлено з реального коду StackClientManagerService.ts

Golden (RMQ, зовнішній сервіс)

ПодіяПоля, що записуютьсяПримітка
set_supervisor / off_supervisorinitiatorId, initiatorType, ladyMongoId, ladyId_api, event, resonMoveLady, supervisorFamilyId, bonuses, golden_admin
set_operator / off_operatorinitiatorId, initiatorType, ladyMongoId, ladyId_api, event, resonMoveLady, operatorFamilyId, supervisorFamilyId, bonuses, golden_admin
set_cm / off_cmclManFamilyId, ladyMongoId, ladyId_api, event, resonMoveLady: user, bonuses: 0 (авто)initiatorId/initiatorType не прокидаються окремо в цьому виклику (GoldenLog routing)
operator_logininitiatorId (= operatorFamilyId, бага), initiatorType: operator, operatorFamilyId, supervisorFamilyId, ladyMongoId, ladyId_api, event, bonuses: 0, golden_admin

Prime (RMQ, зовнішній сервіс)

ПодіяПоля, що записуютьсяПримітка
operator_logininitiatorId (= operatorFamilyId, та сама бага), initiatorType: operator, ladyMongoId, prime_id, event, operatorFamilyId, bonuses, prime_admin, supervisorFamilyId
set_cm / off_cmclManFamilyId, initiatorId: 'director' (хардкод), initiatorType: director, ladyMongoId, prime_id, event, resonMoveLady: user, bonuses: 0
set_supervisor / off_supervisorinitiatorId, initiatorType, ladyMongoId, prime_id, event, resonMoveLady, supervisorFamilyId, existSupervisors, bonuses, prime_adminexistSupervisors — планується видалити
set_operator / off_operatorinitiatorId, initiatorType, ladyMongoId, prime_id, event, resonMoveLady, operatorFamilyId, supervisorFamilyId, bonuses, prime_admin

Udate (udate_log_lady)

ПодіяПоля, що записуютьсяПримітка
set_cm / off_cmclManFamilyId, initiatorId: 'director' (хардкод), initiatorType: director, ladyMongoId, ladyId_api, event, resonMoveLady: user, bonuses: 0
set_supervisor / off_supervisorinitiatorId, initiatorType, ladyMongoId, ladyId_api, event, resonMoveLady, supervisorFamilyId, udate_admin, existSupervisorsexistSupervisors — планується видалити
set_operator / off_operatorinitiatorId, initiatorType, ladyMongoId, ladyId_api, event, resonMoveLady, operatorFamilyId, supervisorFamilyId, udate_admin

TypeScript типи: поточний (старий) формат, по event для кожної family

Формалізація таблиць з розділу вище — по одному type/interface на кожну групу подій, для конвертерів/валідації при міграції. Це типи “як є зараз” — нічого спільного між family не перевикористовується (кожна family має власний, ізольований набір полів).

Chathouse (log_lady)

// set_supervisor / off_supervisor
interface IChathouseLogLady_Supervisor {
	initiatorId: string; // бага: хардкод director / familyId ініціатора / 'system', не реальний actor
	initiatorType: LogInitiatorType;
	ladyDbId: string;
	supervisorFamilyId: string;
	bonuses: number;
	event: LogLadyEventType.set_supervisor | LogLadyEventType.off_supervisor;
	family: Families.chathouse;
	admin: string; // adminId, зараз не заповнюється (chathouseFamilySupervisor.admin)
}
 
// set_operator / off_operator / operator_login
interface IChathouseLogLady_Operator {
	initiatorId: string; // та сама бага, що й у Supervisor
	initiatorType: LogInitiatorType;
	ladyDbId: string;
	operatorFamilyId: string;
	supervisorFamilyId: string;
	bonuses: number;
	event: LogLadyEventType.set_operator | LogLadyEventType.off_operator | LogLadyEventType.operator_login;
	family: Families.chathouse;
	admin: string;
}
 
// set_cm / off_cm — виявлено з реального коду StackClientManagerService.ts, не було в оригінальних нотатках
interface IChathouseLogLady_ClientManager {
	initiatorId: string; // хардкод '61d4457949e705e11376aea6' (director)
	initiatorType: LogInitiatorType.director; // хардкод
	ladyDbId: string;
	cmFamilyId: string;
	bonuses: 0; // хардкод
	api_id: 0; // хардкод, поле по факту не використовується
	event: LogLadyEventType.set_cm | LogLadyEventType.off_cm;
	family: Families.chathouse;
}
 
type IChathouseLogLadyEvent = IChathouseLogLady_Supervisor | IChathouseLogLady_Operator | IChathouseLogLady_ClientManager;

Golden (RMQ, зовнішній сервіс)

// set_supervisor / off_supervisor
interface IGoldenLogLady_Supervisor {
	initiatorId: string;
	initiatorType: StackInitiatorLogType;
	ladyMongoId: string;
	ladyId_api: number;
	event: StackLadyLogEventType.set_supervisor | StackLadyLogEventType.off_supervisor;
	resonMoveLady: StackResonMoveLady;
	supervisorFamilyId: string;
	bonuses: { [key in GoldenStatisticsTypes]?: number };
	golden_admin: number; // зовнішній api-ід адміна, НЕ adminId з бази
}
 
// set_operator / off_operator
interface IGoldenLogLady_Operator {
	initiatorId: string;
	initiatorType: StackInitiatorLogType;
	ladyMongoId: string;
	ladyId_api: number;
	event: StackLadyLogEventType.set_operator | StackLadyLogEventType.off_operator;
	resonMoveLady: StackResonMoveLady;
	operatorFamilyId: string;
	supervisorFamilyId: string;
	bonuses: { [key in GoldenStatisticsTypes]?: number };
	golden_admin: number;
}
 
// set_cm / off_cm — initiatorId/initiatorType не прокидаються окремо в цьому RMQ-виклику
interface IGoldenLogLady_ClientManager {
	clManFamilyId: string;
	ladyMongoId: string;
	ladyId_api: number;
	event: StackLadyLogEventType.set_cm | StackLadyLogEventType.off_cm;
	resonMoveLady: StackResonMoveLady.user;
	bonuses: 0;
}
 
// operator_login
interface IGoldenLogLady_OperatorLogin {
	initiatorId: string; // бага: фактично = operatorFamilyId
	initiatorType: StackInitiatorLogType.operator;
	operatorFamilyId: string;
	supervisorFamilyId: string;
	ladyMongoId: string;
	ladyId_api: number;
	event: StackLadyLogEventType.operator_login;
	bonuses: 0;
	golden_admin: number;
}
 
type IGoldenLogLadyEvent = IGoldenLogLady_Supervisor | IGoldenLogLady_Operator | IGoldenLogLady_ClientManager | IGoldenLogLady_OperatorLogin;

Prime (RMQ, зовнішній сервіс)

// set_supervisor / off_supervisor
interface IPrimeLogLady_Supervisor {
	initiatorId: string;
	initiatorType: StackInitiatorLogType;
	ladyMongoId: string;
	prime_id: number; // специфічне tuId для prime family
	event: StackLadyLogEventType.set_supervisor | StackLadyLogEventType.off_supervisor;
	resonMoveLady: StackResonMoveLady; // планується видалити
	supervisorFamilyId: string;
	existSupervisors: number; // планується видалити
	bonuses: number;
	prime_admin: string;
}
 
// set_operator / off_operator
interface IPrimeLogLady_Operator {
	initiatorId: string;
	initiatorType: StackInitiatorLogType;
	ladyMongoId: string;
	prime_id: number;
	event: StackLadyLogEventType.set_operator | StackLadyLogEventType.off_operator;
	resonMoveLady: StackResonMoveLady; // планується видалити
	operatorFamilyId: string;
	supervisorFamilyId: string;
	bonuses: number;
	prime_admin: string;
}
 
// set_cm / off_cm
interface IPrimeLogLady_ClientManager {
	clManFamilyId: string;
	initiatorId: 'director'; // хардкод
	initiatorType: StackInitiatorLogType.director; // хардкод
	ladyMongoId: string;
	prime_id: number;
	event: StackLadyLogEventType.set_cm | StackLadyLogEventType.off_cm;
	resonMoveLady: StackResonMoveLady.user;
	bonuses: 0;
}
 
// operator_login
interface IPrimeLogLady_OperatorLogin {
	initiatorId: string; // бага: фактично = operatorFamilyId
	initiatorType: StackInitiatorLogType.operator;
	ladyMongoId: string;
	prime_id: number;
	event: StackLadyLogEventType.operator_login;
	operatorFamilyId: string;
	bonuses: number;
	prime_admin: string;
	supervisorFamilyId: string;
}
 
type IPrimeLogLadyEvent = IPrimeLogLady_Supervisor | IPrimeLogLady_Operator | IPrimeLogLady_ClientManager | IPrimeLogLady_OperatorLogin;

Udate (udate_log_lady)

// set_supervisor / off_supervisor
interface IUdateLogLady_Supervisor {
	initiatorId: string;
	initiatorType: StackInitiatorLogType;
	ladyMongoId: string;
	ladyId_api: number;
	event: StackLadyLogEventType.set_supervisor | StackLadyLogEventType.off_supervisor;
	resonMoveLady: StackResonMoveLady; // планується видалити
	supervisorFamilyId: string;
	udate_admin: string;
	existSupervisors: number; // планується видалити
}
 
// set_operator / off_operator
interface IUdateLogLady_Operator {
	initiatorId: string;
	initiatorType: StackInitiatorLogType;
	ladyMongoId: string;
	ladyId_api: number;
	event: StackLadyLogEventType.set_operator | StackLadyLogEventType.off_operator;
	resonMoveLady: StackResonMoveLady; // планується видалити
	operatorFamilyId: string;
	supervisorFamilyId: string;
	udate_admin: string;
}
 
// set_cm / off_cm
interface IUdateLogLady_ClientManager {
	clManFamilyId: string;
	initiatorId: 'director'; // хардкод
	initiatorType: StackInitiatorLogType.director; // хардкод
	ladyMongoId: string;
	ladyId_api: number;
	event: StackLadyLogEventType.set_cm | StackLadyLogEventType.off_cm;
	resonMoveLady: StackResonMoveLady.user;
	bonuses: 0;
}
 
type IUdateLogLadyEvent = IUdateLogLady_Supervisor | IUdateLogLady_Operator | IUdateLogLady_ClientManager;

TypeScript типи: новий (уніфікований) формат

На відміну від старого формату (вище — 4 повністю ізольовані набори полів), новий формат складається з:

  • Спільної частини (ILogLadyEventCommon) — однакова буквально для всіх подій без винятку (id, timestamp, tuId, family, data).
  • Розбивки по групах подій (ILogLadyRoleEvent / ILogLadyLifecycleEvent) — бо різні event реально потребують різний набір полів (див. розділ нижче “Розбивка по подіях”).
  • Family-специфічної частини (data) — окремий тип на кожну family; тут лишається все, що не вдалось уніфікувати (напр. bonuses, бо в golden це об’єкт, а не число).

Подієвий enum спрощується: замість окремих set_supervisor/off_supervisor/set_operator/off_operator/set_cm/off_cm (6 значень) лишається set/off + окремо operator_login, а те, кого саме призначили/зняли (supervisor/operator/client manager), визначає поле role.

// Новий event — замінює LogLadyEventType (chathouse) і StackLadyLogEventType (golden/prime/udate)
enum LogLadyEventTypeV2 {
	set = 1,
	off = 2,
	operator_login = 3,
	// lifecycle TU, перенесено з окремої колекції log_lady_events (ILadyEventLog.event) - див. розділ нижче
	created = 4,
	deleted = 5,
	activated = 6,
	deactivated = 7,
}

Розбивка по подіях (event) — чому вона потрібна

На відміну від розбивки по role (яку ми визнали зайвою — там форма полів однакова для всіх ролей), розбивка по групах event виправдана, бо форма полів реально відрізняється:

  • set / off / operator_login — це рольові події: завжди мають initiatorId, initiatorType, role, assignmentFamilyId, adminId.
  • created / deleted / activated / deactivated (lifecycle, з колишньої log_lady_events) — взагалі не мають жодного з цих полів, бо пишуться без actor’а й без прив’язки до ролі.

Тобто критерій “чи варто дискримінувати” — чи справді відрізняється набір/форма полів. Для event — так (0 спільних полів понад ILogLadyEventCommon), для role — ні (усі три ролі мають однаковий assignmentFamilyId: string).

// Спільні поля - буквально для КОЖНОЇ події, без винятків
interface ILogLadyEventCommon<TFamily extends Families, TData> {
	id: string;
	timestamp: number;
	tuId: string; // замінює ladyDbId (chathouse) / ladyMongoId (golden/prime/udate)
	family: TFamily;
	data: TData;
}
 
// set / off / operator_login - рольові події, завжди мають actor'а і роль
interface ILogLadyRoleEvent<TFamily extends Families, TData> extends ILogLadyEventCommon<TFamily, TData> {
	event: LogLadyEventTypeV2.set | LogLadyEventTypeV2.off | LogLadyEventTypeV2.operator_login;
	initiatorId: string; // TODO: бага з реальним actor'ом лишається й після міграції - виправляється окремо
	initiatorType: StackInitiatorLogType;
	role: StackRoles; // supervisor | operator | client_manager; для operator_login завжди StackRoles.operator
	assignmentFamilyId: string; // замінює supervisorFamilyId / operatorFamilyId / cmFamilyId / clManFamilyId
	adminId: string; // ⚠️ у golden - зовнішнє число (`golden_admin`), а не adminId з бази
}
 
// created / deleted / activated / deactivated - lifecycle TU, жодних role/actor полів немає взагалі
interface ILogLadyLifecycleEvent<TFamily extends Families, TData> extends ILogLadyEventCommon<TFamily, TData> {
	event: LogLadyEventTypeV2.created | LogLadyEventTypeV2.deleted | LogLadyEventTypeV2.activated | LogLadyEventTypeV2.deactivated;
}
 
// Дискримінована унія по event - звужується автоматично при перевірці event у switch/if
type ILogLadyEvent<TFamily extends Families, TData> = ILogLadyRoleEvent<TFamily, TData> | ILogLadyLifecycleEvent<TFamily, TData>;

Перевага порівняно з попереднім варіантом (де role/assignmentFamilyId/adminId/initiatorId/initiatorType були просто позначені як опціональні ? в одному спільному типі): компілятор тепер не дозволить випадково підставити role в lifecycle-подію чи, навпаки, забути assignmentFamilyId в set/off — раніше опціональність цього не гарантувала (можна було або забути обов’язкове поле для role-події, або помилково додати зайве поле до lifecycle-події, і TS цього не ловив).

Family-специфічні типи (data)

Лишається тільки те, що реально не вдається привести до спільного вигляду.

// Chathouse - нічого специфічного, крім bonuses (число)
interface IChathouseLogLadyDataV2 {
	bonuses: number;
}
type IChathouseLogLadyEventV2 = ILogLadyEvent<Families.chathouse, IChathouseLogLadyDataV2>;
 
// Golden - ladyId_api (зовнішній ід) + bonuses як мапа по GoldenStatisticsTypes (не число)
interface IGoldenLogLadyDataV2 {
	ladyId_api: number;
	bonuses: { [key in GoldenStatisticsTypes]?: number };
}
type IGoldenLogLadyEventV2 = ILogLadyEvent<Families.golden, IGoldenLogLadyDataV2>;
 
// Prime - prime_id (зовнішній ід, специфічний для prime) + bonuses (число)
interface IPrimeLogLadyDataV2 {
	prime_id: number;
	bonuses: number;
}
type IPrimeLogLadyEventV2 = ILogLadyEvent<Families.prime, IPrimeLogLadyDataV2>;
 
// Udate - лише ladyId_api, bonuses в udate теж число, але лишено в data через різні типи в golden
interface IUdateLogLadyDataV2 {
	ladyId_api: number;
	bonuses: number;
}
type IUdateLogLadyEventV2 = ILogLadyEvent<Families.udate, IUdateLogLadyDataV2>;
 
// Повна унія - по family ТА по event одночасно
type ILogLadyEventV2 = IChathouseLogLadyEventV2 | IGoldenLogLadyEventV2 | IPrimeLogLadyEventV2 | IUdateLogLadyEventV2;

existSupervisors і resonMoveLady в нові типи не переносяться — заплановані на видалення (див. таблицю нижче). api_id (chathouse, хардкод 0, реально не використовується) також не переноситься.

Зведення: що глобальне, що family-специфічне

Тип поляСтарий форматНовий форматПеревикористовується?
id, timestamp, tuId, family, dataрізні набори по familyILogLadyEventCommon✅ глобальне, для КОЖНОЇ події
initiatorId, initiatorType, role, assignmentFamilyId, adminIdsupervisorFamilyId/operatorFamilyId/cmFamilyId/clManFamilyId + admin/golden_admin/prime_admin/udate_admin (окремі поля)ILogLadyRoleEvent (лише для set/off/operator_login)✅ глобальне, але лише для рольових подій — відсутнє у lifecycle
eventLogLadyEventType (numeric, 7 значень) / StackLadyLogEventType (string, 7 значень)LogLadyEventTypeV2 (7 значень: set/off/operator_login + 4 lifecycle)✅ глобальне (і спрощене для рольової частини)
bonusesрізний тип у golden (об’єкт) проти інших (число)IChathouseLogLadyDataV2.bonuses / IGoldenLogLadyDataV2.bonuses / …❌ family-специфічне (лишається в data)
ladyId_api / prime_id / api_idрізні назви й наявність у кожній familyIGoldenLogLadyDataV2.ladyId_api / IPrimeLogLadyDataV2.prime_id / IUdateLogLadyDataV2.ladyId_api❌ family-специфічне (chathouse взагалі не має)
existSupervisors, resonMoveLadyPrime + Udate (частково Golden)🗑️ видаляється, нікуди не переноситься

Чи варто робити жорсткі Assign/Unassign-інтерфейси на кожну роль?

Питання: замість плоского role: StackRoles + assignmentFamilyId: string у ILogLadyRoleEvent — робити окремі інтерфейси на кшталт IAssignSupervisorEvent / IUnassignSupervisorEvent / IAssignOperatorEvent / … (discriminated union по role, а не тільки по event/family)?

Поки що — ні, не варто. Причина: role/assignmentFamilyId сьогодні мають однакову форму для всіх трьох ролей (supervisor/operator/client manager) — просто string-ід запису в family. Жодна роль не додає своїх власних полів понад те, що вже описано в data (family-специфіка). Якщо форма даних однакова, окремі інтерфейси на роль лише збільшують кількість типів (3 ролі × 4 family × 2 дії set/off = 24 комбінації) без жодної додаткової перевірки компілятором — чистий boilerplate. Це відрізняється від розбивки по event вище — там форма полів дійсно різна (0 спільних полів у lifecycle проти 5 обов’язкових у рольових подій).

Приклад, як це виглядало б, якби знадобилось (для довідки, не для реалізації зараз):

// Так МОЖНА зробити, якщо колись роль отримає власні поля (яких зараз немає)
interface IAssignRoleEvent<TFamily extends Families, TData, TRole extends StackRoles> extends ILogLadyEventCommon<TFamily, TData> {
	event: LogLadyEventTypeV2.set;
	role: TRole;
	assignmentFamilyId: string;
}
interface IUnassignRoleEvent<TFamily extends Families, TData, TRole extends StackRoles> extends ILogLadyEventCommon<TFamily, TData> {
	event: LogLadyEventTypeV2.off;
	role: TRole;
	assignmentFamilyId: string;
}

Коли це стане виправданим (тригер для перегляду рішення):

  • Якщо для конкретної ролі з’явиться власне поле, якого немає в інших ролей (наприклад, лише в client manager — причина зняття, лише в supervisor — якийсь ліміт).
  • Якщо потрібна exhaustiveness-перевірка компілятором у switch (role) при записі логу (сьогодні цього немає — logLadyEvent({ role, assignmentFamilyId, ... }) і так приймає один спільний тип, бо поля однакові).

До того часу простіший варіант (role + assignmentFamilyId як звичайні поля в ILogLadyRoleEvent, без generic-параметризації по ролі) — саме те, що потрібно: менше типів, той самий рівень типобезпеки для наявних полів.

План уніфікації в одну колекцію

Мета — одна колекція (за аналогією з планом міграції в LogOperator), де family: Families вказує, як інтерпретувати data. Конкретні типи глобальних і family-специфічних полів — див. розділ “TypeScript типи: новий (уніфікований) формат” вище.

Поля, що плануються на видалення

ПолеДе зустрічаєтьсяПричина видалення (з нотаток)
existSupervisorsPrime, Udateне пояснено детально в нотатках — позначено як “видалити”
resonMoveLadyGolden, Prime, Udateпозначено як “видалити”; в chathouse цього поля й так немає

Об’єднання з lifecycle-логами (log_lady_events / ILadyEventLog)

LogLadyEventsModel.ts (log_lady_events) вже уніфікована по всіх family, але це окрема колекція з зовсім іншим, набагато простішим набором полів:

enum LadyEvent {
	Created = 'created',
	Deleted = 'deleted',
	Activated = 'activated ',
	Deactivated = 'deactivated',
}
interface ILadyEventLog {
	ladyId: string; // = tuId
	timestamp: number; // default Date.now()
	event: LadyEvent;
	family: Families;
}

У неї немає initiatorId/initiatorType/role/assignmentFamilyId/adminId — lifecycle TU (створення/видалення/активація/деактивація) сьогодні пишеться без actor’а й без прив’язки до ролі. Саме тому в новому форматі вище (розділ “Розбивка по подіях”) ці lifecycle-події винесені в окремий ILogLadyLifecycleEvent, який структурно не містить цих полів (а не позначає їх опціональними) — так компілятор гарантує, що lifecycle-запис ніколи не матиме зайвих рольових полів. Щоб влити ці записи в ту саму нову колекцію:

  1. Розширити LogLadyEventTypeV2 значеннями lifecycle-подій (окремий діапазон, щоб не плутати з set/off/operator_login) — уже враховано в enum вище (created = 4deactivated = 7).
  2. Записувати їх через ILogLadyLifecycleEvent<TFamily, TData> (розділ вище) — жодних змін в базовому типі не треба, він вже це підтримує.
  3. Міграція данихlog_lady_events переливається в log_lady (чи як буде названа нова уніфікована колекція) через маппінг ladyId → tuId, event → LogLadyEventTypeV2 (за enum вище); поля initiatorId/role/assignmentFamilyId/adminId просто відсутні в цих документах (а не null/undefined в required полі). Стару колекцію log_lady_events можна прибрати лише після того, як усі читачі (LadyCardLogService й аналогічні) перейдуть на нову колекцію.

⚠️ Якщо в майбутньому lifecycle-подіям все ж знадобиться actor (initiatorId) — це вже структурна зміна типу (ILogLadyLifecycleEvent довелось би розширити цими полями чи завести четвертий варіант унії), а не просто “почати заповнювати опціональне поле”.