BeachGuide · Rating Engine v2

Формула рейтинга: как считается каждый вес
и как считаются реальные пляжи

Разбор движка оценки (0–10) по строкам кода, пошаговые примеры по каждому компоненту, три полных прогона на пляжах-архетипах и адверсариальная оценка формулы на качество ранжирования.

источник: rating.engine.ts + ingest.scores.repository.ts · сгенерировано 2026-06-23 · числа в примерах иллюстративны (реальные cohort-статы берутся из БД на пересчёте)

1. Главная формула

score = Σ ( normalizedScore_i × weight_i ) // по всем 17 компонентам ↓ score = min( score , water_cap ) // жёсткий потолок по качеству воды ↓ если нет НИ ОДНОГО objective/reputation сигнала → score = null (badge "pending") dataCoverage = round( Σ весов компонентов_с_данными × 100 ) // % покрытия, отдельно от score

Три железных правила движка (rating.engine.ts:365–437):

  • Веса не перераспределяются. Нет данных у компонента его вклад = 0, но вес не уходит к соседям. Отсутствие данных = прямая потеря очков.
  • Каждый subscore нормирован в 0–10, затем умножается на вес. Сумма весов = 1.00, поэтому итог тоже в 0–10.
  • Водный кэп применяется последним — поверх суммы, как «ниже этого нельзя».
scorebadge
≥ 8.5excellentотличный
≥ 7.0goodхороший
≥ 5.5averageсредний
< 5.5poorслабый
nullpendingнет рейтингового сигнала

2. Все веса (Σ = 1.00)

REP google_rating_bayesian0.12
REP volume_score0.12
OBJ water_quality0.10
REP tripadvisor_rating_bayesian0.10
OBJ infrastructure0.08
REP google_reviews_score0.07
OBJ accessibility0.06
REP tripadvisor_reviews_score0.06
OBJ beach_profile0.05
OBJ nature_environment0.04
CNF source_coverage0.04
OBJ climate_seasonality0.03
OBJ crowding_comfort0.03
REP rating_consistency0.03
CNF data_freshness0.03
CNF source_diversity0.03
OBJ awards0.01

OBJECTIVE  Σ 0.40

Факты о самом пляже: вода, инфраструктура, доступность, профиль, природа, климат, загрузка, награды.

REPUTATION  Σ 0.50

Внешние оценки: Google/TripAdvisor рейтинги и объёмы, популярность (volume), согласованность.

Сразу видна асимметрия: репутация (0.50) весит больше, чем объективные качества пляжа (0.40). Половина рейтинга — «насколько пляж популярен/обсуждаем», а не «насколько он хорош». К этому вернёмся в оценке.

3. Как считается каждый компонент

Для каждого — точная формула из кода и числовой пример. Subscore всегда 0–10, потом × вес.

OBJ Objective — факты пляжа

water_quality · 0.10

obs.water_quality_classification (EEA)
excellent→10 · good→8 · sufficient→5.5 poor→2 · prohibited/unsafe→0 ► good = 8.0 → вклад 8.0×0.10 = 0.80 ⚠ poor/prohibited ещё включают water_cap

infrastructure · 0.08

core.beach_facility
min(10, кол-во_удобств × 1.25) ► 5 удобств = 6.25 → 6.25×0.08 = 0.50 8+ удобств упираются в 10

accessibility · 0.06

core.beach_transport
4.0 базы +парковка2 +транспорт1.5 +аэропорт(≤25→2,≤75→1) +пешком −лестницы ► парковка+аэропорт≤25 = 8.0 → 0.48

beach_profile · 0.05

core.beach_profile
4.0 + (заполнено_полей / 8) × 6 ► 5 из 8 полей = 7.75 → 7.75×0.05 = 0.39

nature_environment · 0.04

conservation_zone + cleaning_snapshot
conservation: 6.0 +флаги×0.75 +eco×0.1 nature = avg(conservation, cleaning) ► 3 флага+eco5 = 8.75 → 8.75×0.04 = 0.35

climate_seasonality · 0.03

beach_monthly_profile (лето, мес 6–8)
вода 24–28°C→10 · 21–24→8 · >29→7 + солнце (≥8ч +0.5, <5ч −0.5) ► 25°C+солнце = 10 → 10×0.03 = 0.30

crowding_comfort · 0.03

beach_monthly_profile.crowdScore
avg(crowdScore) // выше = комфортнее ► 6.0 → 6.0×0.03 = 0.18

awards · 0.01

content.beach_award (active)
есть активная награда → 10, иначе null ► Голубой флаг = 10 → 10×0.01 = 0.10 ⚠ макс вклад всего 0.10 — почти косметика

REP Reputation — внешние оценки

google_rating_bayesian · 0.12

beach_external_ref.google_maps
байес-сглаживание к среднему региона (см. раздел 4) ► 4.7@1200отз → 9.26 → ×0.12 = 1.11

volume_score · 0.12

rawSnapshot.volume / search_volume
log1p(volume) / log1p(P95_региона) × 10 ► 5000 при P95 9000 = 9.35 → ×0.12 = 1.12

tripadvisor_rating_bayesian · 0.10

beach_external_ref.tripadvisor
тот же байес, своя cohort-статистика ► 4.5@400отз → 8.89 → ×0.10 = 0.89

google_reviews_score · 0.07

google_maps reviewCount
log1p(reviews) / log1p(P95) × 10 ► 1200 при P95 6000 = 8.15 → ×0.07 = 0.57

tripadvisor_reviews_score · 0.06

tripadvisor reviewCount
log-объём отзывов TA ► 400 при P95 2000 = 7.89 → ×0.06 = 0.47

rating_consistency · 0.03

|google − tripadvisor|
≤0.5→10 · ≤1→8 · ≤2→5 · иначе 3 нужны ОБА рейтинга, иначе null ► разница 0.37 → 10 → ×0.03 = 0.30

CNF Confidence — мета (НЕ снимают «pending»)

source_coverage · 0.04

core.beach.dataCompleteness
dataCompleteness / 10 ► 90 → 9.0 → 9.0×0.04 = 0.36 ⚠ единственный канал, где «полнота» (вкл. медиа+описание) течёт в score

data_freshness · 0.03

newestSourceAt
≤90д→10 · ≤180→8 · ≤365→6 · ≤730→4 · иначе 2 ► 30 дней → 10 → ×0.03 = 0.30

source_diversity · 0.03

кол-во семейств источников (0–9)
min(10, sourceCount/5 × 10) ► 5+ источников = 10 → ×0.03 = 0.30

4. Два нетривиальных механизма

Байесовское сглаживание рейтинга

C = max(1, медиана_отзывов_региона || 50) corrected = (n / (n+C)) · rating + (C / (n+C)) · среднее_региона score = corrected / 5 × 10 // rating.engine.ts:290–310

Чем меньше отзывов n, тем сильнее оценку тянет к среднему по региону. Цель — не дать пляжу «5.0 при 3 отзывах» взлететь на вершину.

пляжratingотзывов nC→ score (0–10)
популярный4.712002509.26
hidden gem4.9352508.40
накрутка5.042508.31
Видно обе стороны: накрутка 5.0/4отз режется до 8.31 (хорошо), но и честный hidden gem 4.9/35отз тянется к 8.40 вместо ~9.8 (спорно — концерн №4).

Водный кэп — жёсткий потолок

poor → score не выше 6.0 prohibited / unsafe → score не выше 3.5 // rating.engine.ts:354–360 иначе → без кэпа

Применяется после суммы и отдельно от веса water_quality (0.10). Грязная вода обрезает итог независимо от всего остального.

Пляж, набравший по сумме 8.31, при воде poor станет ровно 6.0 (см. пляж В ниже). Падение на 2.3 очка — и вся разница между «8.3» и «6.5» стирается в одну точку 6.0.

5. Три реальных прогона

Пляжи-архетипы, специально подобранные, чтобы показать поведение формулы. Cohort-статы (Италия): сред. Google 4.3 / медиана 250 / P95 6000; сред. TA 4.2 / медиана 90 / P95 2000; volume P95 9000.

Пляж А — «Cala премиум»: полные данные, всё хорошо

компонентsubscoreвесвклад
water_qualityexcellent → 10.00.101.00
infrastructure5 удобств → 6.250.080.50
accessibilityаэропорт+0, лестницы → 4.00.060.24
beach_profile5/8 → 7.750.050.39
nature_environment8.750.040.35
climate_seasonality25°C → 10.00.030.30
crowding_comfort6.00.030.18
awardsГолубой флаг → 100.010.10
google_rating_bayesian4.8@2000 → 9.490.121.14
google_reviews_score2000 → 8.740.070.61
tripadvisor_rating_bayesian4.6@500 → 9.080.100.91
tripadvisor_reviews_score500 → 8.180.060.49
volume_score5000 → 9.350.121.12
rating_consistencyΔ0.41 → 100.030.30
source_coveragecompleteness 90 → 9.00.040.36
data_freshness30д → 100.030.30
source_diversity9 источников → 100.030.30
ИТОГОcap нет (вода excellent)1.008.59
score 8.59  ·  excellent  ·  dataCoverage 100%
Демо твоих концернов №1–2 на этом пляже: убираем медиа (completeness 90→80) source_coverage 9.0→8.0 score −0.04 (8.59→8.55). Убираем описание (−15) score −0.06. Это весь эффект медиа и описания на рейтинг. Видео и текст почти ничего не значат — но «почти», не «ноль».

Пляж Б — «Hidden gem»: дикий, 4.9★ но мало отзывов, слабый регион

компонентsubscoreвесвклад
water_qualityexcellent → 10.00.101.00
infrastructureнет данных0.080.00
accessibilityдикий → 4.00.060.24
beach_profile7.750.050.39
nature_environment8.750.040.35
climate_seasonality10.00.030.30
crowding_comfortпусто → 8.00.030.24
awardsнет флага0.010.00
google_rating_bayesian4.9@35, регион 4.1 → 8.400.121.01
google_reviews_score35 отзывов → 4.120.070.29
tripadvisor_rating_bayesianнет TA0.100.00
tripadvisor_reviews_scoreнет TA0.060.00
volume_scoreмалоизвестен → 5.830.120.70
rating_consistencyнужны оба0.030.00
source_coveragecompleteness 55 → 5.50.040.22
data_freshness100.030.30
source_diversity6 источников → 100.030.30
ИТОГОcap нет1.005.33
score 5.33  ·  poor  ·  dataCoverage 72%
Тут видно концерн №4 и кое-что глубже. Девственный пляж с рейтингом 4.9 получает 5.33 = poor. Причины: (1) байес тянет 4.9★ к 8.40; (2) но главное — нет TA, нет consistency, нет инфраструктуры, нет наград: эти веса (0.10+0.06+0.03+0.08+0.01 = 0.28) висят нулями и не перераспределяются. Hidden gem проигрывает не из-за качества, а из-за тонких данных. Это сильнее, чем сам байес.

Пляж В — «Городской, отличный, но вода poor»: показывает кэп

компонентsubscoreвесвклад
water_qualitypoor → 2.00.100.20
infrastructure8 удобств → 100.080.80
accessibilityгород → 100.060.60
beach_profile9.250.050.46
nature_environmentcleaning → 5.00.040.20
climate_seasonality100.030.30
crowding_comfortтолпы → 3.00.030.09
awardsнет0.010.00
google_rating_bayesian4.4@8000 → 8.790.121.05
google_reviews_score8000 → 100.070.70
tripadvisor_rating_bayesian4.3@3000 → 8.590.100.86
tripadvisor_reviews_score3000 → 100.060.60
volume_score9000 → 100.121.20
rating_consistencyΔ0.20 → 100.030.30
source_coverage85 → 8.50.040.34
data_freshness100.030.30
source_diversity8 → 100.030.30
сумма до кэпа8.318.31
ИТОГОcap poor → min(8.31, 6.0)1.006.00
score 6.00  ·  average  ·  без кэпа было бы 8.31 «good»
Концерн №3 вживую: пляж 8.31 → обрезан до 6.0. Падение 2.3 очка. Badge «average» (не «poor», как ты сказал — для класса poor кэп 6.0 ≈ нижняя граница average), но дифференциация убита: и 8.3, и 6.5 станут одинаковыми 6.0.