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

GET /api/v1/referral-links/{referralLinkId}/stats — Статистика одной реферальной ссылки

Назначение

Детальная статистика конкретной ссылки владельца: переходы (VisitsCount), регистрации по журналу, запрошенные лиды, реферальные связи (активные/всего) и сделки (фаза 2). Владелец оценивает эффективность конкретного канала/размещения — экран деталей ссылки на странице /profile/partner. Реферальная связь — основание будущих выплат (referral-bond.md). Показатели — агрегаты по журналу/связям + счётчик VisitsCount (решение №6 README.md: без ClickHouse/Redis).

Metadata

ПолеЗначение
Сервис/контекстreferral
API areapublic
Feature grouppartner-stats
Статусdraft
Документdocs/06-services/referral/api/public/get-link-stats.md

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

ПроверкаЗначение
Actor typeUser (владелец ссылки)
Auth policy / Permissionsreferral.links.view-self (каталог — permission.md)
Scope (RBAC)self-service
Record-levelстрого своя ссылка: Id == referralLinkId && OwnerUserId == actor.UserId; чужая/несуществующая → REFERRAL_LINK_NOT_FOUND без раскрытия существования (INV-REF-L4)

HTTP-контракт

ПолеЗначение
MethodGET
Route/api/v1/referral-links/{referralLinkId}/stats
Success status200
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДаRequiredrecord-level фильтр OwnerUserId
ReferralLinkIdroute:referralLinkIdGuidДанепустой; невалидный Guid → 400untrusted route-параметр

Body и query-параметры отсутствуют. Период (from/to) в первой итерации не поддерживается: показатели накопительные с момента создания ссылки — VisitsCount не имеет истории по датам (единственный счётчик-колонка, referral-link.md); периодные срезы по регистрациям/связям — кандидат на additive-расширение (from/to по RegisteredAt/BoundAt) без breaking change.

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

ПолеТипОбязательностьИсточникMaskingОписание
ReferralLinkIdGuidДаReferralLinkEntity.IdИдентификатор ссылки.
UrlstringДаReferralLinkEntity.UrlТекущий slug (заголовок карточки статистики).
Descriptionstring?НетReferralLinkEntity.DescriptionОписание.
Channelstring (enum ReferralChannel)ДаReferralLinkEntity.ChannelКанал ссылки (дом enum — referral-link.md).
IsDefaultboolДаReferralLinkEntity.IsDefaultПризнак default-ссылки.
CreatedAtDateTimeOffsetДаReferralLinkEntity.CreatedAtС какого момента копятся показатели.
VisitsCountlongДаReferralLinkEntity.VisitsCountПереходы: инкременты validateReferralLink (validate-referral-link.md).
RegistrationsCountintДаcomputed по журналуРегистрации/привязки (Initial + Rebind); та же семантика, что registrationsCount.
LeadsRequestedCountintДаcomputed по журналу (LeadRequested = true)Сколько привязок запросило карточку лида в CRM (referral-registration.md). Факт создания/жизни лида — в CRM.
ActiveBondsCountintДаcomputed по связям (Status = Active)Действующие реферальные связи через ссылку (производный — referral-link.md).
TotalBondsCountintДаcomputed по связям (все статусы)Все связи, включая Replaced/Revoked (разбор admin-коррекций).
DealsCountint?Нетфаза 2 (см. «Совместимость»)Сделки CRM, дошедшие из лидов этой ссылки. В первой итерации всегда nullreferral не располагает данными сделок.

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

  • Предусловия. Пользователь авторизован. Запрашиваемая ссылка существует в системе (в т.ч. soft-deleted) и принадлежит актору. Журнал регистраций и реферальные связи наполняются асинхронно привязкой (consumer-ы identity, attach-rules.md) — показатели свежи с точностью до лага привязки (SLO < 1 мин, README.md); VisitsCount актуален на момент последнего успешного validate-referral-link.md.
  • Постусловия. Состояние не меняется (чистое чтение): ни ссылка, ни журнал, ни связи не модифицируются, события не публикуются, VisitsCount этой операцией не инкрементируется. Ответ — снимок счётчиков на момент запроса.

Алгоритм

  1. Контекст и доступ. Пользователь авторизован; требуется permission referral.links.view-self (иначе FORBIDDEN).
  2. Проверка входа. ReferralLinkId непустой валидный Guid, иначе ValidationError.
  3. Проверка владения. Ссылка должна принадлежать актору (OwnerUserId == UserId); иначе → REFERRAL_LINK_NOT_FOUND (record-level, INV-REF-L4: чужая ссылка неотличима от несуществующей). Статистика доступна и для soft-deleted ссылки владельца (история канала не пропадает после удаления ссылки).
  4. Сбор показателей. По ссылке считаются: число регистраций (RegistrationsCount), число запрошенных лидов (LeadsRequestedCount — записи журнала с LeadRequested = true), число действующих связей (ActiveBondsCountStatus = Active) и всех связей (TotalBondsCount). VisitsCount берётся из счётчика ссылки; DealsCount = null (фаза 2). Расхождение счётчиков на единицы при конкурентной привязке допустимо для статистики.
    • Скоуп по ссылке. Все показатели журнала и связей считаются строго по этой ссылке (ReferralLinkId = referralLinkId) — в отличие от get-partner-stats.md, где агрегация идёт по владельцу (ReferrerUserId) и по каналам.
    • Семантика воронки. Открытия ссылки (правило R4, validate-referral-link.md) растят только VisitsCount, но не создают записи журнала и связи. Поэтому штатно VisitsCount >= RegistrationsCount >= ActiveBondsCount — это воронка «переход → регистрация → связь», а не рассинхрон. Обратное соотношение (RegistrationsCount > VisitsCount) возможно лишь как краевой эффект best-effort-инкремента VisitsCount (потеря инкремента, ADR-0056) и трактуется клиентом как норма, а не ошибка.
    • LeadsRequestedCount — не число живых лидов. Показатель считает факты запроса лида по привязке (журнал LeadRequested = true), а не активные карточки лидов CRM: лид мог быть закрыт/ объединён на стороне CRM. Для актуального состояния лидов клиент обращается к CRM.

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
ValidationError400ReferralLinkId пустой/невалидный Guid«Некорректный идентификатор ссылки»no retry
REFERRAL_LINK_NOT_FOUND404Ссылка не существует или принадлежит другому пользователю (INV-REF-L4 — без раскрытия)«Ссылка не найдена»no retry
FORBIDDEN403Нет referral.links.view-self«Недостаточно прав»no retry
UNAUTHORIZED401Нет/просрочен access-токен«Требуется авторизация»retry после refresh-token

Критерии приёмки

  • Given авторизованный владелец ссылки, по которой были переходы, регистрации и активные связи, When запрашивается статистика ссылки, Then 200 с VisitsCount из счётчика ссылки, RegistrationsCount/LeadsRequestedCount из журнала, ActiveBondsCount/TotalBondsCount из связей и DealsCount = null (фаза 2).
  • Given только что созданная ссылка без активности, When запрос, Then 200 со всеми счётчиками = 0 (не 404), DealsCount = null.
  • Given ссылка принадлежит другому пользователю или не существует, When запрос, Then REFERRAL_LINK_NOT_FOUND (404) — существование не раскрывается (INV-REF-L4).
  • Given soft-deleted ссылка владельца, When запрос, Then 200 с исторической статистикой — показатели канала удалением ссылки не обнуляются.
  • Given актор без permission referral.links.view-self, When запрос, Then FORBIDDEN (403); при отсутствии/просрочке токена — UNAUTHORIZED (401).
  • Given конкурентная привязка ровно в момент чтения, When запрос, Then допускается расхождение счётчиков на единицы — статистика eventually consistent, чтение не блокирует привязку.
  • Given по ссылке было N переходов, M < N из которых завершились регистрацией, When запрос, Then VisitsCount = N, RegistrationsCount = M, VisitsCount >= RegistrationsCount — переходы без регистрации (в т.ч. правило R4) не создают записей журнала, воронка сохраняется.

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

  • Новые optional поля ответа (в т.ч. периодные срезы from/to) — additive; удаление/ переименование/смена типа — breaking (migration + rollout).
  • DealsCount (фаза 2): контрактно поле присутствует с первой версии как int? и равно null, чтобы включение фазы 2 не меняло схему ответа. Источник фазы 2 — событие завершённой сделки контекста crm (топик pin.crm.deals.v1): CRM ведёт ReferralLinkId на карточке лида (получен из ReferralLeadAttachRequestedKafkaEventattach-rules.md, шаг 6) и включает его в событие сделки; referral заведёт consumer и таблицу фактов сделок для подсчёта per ссылка/канал. До согласования контракта с crm фаза 2 не активируется; расширение реестра consumed-событий фиксируется в README.md при активации.
  • Unknown Channel при чтении — fallback Other + warning (referral-link.md).
  • Logs: UserId, LinkId, длительность счётных запросов (без PII); metrics: referral_link_stats_get_total{result}, histogram referral_stats_query_duration_seconds; traces — сквозной trace через traceparent. Dashboard — referral-overview; алерт на p95 длительности счётных запросов > 300 мс (SLO public API, README.md).

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

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

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