Compare commits
No commits in common. "698c1be4f362dc4d108433da44ba19ae15c3b190" and "b902edd44c09dd91b9fb4fc952317313280e229e" have entirely different histories.
698c1be4f3
...
b902edd44c
1 changed files with 19 additions and 34 deletions
|
|
@ -8,13 +8,10 @@
|
||||||
// renders. ALL money/format/geometry lives here — never copy fixture literals.
|
// renders. ALL money/format/geometry lives here — never copy fixture literals.
|
||||||
//
|
//
|
||||||
// Reconciliations / known FE-approximations (look for "TODO BE-N"):
|
// Reconciliations / known FE-approximations (look for "TODO BE-N"):
|
||||||
// BE-1 #2043 shipped 2026-07-02: AggregatedEstimate now carries real
|
// BE-1 created_at, cv, per-source lot counts are NOT in the API response.
|
||||||
// cv/source_counts/created_at. mapSources' marketAds.kpi.cv (#2041)
|
// Report date is derived as expires_at − 24h; cv is the FE coefficient
|
||||||
// prefers estimate.cv and only falls back to the FE coefficient of
|
// of variation of analog ₽/м²; source counts are an FE groupBy of
|
||||||
// variation (analog ₽/м²) for pre-#2043 cached estimates where cv is
|
// analogs + actual_deals by .source.
|
||||||
// absent. mapReport/mapResultPanel/mapSummary/mapCache still use the
|
|
||||||
// pre-BE-1 approximations (created_at shift, FE source groupBy) — out
|
|
||||||
// of scope for #2040/#2041, left as-is.
|
|
||||||
// BE-2 target_address is a single string; street/city are split heuristically
|
// BE-2 target_address is a single string; street/city are split heuristically
|
||||||
// (parseAddress). Backend should return structured address components.
|
// (parseAddress). Backend should return structured address components.
|
||||||
// BE-3 location coefficient / street-view caption / compass bearing are not in
|
// BE-3 location coefficient / street-view caption / compass bearing are not in
|
||||||
|
|
@ -70,7 +67,6 @@ import type {
|
||||||
SummaryRow,
|
SummaryRow,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { tokens } from "./tokens";
|
import { tokens } from "./tokens";
|
||||||
import { sourceLabel as registrySourceLabel } from "@/lib/source-registry";
|
|
||||||
|
|
||||||
// Total number of source slots — meta "N / M". Single source of truth is
|
// Total number of source slots — meta "N / M". Single source of truth is
|
||||||
// SOURCE_SLOTS.length (defined with the array below as TOTAL_SOURCES).
|
// SOURCE_SLOTS.length (defined with the array below as TOTAL_SOURCES).
|
||||||
|
|
@ -272,26 +268,6 @@ function cvStr(values: number[]): string {
|
||||||
: "—";
|
: "—";
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Format a 0..1 coefficient-of-variation ratio the same way as cvStr's %. */
|
|
||||||
function cvRatioStr(cv: number): string {
|
|
||||||
return `${(cv * 100).toLocaleString("ru-RU", {
|
|
||||||
minimumFractionDigits: 1,
|
|
||||||
maximumFractionDigits: 1,
|
|
||||||
})}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Analog ₽/м² spread, used by #2041's marketAds.kpi.cv. Prefers the real
|
|
||||||
* backend estimate.cv (#2043/BE-1, 0..1 ratio) and only falls back to the FE
|
|
||||||
* coefficient of variation for pre-#2043 cached estimates where cv is absent
|
|
||||||
* (older persisted rows don't carry it — see the file-header TODO BE-1 note).
|
|
||||||
*/
|
|
||||||
function cvLabel(e: AggregatedEstimate | null): string {
|
|
||||||
if (e == null) return "—";
|
|
||||||
if (e.cv != null && Number.isFinite(e.cv)) return cvRatioStr(e.cv);
|
|
||||||
return cvStr(e.analogs.map((a) => a.price_per_m2));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Bin `values` into 8 buckets across [min,max]; return per-bucket height as
|
* Bin `values` into 8 buckets across [min,max]; return per-bucket height as
|
||||||
* count/maxCount*100 (the mini-histogram bar heights). Empty input -> [].
|
* count/maxCount*100 (the mini-histogram bar heights). Empty input -> [].
|
||||||
|
|
@ -1131,13 +1107,22 @@ function fmtDist(m: number | null | undefined): string {
|
||||||
})}${NBSP}км`;
|
})}${NBSP}км`;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Source key -> brand/RU display label (badges + link source). Delegates to the
|
// Source key -> brand/RU display label (badges + link source).
|
||||||
// single source-registry roster (#2211) instead of a local hardcoded map — an
|
const SOURCE_LABEL: Record<string, string> = {
|
||||||
// unknown/inactive id (e.g. historical 'n1' rows, #2204) echoes back verbatim
|
avito: "Avito",
|
||||||
// via the registry's own fallback rather than inventing a label.
|
avito_imv: "Avito",
|
||||||
|
cian: "Циан",
|
||||||
|
yandex: "Яндекс",
|
||||||
|
rosreestr: "Росреестр",
|
||||||
|
domklik: "Домклик",
|
||||||
|
domclick: "Домклик",
|
||||||
|
restate: "Restate",
|
||||||
|
// n1 полностью выключен (#2204) → истор. строки source='n1' идут через fallback `?? s`.
|
||||||
|
};
|
||||||
|
|
||||||
function sourceLabel(s: string | null | undefined): string {
|
function sourceLabel(s: string | null | undefined): string {
|
||||||
if (!s) return "—";
|
if (!s) return "—";
|
||||||
return registrySourceLabel(s.toLowerCase());
|
return SOURCE_LABEL[s.toLowerCase()] ?? s;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Rosreestr deal tier -> RU caption next to the badge. */
|
/** Rosreestr deal tier -> RU caption next to the badge. */
|
||||||
|
|
@ -1777,7 +1762,7 @@ export function mapSources(
|
||||||
? numRu(e.median_price_per_m2)
|
? numRu(e.median_price_per_m2)
|
||||||
: "—",
|
: "—",
|
||||||
range: e != null ? mlnRangeTight(e.range_low_rub, e.range_high_rub) : "—",
|
range: e != null ? mlnRangeTight(e.range_low_rub, e.range_high_rub) : "—",
|
||||||
cv: cvLabel(e),
|
cv: e != null ? cvStr(e.analogs.map((a) => a.price_per_m2)) : "—",
|
||||||
},
|
},
|
||||||
filters: buildAdFilters(e),
|
filters: buildAdFilters(e),
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue