feat(site-finder): X1 score breakdown + verbal explain (#47) #87

Closed
lekss361 wants to merge 8 commits from feat/site-finder-score-breakdown into main
2 changed files with 39 additions and 3 deletions
Showing only changes of commit b5a218160d - Show all commits

View file

@ -902,7 +902,9 @@ def analyze_parcel(
else:
center_bonus = 0.0
# X1 (#47): centrality как отдельный synthetic factor в breakdown
# X1 (#47): centrality как отдельный synthetic factor в breakdown.
# NB: для centrality decay не применяется (bonus IS the value), поэтому
# weight=1.0 семантически — "no decay multiplier"; contribution = center_bonus.
if center_bonus > 0:
factors_detailed.append(
{
@ -911,7 +913,7 @@ def analyze_parcel(
"category_ru": "Центральность",
"group": "Локация",
"value": round(dist_to_center_km, 2),
"weight": center_bonus,
"weight": 1.0,
"contribution": round(center_bonus, 2),
"verbal": (
f"Близость к центру ЕКБ ({dist_to_center_km:.1f}км) — "

View file

@ -111,7 +111,8 @@ export function ScoreBreakdownPanel({
color: "#6b7280",
}}
>
{byGroup.map((g) => (
{/* Positive groups — visible в баре */}
{positiveGroups.map((g) => (
<div
key={g.group}
style={{ display: "flex", alignItems: "center", gap: 4 }}
@ -134,6 +135,39 @@ export function ScoreBreakdownPanel({
</div>
))}
</div>
{/* Negative groups отдельной "drag" линией под баром (legend для bar
использует только positive, чтобы не было orphan swatches без сегмента) */}
{byGroup
.filter((g) => g.contribution < 0)
.map((g) => (
<div
key={`neg-${g.group}`}
style={{
display: "flex",
alignItems: "center",
gap: 4,
marginTop: 6,
fontSize: 12,
color: "#6b7280",
}}
>
<span
style={{
display: "inline-block",
width: 10,
height: 10,
borderRadius: 2,
background: GROUP_COLORS[g.group] ?? GROUP_COLORS.Прочее,
}}
/>
<span>
Снижают балл {g.group}:{" "}
<strong style={{ color: "#dc2626" }}>
{fmtContribution(g.contribution)}
</strong>
</span>
</div>
))}
</div>
)}