fix(#3376): первая точка — только по третьей точке истории; числа под критерием
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
All checks were successful
CI Trade-In / changes (pull_request) Successful in 8s
CI / changes (pull_request) Successful in 10s
CI Trade-In / browser-tests (pull_request) Has been skipped
CI Trade-In / frontend-checks (pull_request) Has been skipped
CI / backend-tests (pull_request) Has been skipped
CI / frontend-tests (pull_request) Has been skipped
CI / openapi-codegen-check (pull_request) Has been skipped
CI Trade-In / backend-tests (pull_request) Successful in 4m58s
This commit is contained in:
parent
dc63b1830b
commit
1b86ba7e06
5 changed files with 98 additions and 61 deletions
|
|
@ -14,8 +14,10 @@
|
|||
-- эти точки не влияли.
|
||||
--
|
||||
-- Код починен в том же PR: гейт drop_decimal_slips в scraper_kit/offer_price_history.py
|
||||
-- подключён к писателям истории (domclick/detail.py, cian/detail.py). Эта миграция
|
||||
-- отрабатывает задним числом по уже собранным строкам.
|
||||
-- подключён к писателям НА ЖИВОМ ТРАКТЕ (domclick/detail.py, cian/detail.py). Эта
|
||||
-- миграция отрабатывает задним числом по уже собранным строкам. Разовый
|
||||
-- scripts/local-cian/playwright_history.py гейта не знает, но переиграть миграцию не
|
||||
-- может: он выбирает только объявления вообще БЕЗ offer_price_history.
|
||||
--
|
||||
-- ВЫБОРКА КАНДИДАТОВ ЗЕРКАЛИТ ГЕЙТ 1:1, А НЕ «ТОТ ЖЕ КРИТЕРИЙ»
|
||||
-- Первая редакция этого файла брала предыдущую точку через lag(), то есть СЫРУЮ
|
||||
|
|
@ -35,21 +37,30 @@
|
|||
-- КРИТЕРИЙ — ДВА СВИДЕТЕЛЯ, И У ПЕРВОЙ ТОЧКИ ТОЖЕ ДВА
|
||||
-- Внутри серии: 1) скачок к предыдущей ОСТАВЛЕННОЙ точке, price/base ∈ [9.5, 10.5]
|
||||
-- в любую сторону; 2) возврат к базе у следующей точки, next/base ∈ [0.9, 1.1].
|
||||
-- Первая точка: 1) она ×10/÷10 ко второй оставшейся; 2) вторая подтверждена третьей
|
||||
-- оставшейся, а если третьей нет — текущей ценой объявления, witness/second ∈ [0.9, 1.1].
|
||||
-- Первая точка: 1) она ×10/÷10 ко второй оставшейся; 2) вторая подтверждена ТРЕТЬЕЙ
|
||||
-- оставшейся, third/second ∈ [0.9, 1.1]. Третьей нет → кандидата нет.
|
||||
-- Окно шире чистой десятки, потому что сдвиг разряда часто идёт вместе с настоящим
|
||||
-- мелким изменением цены (377 000 → 3 720 000 это ×9.87). Одного скачка НЕ хватает:
|
||||
-- ×10 без подтверждения — это возможное честное изменение цены (400 000 → 4 000 000 →
|
||||
-- 8 000 000 — разгон, а не разряд), и такие точки остаются. Нет второго свидетеля
|
||||
-- вообще (последняя точка серии, а listings.price_rub пуст) → точку не трогаем.
|
||||
--
|
||||
-- ПОЧЕМУ У ПЕРВОЙ ТОЧКИ НЕТ ОТКАТА НА listings.price_rub. Он был и снят сознательно:
|
||||
-- у 17 из 21 кандидата первой точки (yandex 6, domklik 9, cian 2) listings.price_rub
|
||||
-- ТОЧНО равна второй точке — у yandex это буквально одна переменная lot.price_rub,
|
||||
-- записанная в двух местах. «Два свидетеля» там вырождаются в одного, а DELETE
|
||||
-- необратим. Эти 17 строк остаются жить; их процент, если он не процент, занулит
|
||||
-- финальный шаг |diff_percent| > 100 → NULL. Для yandex отлов переехал в #3385 —
|
||||
-- сравнение со СЛЕДУЮЩИМ наблюдением. У ПОСЛЕДНЕЙ точки цена объявления свидетелем
|
||||
-- остаётся: она там сравнивается с базой СЛЕВА, то есть с другим наблюдением.
|
||||
--
|
||||
-- СКОЛЬКО ЖДЁМ УДАЛЕНИЙ
|
||||
-- Предзамер на проде 06.09.2026 под ДВУХСВИДЕТЕЛЬСКИМ критерием: 35 строк внутри
|
||||
-- серий + ~12 первых точек (12 из 20 остатков domklik — ровно вид 330 000 → 3 300 000)
|
||||
-- ≈ 47. Прежняя цифра 76 в шапке была из ОДНОСВИДЕТЕЛЬСКОГО замера (просто отношение
|
||||
-- к предыдущей строке) и к этому критерию отношения не имеет. Порог остановки 200 —
|
||||
-- четырёхкратный запас: поймали больше — критерий ловит не то, и миграция обязана
|
||||
-- упасть, а не молча вычистить историю.
|
||||
-- Замер на проде 06.09.2026 ровно под ЭТИМ критерием: 34 строки внутри серий
|
||||
-- (cian 12, domklik 22) + 4 первых точки по третьей точке (domklik 4) = 38.
|
||||
-- По источникам: cian 12, domklik 26, yandex 0 — yandex целиком отпал вместе с
|
||||
-- откатом на listings.price_rub (см. выше), его случай уехал в #3385.
|
||||
-- Порог остановки 200 — примерно пятикратный запас: поймали больше — критерий
|
||||
-- ловит не то, и миграция обязана упасть, а не молча вычистить историю.
|
||||
--
|
||||
-- ТОЛЬКО СТРОКИ ЗАГРУЗЧИКА: change_time <> recorded_at
|
||||
-- Признак происхождения тот же, что в 285 (там же его доказательство): триггер
|
||||
|
|
@ -65,6 +76,9 @@
|
|||
-- же цена есть у ТРИГГЕРНОЙ строки того же объявления — значит listings.price_rub
|
||||
-- когда-то реально равнялась этому значению, и подозреваемая точка загрузчика
|
||||
-- подтверждена независимым писателем. Такую не удаляем и оставляем в цепочке базой.
|
||||
-- ЧЕСТНО ПРО ЕГО ВЕС: на прод-данных 06.09.2026 он не спас НИ ОДНОЙ строки (0 из 21
|
||||
-- кандидата правила первой точки). Механизм проверен только на синтетике — держим
|
||||
-- как страховку на будущих прогонах, а не как замеренную защиту.
|
||||
--
|
||||
-- ПЕРЕСЧЁТ diff_percent У СЛЕДУЮЩЕЙ СТРОКИ
|
||||
-- У строки, шедшей за удалённой, предыдущая цена сменилась — процент, посчитанный
|
||||
|
|
@ -187,10 +201,8 @@ WITH kept AS (
|
|||
row_number() OVER w AS rn,
|
||||
lead(oph.id) OVER w AS second_id,
|
||||
lead(oph.price_rub) OVER w AS second_price,
|
||||
lead(oph.price_rub, 2) OVER w AS third_price,
|
||||
l.price_rub AS listing_price
|
||||
lead(oph.price_rub, 2) OVER w AS third_price
|
||||
FROM offer_price_history oph
|
||||
LEFT JOIN listings l ON l.id = oph.listing_id
|
||||
WHERE oph.change_time <> oph.recorded_at
|
||||
AND NOT EXISTS (SELECT 1 FROM oph_decimal_slips s WHERE s.id = oph.id)
|
||||
WINDOW w AS (PARTITION BY oph.listing_id ORDER BY oph.change_time, oph.id)
|
||||
|
|
@ -202,8 +214,11 @@ SELECT k.id, k.listing_id, k.second_id, k.source
|
|||
AND k.second_price > 0
|
||||
AND (k.second_price / k.price_rub BETWEEN 9.5 AND 10.5
|
||||
OR k.price_rub / k.second_price BETWEEN 9.5 AND 10.5)
|
||||
AND COALESCE(k.third_price, k.listing_price) > 0
|
||||
AND COALESCE(k.third_price, k.listing_price) / k.second_price BETWEEN 0.9 AND 1.1
|
||||
-- Свидетель — только ТРЕТЬЯ точка истории. listings.price_rub здесь НЕ
|
||||
-- подставляется: у 17 из 21 кандидата она в точности равна второй точке,
|
||||
-- то есть это то же наблюдение, а не второй свидетель (см. шапку).
|
||||
AND k.third_price > 0
|
||||
AND k.third_price / k.second_price BETWEEN 0.9 AND 1.1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM offer_price_history t
|
||||
|
|
@ -236,7 +251,8 @@ BEGIN
|
|||
|
||||
IF to_delete > 200 THEN
|
||||
RAISE EXCEPTION
|
||||
'offer_price_history: к удалению % строк при предзамере 06.09.2026 ≈ 47 — '
|
||||
'offer_price_history: к удалению % строк при замере 06.09.2026 = 38 '
|
||||
'(34 внутри серий + 4 первых точки; cian 12, domklik 26, yandex 0) — '
|
||||
'критерий ловит не то, миграция остановлена', to_delete;
|
||||
END IF;
|
||||
|
||||
|
|
|
|||
|
|
@ -737,9 +737,9 @@ def test_validate_diff_percent_bool_treated_as_none() -> None:
|
|||
|
||||
|
||||
# ── drop_decimal_slips (#3376) — сдвиг разряда в priceHistory источника ───────
|
||||
# Прод-замер 06.09.2026: 76 точек ровно ×10/÷10 к соседям у 55 объявлений
|
||||
# (domklik 48, cian 21, yandex 6). Вид: 377 000 → 3 770 000 → 3 720 000 — цена
|
||||
# возвращается к базе следующей же точкой.
|
||||
# Прод-замер 06.09.2026 под ЭТИМ критерием: 38 точек (cian 12, domklik 26,
|
||||
# yandex 0). Вид: 377 000 → 3 770 000 → 3 720 000 — цена возвращается к базе
|
||||
# следующей же точкой.
|
||||
|
||||
|
||||
def test_drop_decimal_slips_drops_spike_up(caplog: pytest.LogCaptureFixture) -> None:
|
||||
|
|
@ -792,15 +792,10 @@ def test_drop_decimal_slips_last_point_kept_without_current_price() -> None:
|
|||
|
||||
|
||||
# ── правило ПЕРВОЙ точки (#3376, прод-разбор 06.09.2026) ─────────────────────
|
||||
# 12 из 20 остатков domklik — серии, у которых дефектная точка первая: 330 000 →
|
||||
# 3 300 000 (текущая цена 3 300 000), 420 000 → 4 200 000 → 4 500 000. Слева базы
|
||||
# нет, основное правило такую точку не видит. Свидетелей по-прежнему двое.
|
||||
|
||||
|
||||
def test_drop_decimal_slips_drops_first_point_witnessed_by_current_price() -> None:
|
||||
"""330 000 → 3 300 000 при цене 3 300 000: вторую точку подтверждает цена лота."""
|
||||
kept, dropped = drop_decimal_slips([330000, 3300000], lambda p: p, current_price=3300000)
|
||||
assert (kept, dropped) == ([3300000], 1)
|
||||
# Серии, у которых дефектная точка первая: 420 000 → 4 200 000 → 4 500 000. Слева
|
||||
# базы нет, основное правило такую точку не видит. Свидетелей по-прежнему двое, и
|
||||
# оба — из самой истории: вторая точка и ТРЕТЬЯ. Кандидатов первой точки на проде
|
||||
# было 21, свидетеля из истории имеют 4 (все domklik).
|
||||
|
||||
|
||||
def test_drop_decimal_slips_drops_first_point_witnessed_by_third() -> None:
|
||||
|
|
@ -815,9 +810,18 @@ def test_drop_decimal_slips_keeps_first_point_when_series_runs_away() -> None:
|
|||
assert drop_decimal_slips(series, lambda p: p) == (series, 0)
|
||||
|
||||
|
||||
def test_drop_decimal_slips_keeps_first_point_without_second_witness() -> None:
|
||||
"""Две точки и текущей цены нет — второго свидетеля не существует."""
|
||||
def test_drop_decimal_slips_keeps_first_point_without_third_point() -> None:
|
||||
"""330 000 → 3 300 000: третьей точки нет — свидетеля нет, ДАЖЕ с ценой лота.
|
||||
|
||||
Цена объявления в правиле первой точки не участвует сознательно: у 17 из 21
|
||||
прод-кандидата (yandex 6, domklik 9, cian 2) listings.price_rub в точности
|
||||
равнялась второй точке — у yandex это буквально одна переменная lot.price_rub,
|
||||
записанная в двух местах. Такой «второй свидетель» — то же наблюдение, а
|
||||
удаление точки необратимо. Верни COALESCE на current_price в правило первой
|
||||
точки — первый assert покраснеет.
|
||||
"""
|
||||
series = [330000, 3300000]
|
||||
assert drop_decimal_slips(series, lambda p: p, current_price=3300000) == (series, 0)
|
||||
assert drop_decimal_slips(series, lambda p: p, current_price=None) == (series, 0)
|
||||
|
||||
|
||||
|
|
@ -827,7 +831,8 @@ def test_drop_decimal_slips_first_point_decided_on_kept_series() -> None:
|
|||
1M → 10M → 100M → 10M: 100M выбрасывает основное правило, и только после этого
|
||||
видно, что первая точка ÷10 к оставшейся серии [10M, 10M]. Считай правило по
|
||||
сырым соседям — первый проход оставил бы 1M, а второй выбросил, то есть гейт
|
||||
перестал бы быть идемпотентным (перебор ниже ловит 1122 такие серии).
|
||||
перестал бы быть идемпотентным (перебор ниже ловит 1116 таких прогонов, и эта
|
||||
серия — первый из них).
|
||||
"""
|
||||
kept, dropped = drop_decimal_slips(
|
||||
[1_000_000, 10_000_000, 100_000_000, 10_000_000], lambda p: p
|
||||
|
|
@ -875,11 +880,12 @@ def test_drop_decimal_slips_is_idempotent(caplog: pytest.LogCaptureFixture) -> N
|
|||
пошагово, а доказать идемпотентность на самом SQL-файле нечем — прод-прогон
|
||||
один. Раз выборка кандидатов повторяет гейт, свойство переносится на неё.
|
||||
|
||||
ФАЛЬСИФИЦИРУЕМОСТЬ (замерено на этом же переборе — 19 525 серий × 3 цены):
|
||||
ФАЛЬСИФИЦИРУЕМОСТЬ (перемерено 06.09.2026 на этом же переборе — 19 525 серий
|
||||
× 3 цены = 58 575 прогонов):
|
||||
• база = сырая предыдущая точка вместо оставленной → 136 неидемпотентных
|
||||
прогонов, первый же — (1M, 10M, 1.05M, 9.9M) при цене 1M: [1M, 9.9M] → [1M];
|
||||
• решение по первой точке на сырых соседях вместо оставшихся → 1122 прогона,
|
||||
первый — (1M, 10M, 100M) при цене 10M: [1M, 10M] → [10M].
|
||||
• решение по первой точке на сырых соседях вместо оставшихся → 1116 прогонов,
|
||||
первый — (1M, 10M, 100M, 10M) без текущей цены: [1M, 10M, 10M] → [10M, 10M].
|
||||
"""
|
||||
# Гейт логирует каждый выброс; на таком переборе это десятки тысяч записей.
|
||||
caplog.set_level(logging.CRITICAL, logger="scraper_kit.offer_price_history")
|
||||
|
|
|
|||
|
|
@ -18,8 +18,10 @@ loader клал поле источника ``diff``, а там рубли. Пр
|
|||
Здесь же живёт второй гейт той же границы записи — drop_decimal_slips (#3376):
|
||||
он выбрасывает из СЕРИИ точки с потерянным разрядом источника (×10/÷10 с
|
||||
подтверждением у соседей), а recompute_diff_percent чинит процент у соседа
|
||||
выброшенной точки. Оба вызываются писателями истории (domclick/detail.py,
|
||||
cian/detail.py) и зеркалятся миграцией 286.
|
||||
выброшенной точки. Оба вызываются писателями на живом тракте (domclick/detail.py,
|
||||
cian/detail.py) и зеркалятся миграцией 286. Загейчены именно они: разовый
|
||||
scripts/local-cian/playwright_history.py пишет мимо гейта, но переиграть миграцию
|
||||
не может — он выбирает только объявления вообще БЕЗ offer_price_history.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
|
@ -103,12 +105,20 @@ def _is_decimal_slip(prev: float, cur: float, nxt: float | None) -> bool:
|
|||
def _is_first_point_slip(first: float, second: float, witness: float | None) -> bool:
|
||||
"""Первая точка серии = сдвиг разряда: у неё нет prev, свидетели справа.
|
||||
|
||||
Прод-разбор 06.09.2026: 12 из 20 остатков domklik — серии вида
|
||||
``330 000 → 3 300 000`` (текущая цена 3 300 000) и ``420 000 → 4 200 000 →
|
||||
4 500 000``. Дефектная точка ПЕРВАЯ, основное правило её не видит: базы слева
|
||||
нет. Свидетелей по-прежнему двое — ×10 ко ВТОРОЙ точке и подтверждение второй
|
||||
точки третьей-или-текущей-ценой. Одного скачка мало: ``400 000 → 4 000 000 →
|
||||
8 000 000`` это разгон цены, а не потерянный разряд.
|
||||
Прод-разбор 06.09.2026: серии вида ``420 000 → 4 200 000 → 4 500 000``.
|
||||
Дефектная точка ПЕРВАЯ, основное правило её не видит: базы слева нет.
|
||||
Свидетелей по-прежнему двое — ×10 ко ВТОРОЙ точке и подтверждение второй точки
|
||||
ТРЕТЬЕЙ. Одного скачка мало: ``400 000 → 4 000 000 → 8 000 000`` это разгон
|
||||
цены, а не потерянный разряд.
|
||||
|
||||
Свидетель здесь только третья точка истории; текущей цены объявления в этом
|
||||
правиле нет СОЗНАТЕЛЬНО. У 17 из 21 прод-кандидата (yandex 6, domklik 9,
|
||||
cian 2) ``listings.price_rub`` точно равнялась второй точке — у yandex это
|
||||
буквально одна переменная ``lot.price_rub``, записанная в двух местах. Такой
|
||||
«второй свидетель» — то же самое наблюдение, и два свидетеля вырождаются в
|
||||
одного, а выброс точки необратим. У ПОСЛЕДНЕЙ точки (_is_decimal_slip) цена
|
||||
объявления свидетелем остаётся: там она сравнивается с базой СЛЕВА, то есть с
|
||||
другим наблюдением.
|
||||
"""
|
||||
if not _shifted_by_decade(first, second) or witness is None:
|
||||
return False
|
||||
|
|
@ -124,16 +134,16 @@ def drop_decimal_slips(
|
|||
) -> tuple[list[T], int]:
|
||||
"""Убрать из серии точки с потерянным разрядом (#3376).
|
||||
|
||||
Прод-замер 06.09.2026: 76 строк-выбросов у 55 объявлений во всех источниках
|
||||
кроме avito — ``prev=377000 → 3770000 → next=3720000``. Это не рынок, а разряд,
|
||||
Вид дефекта: ``prev=377000 → 3770000 → next=3720000``. Это не рынок, а разряд,
|
||||
потерянный в priceHistory источника: цена возвращается к базе следующей же
|
||||
точкой. Читатели таблицы (медианный торг #3223, админка, /scrapers) получают
|
||||
от такой точки выброс в сотни процентов.
|
||||
от такой точки выброс в сотни процентов. Прод-замер 06.09.2026 под ЭТИМ
|
||||
критерием: 38 строк (cian 12, domklik 26, yandex 0).
|
||||
|
||||
Критерий требует ДВУХ свидетелей: скачок ×10 к предыдущей точке И возврат к
|
||||
базе у следующей. Одного скачка мало — так выглядит и честная смена цены.
|
||||
У ПЕРВОЙ точки предыдущей нет, для неё работает зеркальное правило
|
||||
(_is_first_point_slip) — оба свидетеля справа.
|
||||
(_is_first_point_slip) — оба свидетеля справа, и оба из самой истории.
|
||||
|
||||
ИДЕМПОТЕНТНОСТЬ — свойство, а не пожелание: ``gate(gate(s)) == gate(s)``.
|
||||
Его держит property-тест перебором серий длины 2-6 (см.
|
||||
|
|
@ -152,8 +162,9 @@ def drop_decimal_slips(
|
|||
Args:
|
||||
points: точки одного объявления, упорядоченные по времени (возрастание).
|
||||
price_of: как достать цену из точки (у писателей разная форма точки).
|
||||
current_price: текущая цена объявления — второй свидетель для ПОСЛЕДНЕЙ
|
||||
точки, у которой нет следующей. None → последнюю точку не трогаем.
|
||||
current_price: текущая цена объявления — второй свидетель ТОЛЬКО для
|
||||
ПОСЛЕДНЕЙ точки, у которой нет следующей. None → последнюю точку не
|
||||
трогаем. В правиле первой точки не участвует (_is_first_point_slip).
|
||||
listing_id: только для логов.
|
||||
|
||||
Returns:
|
||||
|
|
@ -188,9 +199,11 @@ def drop_decimal_slips(
|
|||
prev = cur
|
||||
|
||||
# Первая точка — последней: её свидетели это уже ОСТАВШИЕСЯ вторая и третья
|
||||
# точки (см. про идемпотентность выше).
|
||||
# точки (см. про идемпотентность выше). Третьей нет → свидетеля нет: current_price
|
||||
# здесь не подставляем, она слишком часто копия второй точки (см.
|
||||
# _is_first_point_slip).
|
||||
if len(kept_prices) >= 2 and kept_prices[0] is not None and kept_prices[1] is not None:
|
||||
witness = kept_prices[2] if len(kept_prices) >= 3 else now_price
|
||||
witness = kept_prices[2] if len(kept_prices) >= 3 else None
|
||||
if _is_first_point_slip(kept_prices[0], kept_prices[1], witness):
|
||||
logger.warning(
|
||||
"offer_price_history: первая точка %s выброшена как сдвиг разряда "
|
||||
|
|
|
|||
|
|
@ -475,6 +475,10 @@ def save_detail_enrichment(
|
|||
# Сдвиги разряда (#3376) выбрасываем до вставки; гейту нужна серия по возрастанию
|
||||
# времени, а _extract_price_changes порядок источника не гарантирует. Второй
|
||||
# свидетель для последней точки — цена в listings (_snap_row, читалась выше).
|
||||
# ponytail: сортировка лексикографическая по str(change_time). На наблюдаемом
|
||||
# формате cian (`2026-06-11T08:23:11.950626Z`, всегда одна зона) она совпадает с
|
||||
# хронологией; на смешанных форматах/зонах разойдётся с миграцией 286, которая
|
||||
# сортирует timestamptz. Апгрейд — парсить в datetime, если формат поедет.
|
||||
_changes = sorted(
|
||||
(c for c in enrichment.price_changes if c.get("change_time") and c.get("price_rub")),
|
||||
key=lambda c: str(c["change_time"]),
|
||||
|
|
|
|||
|
|
@ -27,17 +27,16 @@ SET LOCAL lock_timeout = '5s';
|
|||
-- SELECT не выражается — его считает DO-блок ниже. А эти два выражаются.
|
||||
|
||||
-- (а) кандидаты правила ПЕРВОЙ точки по СЫРЫМ строкам, до удалений фазы 1.
|
||||
-- Ожидание из шапки 286: ~12, из них домклик большинство.
|
||||
-- Ожидание из шапки 286: 4, все domklik (yandex 0 — их свидетелем была бы
|
||||
-- listings.price_rub, а откат на неё снят).
|
||||
SELECT 'предзамер: первая точка (сырые строки)' AS metric, k.source, count(*)
|
||||
FROM (
|
||||
SELECT oph.source,
|
||||
oph.price_rub,
|
||||
row_number() OVER w AS rn,
|
||||
lead(oph.price_rub) OVER w AS second_price,
|
||||
lead(oph.price_rub, 2) OVER w AS third_price,
|
||||
l.price_rub AS listing_price
|
||||
lead(oph.price_rub, 2) OVER w AS third_price
|
||||
FROM offer_price_history oph
|
||||
LEFT JOIN listings l ON l.id = oph.listing_id
|
||||
WHERE oph.change_time <> oph.recorded_at
|
||||
WINDOW w AS (PARTITION BY oph.listing_id ORDER BY oph.change_time, oph.id)
|
||||
) k
|
||||
|
|
@ -46,8 +45,8 @@ SELECT 'предзамер: первая точка (сырые строки)' A
|
|||
AND k.second_price > 0
|
||||
AND (k.second_price / k.price_rub BETWEEN 9.5 AND 10.5
|
||||
OR k.price_rub / k.second_price BETWEEN 9.5 AND 10.5)
|
||||
AND COALESCE(k.third_price, k.listing_price) > 0
|
||||
AND COALESCE(k.third_price, k.listing_price) / k.second_price BETWEEN 0.9 AND 1.1
|
||||
AND k.third_price > 0
|
||||
AND k.third_price / k.second_price BETWEEN 0.9 AND 1.1
|
||||
GROUP BY k.source
|
||||
ORDER BY k.source;
|
||||
|
||||
|
|
@ -153,10 +152,8 @@ FOR pass_no IN 1..2 LOOP
|
|||
row_number() OVER w AS rn,
|
||||
lead(oph.id) OVER w AS second_id,
|
||||
lead(oph.price_rub) OVER w AS second_price,
|
||||
lead(oph.price_rub, 2) OVER w AS third_price,
|
||||
l.price_rub AS listing_price
|
||||
lead(oph.price_rub, 2) OVER w AS third_price
|
||||
FROM offer_price_history oph
|
||||
LEFT JOIN listings l ON l.id = oph.listing_id
|
||||
WHERE oph.change_time <> oph.recorded_at
|
||||
AND NOT EXISTS (SELECT 1 FROM oph_decimal_slips s WHERE s.id = oph.id)
|
||||
WINDOW w AS (PARTITION BY oph.listing_id ORDER BY oph.change_time, oph.id)
|
||||
|
|
@ -168,8 +165,9 @@ FOR pass_no IN 1..2 LOOP
|
|||
AND k.second_price > 0
|
||||
AND (k.second_price / k.price_rub BETWEEN 9.5 AND 10.5
|
||||
OR k.price_rub / k.second_price BETWEEN 9.5 AND 10.5)
|
||||
AND COALESCE(k.third_price, k.listing_price) > 0
|
||||
AND COALESCE(k.third_price, k.listing_price) / k.second_price BETWEEN 0.9 AND 1.1
|
||||
-- Свидетель — только третья точка истории (см. шапку 286).
|
||||
AND k.third_price > 0
|
||||
AND k.third_price / k.second_price BETWEEN 0.9 AND 1.1
|
||||
AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM offer_price_history t
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue