chore(tradein/ui): вычистить источник n1 из source-registry и UI (#2204)
All checks were successful
CI / changes (pull_request) Successful in 9s
CI Trade-In / changes (pull_request) Successful in 9s
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 / frontend-checks (pull_request) Successful in 1m36s
CI Trade-In / backend-tests (pull_request) Successful in 1m57s

This commit is contained in:
bot-backend 2026-07-03 10:16:53 +03:00
parent dd050af16d
commit ca0a51c876
9 changed files with 36 additions and 38 deletions

View file

@ -39,7 +39,7 @@ import { useBrand, useActiveBrandSlug } from "@/lib/useBrand";
import { LIVE_LISTING_SOURCES } from "@/lib/source-registry"; import { LIVE_LISTING_SOURCES } from "@/lib/source-registry";
// #2211 находка 7: список источников объявлений выводится из source-registry // #2211 находка 7: список источников объявлений выводится из source-registry
// (n1 деактивирован #2204 → его тут больше нет), а не хардкодится «4 источника (…N1)». // (источник n1 полностью выключен #2204 → его тут больше нет), а не хардкодится числом.
const LISTING_SOURCE_NAMES = LIVE_LISTING_SOURCES.map((s) => s.label).join(", "); const LISTING_SOURCE_NAMES = LIVE_LISTING_SOURCES.map((s) => s.label).join(", ");
function useEstimateId() { function useEstimateId() {
@ -348,7 +348,7 @@ export default function TradeInPage() {
<footer className="page-foot"> <footer className="page-foot">
<div> <div>
Мера · MVP ·{" "} Мера · MVP ·{" "}
<span className="mono">data: Avito + Cian + Yandex + N1 + Росреестр</span> <span className="mono">data: Avito + Cian + Yandex + Росреестр</span>
</div> </div>
<div style={{ display: "flex", gap: 18 }}> <div style={{ display: "flex", gap: 18 }}>
<a href="#">Документация</a> <a href="#">Документация</a>

View file

@ -3,8 +3,8 @@
/** /**
* SourcesProgress карточка «Шаг B · Агрегация». * SourcesProgress карточка «Шаг B · Агрегация».
* *
* Ростер источников берётся из source-registry (живые листинговые + Росреестр), * Ростер источников берётся из source-registry (листинговые + Росреестр),
* а НЕ из хардкода #2211. n1 деактивирован (#2204) в ростере его нет. * а НЕ из хардкода #2211. Источник n1 полностью выключен (#2204) в реестре его нет.
* Статусы выводятся из реального `estimate.sources_used` / `countBySource` / * Статусы выводятся из реального `estimate.sources_used` / `countBySource` /
* `actual_deals`; источник без вклада честно показывает «нет данных». * `actual_deals`; источник без вклада честно показывает «нет данных».
*/ */
@ -39,7 +39,7 @@ export function SourcesProgress({ estimate, isPending }: Props) {
} }
} }
// Живые листинговые источники — из реестра (n1 исключён как деактивированный). // Листинговые источники — из реестра.
const listingRows: SourceRow[] = LIVE_LISTING_SOURCES.map((s) => ({ const listingRows: SourceRow[] = LIVE_LISTING_SOURCES.map((s) => ({
key: s.id, key: s.id,
label: s.label, label: s.label,

View file

@ -2,7 +2,7 @@
/** /**
* StreetDealsCard «По вашей улице» ДКП-сделки Росреестра по улице target адреса * StreetDealsCard «По вашей улице» ДКП-сделки Росреестра по улице target адреса
* с привязкой к историческим listings (Avito / Cian / Yandex / N1). * с привязкой к историческим listings (Avito / Cian / Yandex / ДомКлик).
* *
* Питается от GET /api/v1/trade-in/sales-vs-listings (PR L / #564 Phase 2). * Питается от GET /api/v1/trade-in/sales-vs-listings (PR L / #564 Phase 2).
* Endpoint = superset предыдущего /street-deals: возвращает все pairs (LEFT JOIN * Endpoint = superset предыдущего /street-deals: возвращает все pairs (LEFT JOIN

View file

@ -53,13 +53,14 @@ export function houseTypeLabel(t: string | null | undefined): string {
return HOUSE_TYPE_LABELS[t] ?? t; return HOUSE_TYPE_LABELS[t] ?? t;
} }
// Источник n1 полностью выключен (#2204) → в справочнике его нет. Исторические
// строки из БД с source='n1' безопасно проходят через fallback `?? s` (сырой id).
const SOURCE_LABELS: Record<string, string> = { const SOURCE_LABELS: Record<string, string> = {
cian: "Циан", cian: "Циан",
avito: "Avito", avito: "Avito",
yandex: "Я.Недв", yandex: "Я.Недв",
domklik: "ДомКлик", domklik: "ДомКлик",
rosreestr: "Росреестр", rosreestr: "Росреестр",
n1: "N1.ru",
}; };
export function sourceLabel(s: string | null | undefined): string { export function sourceLabel(s: string | null | undefined): string {

View file

@ -1115,7 +1115,6 @@
.source-chip .src-dot.rosreestr { background: var(--fg); } .source-chip .src-dot.rosreestr { background: var(--fg); }
.source-chip .src-dot.etagi { background: var(--viz-2); } .source-chip .src-dot.etagi { background: var(--viz-2); }
.source-chip .src-dot.restate { background: var(--viz-2); } .source-chip .src-dot.restate { background: var(--viz-2); }
.source-chip .src-dot.n1 { background: var(--muted-2); }
.source-chip .src-dot.domrf { background: var(--accent-2); } .source-chip .src-dot.domrf { background: var(--accent-2); }
/* ============================================================ /* ============================================================
@ -2037,7 +2036,7 @@
.listing-link:hover:not(.is-static) .listing-price { color: var(--accent); } .listing-link:hover:not(.is-static) .listing-price { color: var(--accent); }
.listing-link.is-static { cursor: default; } .listing-link.is-static { cursor: default; }
/* Source badge (AVITO / CIAN / YANDEX / N1 / DOMKLIK) — per ui-tokens accent-soft + accent text uppercase 11px */ /* Source badge (AVITO / CIAN / YANDEX / DOMKLIK) — per ui-tokens accent-soft + accent text uppercase 11px */
.listing-source-badge { .listing-source-badge {
display: inline-block; display: inline-block;
padding: 1px 6px; padding: 1px 6px;

View file

@ -147,7 +147,6 @@ export const sources: SourceCard[] = [
{ name: "АВИТО ОЦЕНКА", count: "—", label: "нет данных", active: false }, { name: "АВИТО ОЦЕНКА", count: "—", label: "нет данных", active: false },
{ name: "ДОМКЛИК ПРАЙС", count: "—", label: "нет данных", active: false }, { name: "ДОМКЛИК ПРАЙС", count: "—", label: "нет данных", active: false },
{ name: "RESTATE", count: "—", label: "нет данных", active: false }, { name: "RESTATE", count: "—", label: "нет данных", active: false },
{ name: "N1.RU", count: "—", label: "нет данных", active: false },
]; ];
// ---- 03 SUMMARY ----------------------------------------------------------- // ---- 03 SUMMARY -----------------------------------------------------------

View file

@ -481,8 +481,9 @@ const SOURCE_SLOTS: ReadonlyArray<{ name: string; keys: string[] }> = [
{ name: "ДОМКЛИК", keys: ["domklik", "domclick"] }, { name: "ДОМКЛИК", keys: ["domklik", "domclick"] },
]; ];
// #2081 round2: N1.RU + RESTATE dropped — мёртвые (всегда «нет данных», не // #2081 round2: N1.RU + RESTATE dropped — мёртвые (всегда «нет данных», не
// учитываем); Домклик оставлен (живой источник, реально даёт лоты). TOTAL_SOURCES // учитываем). N1 с #2204 выключен полностью (backend + расписания). Домклик оставлен
// = единый счётчик «N / M», чтобы плитки, счётчик и текст не расходились. // (живой источник, реально даёт лоты). TOTAL_SOURCES = единый счётчик «N / M»,
// чтобы плитки, счётчик и текст не расходились.
const TOTAL_SOURCES = SOURCE_SLOTS.length; const TOTAL_SOURCES = SOURCE_SLOTS.length;
function buildSources(e: AggregatedEstimate): SourceCard[] { function buildSources(e: AggregatedEstimate): SourceCard[] {
@ -1116,8 +1117,7 @@ const SOURCE_LABEL: Record<string, string> = {
domklik: "Домклик", domklik: "Домклик",
domclick: "Домклик", domclick: "Домклик",
restate: "Restate", restate: "Restate",
n1: "N1.RU", // n1 полностью выключен (#2204) → истор. строки source='n1' идут через fallback `?? s`.
n1ru: "N1.RU",
}; };
function sourceLabel(s: string | null | undefined): string { function sourceLabel(s: string | null | undefined): string {

View file

@ -6,8 +6,8 @@
* участвовали в оценке». Любой ростер/чип/лейбл/знаменатель в UI выводится ЛИБО * участвовали в оценке». Любой ростер/чип/лейбл/знаменатель в UI выводится ЛИБО
* из фактического `estimate.sources_used` (что реально дало данные), ЛИБО из * из фактического `estimate.sources_used` (что реально дало данные), ЛИБО из
* `SOURCES` ниже (справочник известных источников для лейблов/цветов). * `SOURCES` ниже (справочник известных источников для лейблов/цветов).
* 2. Знаменатели вида «N/7», «N/5» НЕ хардкодятся числом. Если нужен знаменатель * 2. Знаменатели вида «N/6», «N/5» НЕ хардкодятся числом. Если нужен знаменатель
* «живых» источников берётся из `LIVE_SOURCE_COUNT` (derived), а не из литерала. * источников берётся из `LIVE_SOURCE_COUNT` (derived), а не из литерала.
* 3. Пустой `sources_used` честный текст «источники не зафиксированы», НЕ подстановка * 3. Пустой `sources_used` честный текст «источники не зафиксированы», НЕ подстановка
* фейковых чипов avito/cian/yandex/rosreestr. * фейковых чипов avito/cian/yandex/rosreestr.
* *
@ -15,8 +15,10 @@
* sources_used = {листинговые источники, реально давшие аналоги} * sources_used = {листинговые источники, реально давшие аналоги}
* {'rosreestr' сделки ДКП} * {'rosreestr' сделки ДКП}
* {оценочные: 'avito_imv' | 'yandex_valuation' | 'cian_valuation'}. * {оценочные: 'avito_imv' | 'yandex_valuation' | 'cian_valuation'}.
* n1 деактивирован (#2204) присутствует в справочнике для лейблинга исторических * n1 полностью выключен (#2204): удалён из справочника. Исторические строки из БД с
* данных, но помечен live:false и исключён из «живого» ростера. * `source='n1'` могут прийти в placement history они безопасно проходят через
* fallback `sourceLabel`/`sourceDot` (лейбл = сырой id, точка = нейтральный `dom`),
* код не падает на неизвестном источнике.
*/ */
export type SourceKind = "listing" | "deals" | "valuation"; export type SourceKind = "listing" | "deals" | "valuation";
@ -30,42 +32,39 @@ export interface SourceMeta {
readonly dot: string; readonly dot: string;
/** Тип вклада: объявления / сделки / внешняя оценка. */ /** Тип вклада: объявления / сделки / внешняя оценка. */
readonly kind: SourceKind; readonly kind: SourceKind;
/** true — источник участвует в текущем сборе; false — деактивирован (n1). */
readonly live: boolean;
} }
/** /**
* Справочник известных источников. `as const satisfies` фиксирует форму на уровне * Справочник известных (активных) источников. `as const satisfies` фиксирует форму на
* типов: массив readonly, добавить «фейковый» источник или сломать поле не даст TS. * уровне типов: массив readonly, добавить «фейковый» источник или сломать поле не даст TS.
* Cоответствие CSS-классам `.src-dot.*` (trade-in.css) проверено вручную для * Cоответствие CSS-классам `.src-dot.*` (trade-in.css) проверено вручную для
* rosreestr валиден класс `rosreestr` (не мифический `ros`, у которого нет стиля). * rosreestr валиден класс `rosreestr` (не мифический `ros`, у которого нет стиля).
*/ */
export const SOURCES = [ export const SOURCES = [
{ id: "avito", label: "Avito", dot: "avito", kind: "listing", live: true }, { id: "avito", label: "Avito", dot: "avito", kind: "listing" },
{ id: "cian", label: "Циан", dot: "cian", kind: "listing", live: true }, { id: "cian", label: "Циан", dot: "cian", kind: "listing" },
{ id: "yandex", label: "Я.Недвижимость", dot: "yandex", kind: "listing", live: true }, { id: "yandex", label: "Я.Недвижимость", dot: "yandex", kind: "listing" },
{ id: "domklik", label: "ДомКлик", dot: "dom", kind: "listing", live: true }, { id: "domklik", label: "ДомКлик", dot: "dom", kind: "listing" },
{ id: "n1", label: "N1.ru", dot: "n1", kind: "listing", live: false }, { id: "rosreestr", label: "Росреестр", dot: "rosreestr", kind: "deals" },
{ id: "rosreestr", label: "Росреестр", dot: "rosreestr", kind: "deals", live: true }, { id: "etagi", label: "Этажи", dot: "etagi", kind: "deals" },
{ id: "etagi", label: "Этажи", dot: "etagi", kind: "deals", live: true }, { id: "avito_imv", label: "Avito Оценка", dot: "avito", kind: "valuation" },
{ id: "avito_imv", label: "Avito Оценка", dot: "avito", kind: "valuation", live: true }, { id: "yandex_valuation", label: "Я.Оценка", dot: "yandex", kind: "valuation" },
{ id: "yandex_valuation", label: "Я.Оценка", dot: "yandex", kind: "valuation", live: true }, { id: "cian_valuation", label: "Циан Оценка", dot: "cian", kind: "valuation" },
{ id: "cian_valuation", label: "Циан Оценка", dot: "cian", kind: "valuation", live: true },
] as const satisfies readonly SourceMeta[]; ] as const satisfies readonly SourceMeta[];
const BY_ID: ReadonlyMap<string, SourceMeta> = new Map(SOURCES.map((s) => [s.id, s])); const BY_ID: ReadonlyMap<string, SourceMeta> = new Map(SOURCES.map((s) => [s.id, s]));
/** Живые листинговые источники (объявления) — для ростера прогресса сбора. */ /** Листинговые источники (объявления) — для ростера прогресса сбора. */
export const LIVE_LISTING_SOURCES: readonly SourceMeta[] = SOURCES.filter( export const LIVE_LISTING_SOURCES: readonly SourceMeta[] = SOURCES.filter(
(s) => s.kind === "listing" && s.live, (s) => s.kind === "listing",
); );
/** /**
* Знаменатель «живых» источников: живые листинговые + сделки (rosreestr). * Знаменатель источников: листинговые + сделки (rosreestr / etagi).
* Derived из реестра заменяет хардкод «/7» / «/5» в компонентах. * Derived из реестра заменяет хардкод «/6» / «/5» в компонентах.
*/ */
export const LIVE_SOURCE_COUNT: number = SOURCES.filter( export const LIVE_SOURCE_COUNT: number = SOURCES.filter(
(s) => s.live && (s.kind === "listing" || s.kind === "deals"), (s) => s.kind === "listing" || s.kind === "deals",
).length; ).length;
/** Известен ли источник справочнику. */ /** Известен ли источник справочнику. */

View file

@ -362,7 +362,7 @@ export interface SalesListingPair {
deal_address: string; deal_address: string;
listing_id: number | null; listing_id: number | null;
listing_source: string | null; // 'avito' / 'cian' / 'yandex' / 'n1' / 'domklik' listing_source: string | null; // 'avito' / 'cian' / 'yandex' / 'domklik' (истор. могут встречаться выключенные источники)
listing_source_url: string | null; listing_source_url: string | null;
listing_date: string | null; // ISO date listing_date: string | null; // ISO date
listing_price_rub: number | null; listing_price_rub: number | null;