GET /api/v1/my/agent-performance — Моя личная статистика (агент, user-centric)
Личная user-centric статистика агента за всю карьеру: пожизненные счётчики и суммы, агрегированные по
AgentUserId сквозь все компании, где агент работал (ADR-0061 п.5, ADR-0070 п.6). Проекция
AgentPerformanceEntity с мульти-членства имеет композитный ключ
(AgentUserId, CompanyId) — по одной строке результативности на агентство; личная сводка собирается
суммированием строк агента (SUM метрик по AgentUserId), а разбивка по агентствам возвращается
опционально (PerAgency). Display-статус членства (InAgency/Independent) и достижения — производные
на чтении (не хранятся). В отличие от agency-centric crm get-statistics
(якорь — один текущий AgencyCompanyId), эти метрики сохраняются при смене/выходе из агентства и
доступны и независимому агенту (без активного членства), и агенту в одном/нескольких агентствах.
Назначение
Дать агенту экран личного профиля/достижений: сколько фиксаций одобрено, сколько сделок закрыто/сорвано,
сколько комиссии начислено и выплачено за всю карьеру (суммарно по всем агентствам), текущий display-статус
членства (в агентстве / независимый), список активных агентств и достижения; при необходимости — разбивку
метрик по агентствам. Периодных фильтров нет — метрики пожизненные (для периодных денежных сводок —
get-my-commission-summary.md). Feature group — my-performance.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | rewards |
| API area | agent |
| Feature group | my-performance |
| Статус | draft |
| Документ | docs/06-services/rewards/api/agent/get-my-agent-performance.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (агент — в агентстве или независимый) |
| Auth policy / Permissions | rewards.performance.view-own (op32 — реестр ../README.md) |
| Scope (RBAC) | платформа: роль agent в scope Platform (независимый) или Organization (в агентстве) — метрика не привязана к tenant |
| Record-level | AgentUserId = <sub актора из claims> — жёстко на сервере, не из query (агент видит только свою проекцию) |
| Tenant isolation | не применяется — AgentPerformanceEntity не тенантится (платформенный scope агента, ADR-0061 п.5, ADR-0070 п.6); выборка — все строки агента по AgentUserId (без tenant-фильтра); CompanyId — разрез строки, не tenant |
| Audit | read-sensitive (суммы комиссий): в trace — actor; суммы не логируются |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | GET |
| Route | /api/v1/my/agent-performance |
| Success status | 200 |
| Idempotency header | не применяется (read) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
AgentUserId | identity claims (sub) | Guid | Да | Required; не из query | Record-level (own), ключ агрегации строк |
IncludePerAgency | query | bool | Нет | default false | — |
Периодных/paging-параметров нет: ответ — агрегат метрик за всю карьеру. IncludePerAgency=true добавляет
опциональную разбивку PerAgency (по одной записи на строку проекции); по умолчанию отдаётся только
суммарный агрегат.
Модель ответа AgentPerformanceModel
Агрегат строк проекции AgentPerformanceEntity по AgentUserId (дом
полей/счётчиков/Money/достижений — там). Счётчики и суммы — SUM по строкам агента сквозь агентства;
display-статус и достижения — производные на чтении. MoneyModel — общий контракт ../README.md.
| Поле | Тип | Обязательность | Источник | Masking | Описание |
|---|---|---|---|---|---|
AgentUserId | Guid | Да | claims | — | Агент (эхо). |
FixationsApprovedCount | int | Да | SUM(строки.FixationsApprovedCount) | — | Одобрено фиксаций сквозь всех застройщиков/агентства. |
DealsWonCount | int | Да | SUM(строки.DealsWonCount) | — | Закрытых сделок с комиссией (по всем агентствам). |
DealsLostCount | int | Да | SUM(строки.DealsLostCount) | — | Сорванных (отменённых после завершения). |
CommissionAccruedTotal | MoneyModel | Да | SUM(строки.CommissionAccruedTotal) | суммы не в логи | Пожизненно начислено (gross), single-currency RUB. |
CommissionPaidTotal | MoneyModel | Да | SUM(строки.CommissionPaidTotal) | суммы не в логи | Пожизненно выплачено (gross начислений Paid). |
MembershipStatus | string (enum AgentMembershipDisplayStatus) | Да | производный (см. алгоритм) | — | InAgency (≥ 1 строка Active) / Independent (нет активных). |
ActiveAgencyCompanyIds | IReadOnlyList<Guid> | Да (может быть пустым) | CompanyId строк с MembershipStatus = Active | — | Активные агентства агента; пусто — независимый. |
FirstActivityAt | DateTimeOffset? | Нет | MIN(строки.FirstActivityAt) | — | Начало карьеры (стаж). |
LastActivityAt | DateTimeOffset? | Нет | MAX(строки.LastActivityAt) | — | Последнее значимое событие. |
Achievements | IReadOnlyList<AgentAchievementModel> | Да (может быть пустым) | производный (пороги над агрегатом) | — | Достижения: Code/AwardedAt/Threshold? (вычисляются на чтении). |
PerAgency | IReadOnlyList<AgentAgencyPerformanceModel> | Нет | строки проекции | — | Разбивка по агентствам; заполняется при IncludePerAgency=true, иначе null/опущено. |
GeneratedAt | DateTimeOffset | Да | now | — | Момент чтения (freshness). |
ActiveAgencyCompanyIds заменяет прежнее одиночное CurrentAgencyCompanyId: с мульти-членством (ADR-0070)
агент может состоять в нескольких агентствах одновременно, поэтому возвращается список.
AgentAgencyPerformanceModel (элемент PerAgency)
Метрики агента в одном агентстве — прямое отражение строки проекции пары (AgentUserId, CompanyId).
| Поле | Тип | Обязательность | Источник | Описание |
|---|---|---|---|---|
CompanyId | Guid | Да | строка.CompanyId | Агентство разреза. |
FixationsApprovedCount | int | Да | строка | Одобрено фиксаций в этом агентстве. |
DealsWonCount | int | Да | строка | Закрытых сделок с комиссией в этом агентстве. |
DealsLostCount | int | Да | строка | Сорванных в этом агентстве. |
CommissionAccruedTotal | MoneyModel | Да | строка | Начислено в этом агентстве (gross). |
CommissionPaidTotal | MoneyModel | Да | строка | Выплачено в этом агентстве (gross). |
MembershipStatus | string (enum AgentMembershipStatus) | Да | строка.MembershipStatus | Состояние строки: Active / Left. |
LeftAt | DateTimeOffset? | Нет | строка.LeftAt | Момент выхода из этого агентства; null при Active. |
LastActivityAt | DateTimeOffset? | Нет | строка.LastActivityAt | Последнее событие пары. |
Точная net-доля агента по периодам и статусам подтверждения — в get-my-commission-summary.md; здесь суммы — gross-агрегаты проекции (индикативные пожизненные).
Алгоритм
Чтение и агрегация строк проекции (read-only):
- Контекст и доступ. Операцию выполняет агент (в агентстве или независимый);
AgentUserId— только из claims (не из query); требуется правоrewards.performance.view-own. Рольagentв scopePlatformилиOrganization— доступно и независимому агенту (ADR-0061 п.5). Иначе —FORBIDDEN. - Что читаем. Все строки проекции агента по
AgentUserId(композитный ключ(AgentUserId, CompanyId)— по строке на агентство; без tenant-фильтра). Если строк нет (агент без событий) — возвращается «нулевая» модель, не404. - Агрегация метрик. Счётчики (
FixationsApprovedCount/DealsWonCount/DealsLostCount) и денежные суммы (CommissionAccruedTotal/CommissionPaidTotal) —SUMпо строкам агента (single-currency RUB — суммирование корректно);FirstActivityAt = MIN,LastActivityAt = MAXпо строкам. - Производный статус членства.
MembershipStatus = InAgency, если у агента есть ≥ 1 строка сMembershipStatus = Active; иначеIndependent.ActiveAgencyCompanyIds=CompanyIdстрок со статусомActive(пусто у независимого). - Производные достижения.
Achievementsвычисляются детерминированно на чтении над агрегатом (пороги суммарных метрик, напр. «10 сделок») → списокAgentAchievementModel(Code/AwardedAt/Threshold?). - Разбивка (опционально). При
IncludePerAgency=true— каждая строка проекции → элементPerAgency(AgentAgencyPerformanceModel); иначеPerAgencyопущено. Денежные поля →MoneyModel;GeneratedAt = now. - Результат.
AgentPerformanceModel→ HTTP200. Агент без строк —200с нулевыми счётчиками,CommissionAccruedTotal/PaidTotal = { Value: 0, Currency: RUB },MembershipStatus = Independent,ActiveAgencyCompanyIds = [],Achievements = [],PerAgency = []/опущено (не404).
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | none (read-only) |
| External calls | none (проекция уже материализована consumer-ами) |
| Cache / projections / search | чтение строк проекции rewards.agent_performance по префиксу ключа (agent_user_id, индекс ix_agent_performance_agent); кеш не применяется (проекция обновляется событиями) |
| Notifications | none |
Ошибки и failure modes
Общие коды (FORBIDDEN 403, UNAUTHORIZED 401) — ../README.md. Специфичных кодов нет:
пустая проекция — не ошибка (200 с нулями). Устаревание проекции (лаг consumer-ов) — не ошибка;
свежесть отражает LastActivityAt/GeneratedAt.
Предусловия и постусловия
- Предусловия. Актор — агент (в агентстве или независимый) с ролью
agentв scopePlatform/Organizationи правомrewards.performance.view-own. Строки проекцииAgentPerformanceEntityпо агенту могут существовать (одна/несколько — по числу агентств) или отсутствовать (агент без активности). - Постусловия. Операция только читает: состояние не меняется, событий нет. Возвращена одна модель
AgentPerformanceModel— агрегат строк агента поAgentUserId = sub; при отсутствии строк — «нулевая» модель (MembershipStatus = Independent,ActiveAgencyCompanyIds = [],Achievements = []), а не404.
Интеграционная последовательность (наполнение проекции). Строки проекции материализуются асинхронно
consumer-ами rewards по паре (AgentUserId, CompanyId): денежные и сделочные счётчики —
agent-performance-projection.md (события
commission-accrual-created/-status-changed); одобренные фиксации —
crm-fixation-approved.md; состояние членства строки
(MembershipStatus пары) — agent-membership-changed.md.
Метод читает готовые строки и агрегирует их на чтении; окно eventual-consistency после недавнего события —
секунды, свежесть отражают LastActivityAt/GeneratedAt. Агрегация — по AgentUserId сквозь все компании,
поэтому счётчики переживают выход/смену агентства (ADR-0061 п.5, ADR-0070 п.6): membership-событие меняет
статус только своей строки, а earned-метрики строк сохраняются.
Acceptance-критерии
- Happy path. Дано: агент с правом
rewards.performance.view-own, наполненные строки проекции (были фиксации, сделки, начисления). Когда:GET /api/v1/my/agent-performance. Тогда:200AgentPerformanceModelс пожизненными счётчиками (FixationsApprovedCount,DealsWonCount,DealsLostCount—SUMпо строкам),CommissionAccruedTotal/CommissionPaidTotal, производнымMembershipStatus,ActiveAgencyCompanyIdsиAchievements;GeneratedAt = now. - Агрегация сквозь агентства (SUM). Дано: у агента две строки — по одному агентству каждая (сделки в
агентствах A и B). Когда:
GET. Тогда:DealsWonCount/суммы = сумма по обеим строкам; приIncludePerAgency=true—PerAgencyиз двух записей (A и B) с построчными метриками. - Агент без событий. Дано: агент, по которому строк проекции ещё нет (ни одного commission/membership-
события). Когда:
GET. Тогда:200с нулевыми счётчиками,CommissionAccruedTotal/PaidTotal = { Value: 0, Currency: RUB },Achievements = [],MembershipStatus = Independent,ActiveAgencyCompanyIds = []— не404. - Независимый агент. Дано: агент без активных строк членства (все строки
Left, scopePlatform). Когда:GET. Тогда:200с пожизненными метриками, сохранёнными при выходе (ADR-0061 п.5, ADR-0070 п.6);MembershipStatus = Independent,ActiveAgencyCompanyIds = []. - Мульти-членство. Дано: агент состоит одновременно в двух агентствах (две строки
Active). Когда:GET. Тогда:MembershipStatus = InAgency,ActiveAgencyCompanyIdsсодержит обаCompanyId. - Метрики сохраняются после смены агентства. Дано: агент вышел из агентства A (строка A →
Left) и вступил в агентство B. Когда:GET. Тогда: счётчики и суммы за карьеру не обнулены (earned-метрики строки A сохранены);MembershipStatus = InAgency,ActiveAgencyCompanyIds = [B]. - Отставание проекции. Дано: только что завершённая сделка ещё не обработана consumer-ом
(agent-performance-projection.md). Когда:
GET. Тогда:200с прежними значениями (eventual consistency, окно — секунды); ошибки нет. - Чужая проекция недоступна. Дано: попытка передать
AgentUserIdв query. Когда:GET. Тогда: параметр игнорируется — ключ берётся из claims (sub); агент всегда видит только свою проекцию. - Нет права. Дано: у актора нет
rewards.performance.view-own. Когда:GET. Тогда:403FORBIDDEN.
Совместимость и наблюдаемость
- Модель
AgentPerformanceModel: новые поля/достижения — additive nullable; удаление/переименование/смена типа поля или значения enum (AgentMembershipDisplayStatus) — breaking (migration + rollout). ПереходCurrentAgencyCompanyId : Guid?→ActiveAgencyCompanyIds : Guid[](мульти-членство, ADR-0070) — breaking для клиента (координируется с фронтом). Формулы агрегации (SUMпо строкам) стабильны; смена семантики (напр. базаCommissionPaidTotal) — breaking для клиента. - Logs:
AgentUserId,MembershipStatus(суммы/счётчики не логируются — Sensitive); metrics:rewards_api_request_duration_seconds{route}; traces: OTel, correlation черезtraceparent; dashboardrewards-overview; runbook при рассинхроне проекции —docs/06-services/rewards/runbooks/rewards-agent-performance-rebuild.md(по конвенции).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Projection: AgentPerformanceEntity
- API (2): GET /api/agent/crm/statistics — Статистика агентства/агента, Rewards API — реестр операций и общие контрактные модели
- Use Cases (1): UC-RWD-006. Агент смотрит личную статистику за всю карьеру (user-centric)
- Консюмеры (1): Consumer: CRM Fixation Approved
- registries (1): Реестр API-методов PIN
Связанные документы
- Проекция (дом полей/enum/
Money/достижения): ../../domain/agent-performance.md. - Модели
MoneyModel, общий конверт/ошибки — ../README.md. - Смежные операции: get-my-commission-summary.md (периодные net-суммы),
list-my-accruals.md; agency-centric параллель — crm
get-statistics.md. - Use-case: UC-RWD-006.
- ADR-0061 (п.5), ADR-0070 (п.6 — композитный ключ
(AgentUserId, CompanyId), мульти-членство).