Сущности предметной области:
Товар (Product) — уникальный товар, не зависит от ГЕО.
ГЕО (GeoRegion) — страна или регион со своей валютой.
Цена товара в ГЕО (ProductPriceInGeo) — базовая цена + доставка для конкретного товара в конкретном ГЕО.
Лид (Lead) — событие интереса к товару в определённом ГЕО (временная метка, возможно ID пользователя).
Коэффициент цены (PriceAdjustment) — динамический множитель, зависящий от количества лидов за окно (например, 10 минут).
Бизнес-логика:
Цена = Базовая цена × Коэффициент.
Коэффициент пересчитывается каждые 10 минут на основе количества лидов за предыдущие 10 минут.
Лиды привязаны к паре (Product, GeoRegion).
Валюта фиксируется на уровне GeoRegion.
Требования к данным:
Хранить историю лидов (для аналитики и пересчёта).
Хранить текущий коэффициент (или пересчитывать "на лету").
Обеспечить быстрый доступ к актуальной цене при показе товара.
Архитектурные решения:
Используем реляционную БД (PostgreSQL/MySQL).
Коэффициент можно хранить в отдельной таблице и обновлять по расписанию (cron / scheduler).
Альтернатива: кэшировать цену в Redis с TTL = 10 мин.
по https://www.plantuml.com/plantuml/
@startuml
class Product {
+id: UUID
+name: string
+sku: string
}
class GeoRegion {
+id: UUID
+name: string
+currency: string
}
class ProductPriceInGeo {
+id: UUID
+base_price: decimal
+shipping_cost: decimal
}
class Lead {
+id: UUID
+created_at: datetime
}
class PriceAdjustment {
+id: UUID
+adjustment_factor: float
+valid_from: datetime
+valid_to: datetime
+leads_count: int
}
Product "1" -- "0..*" ProductPriceInGeo : has
GeoRegion "1" -- "0..*" ProductPriceInGeo : has
ProductPriceInGeo "1" -- "0..*" Lead : generates
ProductPriceInGeo "1" -- "1" PriceAdjustment : current adjustment
@enduml
INSERT INTO lead (product_geo_id, created_at)
VALUES (
(SELECT id FROM product_price_in_geo
WHERE product_id = 'prod-123' AND geo_id = 'geo-us'),
NOW()
);-- Шаг 1: Подсчёт лидов за последние 10 минут по каждому product_geo
WITH recent_leads AS (
SELECT
product_geo_id,
COUNT(*) AS lead_count
FROM lead
WHERE created_at >= NOW() - INTERVAL 10 MINUTE
GROUP BY product_geo_id
)
-- Шаг 2: Обновление коэффициента
UPDATE price_adjustment pa
SET
adjustment_factor = calculate_coefficient(rl.lead_count), -- функция бизнес-логики
leads_count = rl.lead_count,
valid_from = NOW(),
valid_to = NOW() + INTERVAL 10 MINUTE
FROM recent_leads rl
WHERE pa.product_geo_id = rl.product_geo_id;Функция calculate_coefficient(n) может быть, например:
factor = MAX(0.7, 1.0 - 0.01 * n) — чем больше лидов, тем ниже цена (но не ниже 70%).
SELECT
p.name,
g.currency,
(ppg.base_price + ppg.shipping_cost) * pa.adjustment_factor AS final_price
FROM product p
JOIN product_price_in_geo ppg ON p.id = ppg.product_id
JOIN geo_region g ON ppg.geo_id = g.id
JOIN price_adjustment pa ON ppg.id = pa.product_geo_id
WHERE
p.sku = 'ABC123'
AND g.name = 'US'
AND NOW() BETWEEN pa.valid_from AND pa.valid_to;Product - Каталог товаров
GeoRegion - Справочник регионов и валют
ProductPriceInGeo - Базовые цены + доставка
Lead - История лидов (журнал событий)
PriceAdjustment - Текущий коэффициент (актуальная запись)
Кэш final_price на 10 мин для снижения нагрузки
Задача запускается каждые 10 минут (например, через cron или Laravel/Symfony scheduler).
Выполняет:
Подсчёт лидов за окно.
Пересчёт коэффициентов.
Обновление price_adjustment.
Инвалидация кэша (если используется).
Историзация коэффициентов: хранить все версии PriceAdjustment для аналитики.
Очередь лидов: если нагрузка высока — писать лиды в Kafka/RabbitMQ → обрабатывать асинхронно.
Материализованное представление: для быстрого доступа к final_price.