Перейти к основному содержимому

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 areaagent
Feature groupmy-performance
Статусdraft
Документdocs/06-services/rewards/api/agent/get-my-agent-performance.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (агент — в агентстве или независимый)
Auth policy / Permissionsrewards.performance.view-own (op32 — реестр ../README.md)
Scope (RBAC)платформа: роль agent в scope Platform (независимый) или Organization (в агентстве) — метрика не привязана к tenant
Record-levelAgentUserId = <sub актора из claims> — жёстко на сервере, не из query (агент видит только свою проекцию)
Tenant isolationне применяетсяAgentPerformanceEntity не тенантится (платформенный scope агента, ADR-0061 п.5, ADR-0070 п.6); выборка — все строки агента по AgentUserId (без tenant-фильтра); CompanyId — разрез строки, не tenant
Auditread-sensitive (суммы комиссий): в trace — actor; суммы не логируются

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/my/agent-performance
Success status200
Idempotency headerне применяется (read)
Correlationсквозной trace через заголовок traceparent

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
AgentUserIdidentity claims (sub)GuidДаRequired; не из queryRecord-level (own), ключ агрегации строк
IncludePerAgencyqueryboolНетdefault false

Периодных/paging-параметров нет: ответ — агрегат метрик за всю карьеру. IncludePerAgency=true добавляет опциональную разбивку PerAgency (по одной записи на строку проекции); по умолчанию отдаётся только суммарный агрегат.

Модель ответа AgentPerformanceModel

Агрегат строк проекции AgentPerformanceEntity по AgentUserId (дом полей/счётчиков/Money/достижений — там). Счётчики и суммы — SUM по строкам агента сквозь агентства; display-статус и достижения — производные на чтении. MoneyModel — общий контракт ../README.md.

ПолеТипОбязательностьИсточникMaskingОписание
AgentUserIdGuidДаclaimsАгент (эхо).
FixationsApprovedCountintДаSUM(строки.FixationsApprovedCount)Одобрено фиксаций сквозь всех застройщиков/агентства.
DealsWonCountintДаSUM(строки.DealsWonCount)Закрытых сделок с комиссией (по всем агентствам).
DealsLostCountintДаSUM(строки.DealsLostCount)Сорванных (отменённых после завершения).
CommissionAccruedTotalMoneyModelДаSUM(строки.CommissionAccruedTotal)суммы не в логиПожизненно начислено (gross), single-currency RUB.
CommissionPaidTotalMoneyModelДаSUM(строки.CommissionPaidTotal)суммы не в логиПожизненно выплачено (gross начислений Paid).
MembershipStatusstring (enum AgentMembershipDisplayStatus)Дапроизводный (см. алгоритм)InAgency (≥ 1 строка Active) / Independent (нет активных).
ActiveAgencyCompanyIdsIReadOnlyList<Guid>Да (может быть пустым)CompanyId строк с MembershipStatus = ActiveАктивные агентства агента; пусто — независимый.
FirstActivityAtDateTimeOffset?НетMIN(строки.FirstActivityAt)Начало карьеры (стаж).
LastActivityAtDateTimeOffset?НетMAX(строки.LastActivityAt)Последнее значимое событие.
AchievementsIReadOnlyList<AgentAchievementModel>Да (может быть пустым)производный (пороги над агрегатом)Достижения: Code/AwardedAt/Threshold? (вычисляются на чтении).
PerAgencyIReadOnlyList<AgentAgencyPerformanceModel>Нетстроки проекцииРазбивка по агентствам; заполняется при IncludePerAgency=true, иначе null/опущено.
GeneratedAtDateTimeOffsetДаnowМомент чтения (freshness).

ActiveAgencyCompanyIds заменяет прежнее одиночное CurrentAgencyCompanyId: с мульти-членством (ADR-0070) агент может состоять в нескольких агентствах одновременно, поэтому возвращается список.

AgentAgencyPerformanceModel (элемент PerAgency)

Метрики агента в одном агентстве — прямое отражение строки проекции пары (AgentUserId, CompanyId).

ПолеТипОбязательностьИсточникОписание
CompanyIdGuidДастрока.CompanyIdАгентство разреза.
FixationsApprovedCountintДастрокаОдобрено фиксаций в этом агентстве.
DealsWonCountintДастрокаЗакрытых сделок с комиссией в этом агентстве.
DealsLostCountintДастрокаСорванных в этом агентстве.
CommissionAccruedTotalMoneyModelДастрокаНачислено в этом агентстве (gross).
CommissionPaidTotalMoneyModelДастрокаВыплачено в этом агентстве (gross).
MembershipStatusstring (enum AgentMembershipStatus)Дастрока.MembershipStatusСостояние строки: Active / Left.
LeftAtDateTimeOffset?Нетстрока.LeftAtМомент выхода из этого агентства; null при Active.
LastActivityAtDateTimeOffset?Нетстрока.LastActivityAtПоследнее событие пары.

Точная net-доля агента по периодам и статусам подтверждения — в get-my-commission-summary.md; здесь суммы — gross-агрегаты проекции (индикативные пожизненные).

Алгоритм

Чтение и агрегация строк проекции (read-only):

  1. Контекст и доступ. Операцию выполняет агент (в агентстве или независимый); AgentUserId — только из claims (не из query); требуется право rewards.performance.view-own. Роль agent в scope Platform или Organization — доступно и независимому агенту (ADR-0061 п.5). Иначе — FORBIDDEN.
  2. Что читаем. Все строки проекции агента по AgentUserId (композитный ключ (AgentUserId, CompanyId) — по строке на агентство; без tenant-фильтра). Если строк нет (агент без событий) — возвращается «нулевая» модель, не 404.
  3. Агрегация метрик. Счётчики (FixationsApprovedCount/DealsWonCount/DealsLostCount) и денежные суммы (CommissionAccruedTotal/CommissionPaidTotal) — SUM по строкам агента (single-currency RUB — суммирование корректно); FirstActivityAt = MIN, LastActivityAt = MAX по строкам.
  4. Производный статус членства. MembershipStatus = InAgency, если у агента есть ≥ 1 строка с MembershipStatus = Active; иначе Independent. ActiveAgencyCompanyIds = CompanyId строк со статусом Active (пусто у независимого).
  5. Производные достижения. Achievements вычисляются детерминированно на чтении над агрегатом (пороги суммарных метрик, напр. «10 сделок») → список AgentAchievementModel (Code/AwardedAt/Threshold?).
  6. Разбивка (опционально). При IncludePerAgency=true — каждая строка проекции → элемент PerAgency (AgentAgencyPerformanceModel); иначе PerAgency опущено. Денежные поля → MoneyModel; GeneratedAt = now.
  7. Результат. AgentPerformanceModel → HTTP 200. Агент без строк — 200 с нулевыми счётчиками, CommissionAccruedTotal/PaidTotal = { Value: 0, Currency: RUB }, MembershipStatus = Independent, ActiveAgencyCompanyIds = [], Achievements = [], PerAgency = []/опущено (не 404).

Побочные эффекты

ТипДетали
Integration eventsnone (read-only)
External callsnone (проекция уже материализована consumer-ами)
Cache / projections / searchчтение строк проекции rewards.agent_performance по префиксу ключа (agent_user_id, индекс ix_agent_performance_agent); кеш не применяется (проекция обновляется событиями)
Notificationsnone

Ошибки и failure modes

Общие коды (FORBIDDEN 403, UNAUTHORIZED 401) — ../README.md. Специфичных кодов нет: пустая проекция — не ошибка (200 с нулями). Устаревание проекции (лаг consumer-ов) — не ошибка; свежесть отражает LastActivityAt/GeneratedAt.

Предусловия и постусловия

  • Предусловия. Актор — агент (в агентстве или независимый) с ролью agent в scope Platform/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. Тогда: 200 AgentPerformanceModel с пожизненными счётчиками (FixationsApprovedCount, DealsWonCount, DealsLostCountSUM по строкам), CommissionAccruedTotal/CommissionPaidTotal, производным MembershipStatus, ActiveAgencyCompanyIds и Achievements; GeneratedAt = now.
  • Агрегация сквозь агентства (SUM). Дано: у агента две строки — по одному агентству каждая (сделки в агентствах A и B). Когда: GET. Тогда: DealsWonCount/суммы = сумма по обеим строкам; при IncludePerAgency=truePerAgency из двух записей (A и B) с построчными метриками.
  • Агент без событий. Дано: агент, по которому строк проекции ещё нет (ни одного commission/membership- события). Когда: GET. Тогда: 200 с нулевыми счётчиками, CommissionAccruedTotal/PaidTotal = { Value: 0, Currency: RUB }, Achievements = [], MembershipStatus = Independent, ActiveAgencyCompanyIds = []не 404.
  • Независимый агент. Дано: агент без активных строк членства (все строки Left, scope Platform). Когда: 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. Тогда: 403 FORBIDDEN.

Совместимость и наблюдаемость

  • Модель 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; dashboard rewards-overview; runbook при рассинхроне проекции — docs/06-services/rewards/runbooks/rewards-agent-performance-rebuild.md (по конвенции).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы