Skip to content

Commit 0aeb9c5

Browse files
authored
feat(health): deduct open performance findings from the performance score (#801)
* feat(health): deduct open performance findings from the performance score * fix(health): server-supplied category caps win over the UI fallback The glossary's CATEGORY_CAP still said the performance pillar deducts at most 1.0, and score-breakdown preferred that stale TS constant over the cap the scorer actually enforced. Update the mirror to 2.0, flip the preference so the payload cap wins (the glossary stays as a fallback for older-server payloads that predate the cap field), refresh the [9,10] score-band comment on the map, and pin the TS performance cap with a parity test alongside the existing band-cutoff guards.
1 parent 38f6c93 commit 0aeb9c5

7 files changed

Lines changed: 78 additions & 29 deletions

File tree

packages/core/src/repowise/core/analysis/health/scoring.py

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -313,10 +313,14 @@
313313

314314

315315
# ---------------------------------------------------------------------------
316-
# Performance dimension (PR3). Shipped at a small, ADVISORY weight - the whole
317-
# pillar is bounded by a single 1.0 category cap, so even a file riddled with
318-
# perf hits loses at most one health point on this dimension. Promotion to a
319-
# co-equal weight waits on PR4's cross-function precision study.
316+
# Performance dimension (PR3). Originally shipped at a small, ADVISORY weight
317+
# (a single 1.0 category cap), which left the pillar reading near-perfect even
318+
# on repos dense with open perf findings. With the cross-function pass landed
319+
# (PR4) and per-marker precision multipliers in place, the cap is raised to
320+
# 2.0 so open findings visibly move the score - still a deliberately
321+
# conservative ceiling for this first release (a file loses at most two
322+
# points regardless of hit count). Raising it further to a co-equal budget
323+
# waits on corpus precision data for the remaining advisory markers.
320324
# ---------------------------------------------------------------------------
321325

322326
# Per-biomarker weight on the performance dimension. ``io_in_loop`` is the
@@ -339,7 +343,7 @@
339343
# Phase 6 dialect markers. Both are high-precision syntactic shapes (Go
340344
# `go vet`/`gocritic` ship defer-in-loop; the regex marker gates on a static
341345
# literal pattern in Java/Go/Rust). Ship advisory pending this session's
342-
# test-repo gate; bounded by the 1.0 perf cap either way.
346+
# test-repo gate; bounded by the perf category cap either way.
343347
"regex_compile_in_loop": 0.6,
344348
"defer_in_loop": 0.6,
345349
"resource_construction_in_loop": 0.7,
@@ -400,9 +404,13 @@
400404
"sql_cartesian_join": "performance",
401405
}
402406

403-
# One bounded performance category cap. ~1.0 keeps performance advisory.
407+
# One bounded performance category cap. 2.0 is a deliberately conservative
408+
# first-release ceiling: enough for open findings to register on the pillar,
409+
# small enough that an all-advisory marker set cannot crater a file. Upgrade
410+
# path: raise toward the defect-style multi-point budget once the remaining
411+
# advisory markers clear their corpus precision gates (MARKER_BACKLOG.md).
404412
_PERFORMANCE_CATEGORY_CAPS: dict[str, float] = {
405-
"performance": 1.0,
413+
"performance": 2.0,
406414
}
407415

408416
# Perf biomarkers home to ``performance`` for display / per-pillar filtering.
@@ -565,7 +573,7 @@ def score_file(results: Iterable[BiomarkerResult]) -> tuple[dict[str, float | No
565573
identical to the pre-split ``score_file`` (the load-bearing guarantee).
566574
``scores["performance"]`` is now measured (PR3): a file with no perf
567575
findings scores 10.0; the perf detectors deduct under a single bounded
568-
``performance`` cap. It is still capped low (advisory) and never blends
576+
``performance`` cap (2.0, a conservative ceiling). It never blends
569577
into ``defect``.
570578
- ``defect_deductions`` is each finding's contribution to the DEFECT score
571579
after category capping, parallel to *results*. It populates

packages/server/src/repowise/server/routers/code_health/files_routes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def _key(m: Any):
9393
leads = _leads_by_file([f for f in findings if f.file_path in page_paths])
9494
# Per-file performance signal for the map's performance lens: open perf-
9595
# finding counts + whether a perf detector ran on the file's language. Colors
96-
# the lens by findings/coverage instead of the uniformly-green [9,10] score.
96+
# the lens by findings/coverage instead of the narrow-band [8,10] score.
9797
perf_counts: dict[str, int] = {}
9898
for fnd in findings:
9999
if (getattr(fnd, "dimension", None) or "defect") == "performance":
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/**
2+
* Parity guard for the glossary's fallback category caps.
3+
*
4+
* `CATEGORY_CAP` is the TypeScript mirror of the category-cap tables in
5+
* `packages/core/src/repowise/core/analysis/health/scoring.py`. The rendered
6+
* cap always prefers the server-supplied value (see score-breakdown.tsx), so
7+
* this constant only backstops older payloads — but a silent retune of one
8+
* side without the other should still fail CI, the same way the band cutoffs
9+
* are pinned in `packages/types/__tests__/health.test.ts` +
10+
* `tests/unit/health/test_grading.py`. The Python half of this pin is the
11+
* cap-bound behavior in `tests/unit/health/test_scoring_dimensions.py`
12+
* (`test_perf_category_cap_bounds_dimension`).
13+
*/
14+
15+
import { describe, expect, it } from "vitest";
16+
import { CATEGORY_CAP } from "../../src/health/biomarker-glossary.js";
17+
18+
describe("CATEGORY_CAP parity", () => {
19+
it("pins the performance cap to scoring.py's _PERFORMANCE_CATEGORY_CAPS", () => {
20+
expect(CATEGORY_CAP.performance).toBe(2.0);
21+
});
22+
});

packages/ui/src/health/biomarker-glossary.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,9 +48,10 @@ export const CATEGORY_CAP: Record<BiomarkerCategory, number> = {
4848
test_quality: 0.5,
4949
error_handling: 0.5,
5050
// One bounded performance category cap (mirrors `_PERFORMANCE_CATEGORY_CAPS`
51-
// in scoring.py): the whole performance pillar deducts at most 1.0, keeping it
52-
// advisory.
53-
performance: 1.0,
51+
// in scoring.py): the whole performance pillar deducts at most 2.0. Fallback
52+
// only — a server-supplied cap always wins (see score-breakdown.tsx), so this
53+
// constant matters just for payloads that predate the `cap` field.
54+
performance: 2.0,
5455
// SQL smells deduct on the maintainability pillar only, bounded by the `sql`
5556
// cap in `_MAINTAINABILITY_CATEGORY_CAPS` (scoring.py).
5657
sql: 2.0,

packages/ui/src/health/code-health-map.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export interface CodeHealthMapFile {
1515
/** Maintainability pillar score (1–10) — drives the maintainability overlay. */
1616
maintainability_score?: number | null;
1717
/** Performance-risk pillar score (1–10) — computed but NOT used to color the
18-
* performance overlay (it is compressed to [9,10] and reads uniformly green).
18+
* performance overlay (it is compressed to [8,10] and reads uniformly green).
1919
* The performance lens colors by {@link performance_findings} + coverage. */
2020
performance_score?: number | null;
2121
/** Open performance-risk findings on this file — drives the performance heat. */

packages/ui/src/health/score-breakdown.tsx

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ export interface ScoreBreakdownCategoryFinding {
1818

1919
export interface ScoreBreakdownCategory {
2020
category: BiomarkerCategory | string;
21-
cap: number;
21+
/** Cap the scorer actually enforced. Optional only because payloads from
22+
* older servers predate the field; when present it always wins over the
23+
* glossary's `CATEGORY_CAP` fallback. */
24+
cap?: number | null;
2225
raw_deduction: number;
2326
applied_deduction: number;
2427
capped: boolean;
@@ -58,23 +61,24 @@ export function ScoreBreakdown({
5861
return b.applied_deduction - a.applied_deduction;
5962
}
6063
const capA =
61-
CATEGORY_CAP[a.category as BiomarkerCategory] ?? a.cap;
64+
a.cap ?? CATEGORY_CAP[a.category as BiomarkerCategory] ?? 0;
6265
const capB =
63-
CATEGORY_CAP[b.category as BiomarkerCategory] ?? b.cap;
66+
b.cap ?? CATEGORY_CAP[b.category as BiomarkerCategory] ?? 0;
6467
return capB - capA;
6568
})
6669
.map((c) => {
6770
const label =
6871
CATEGORY_LABEL[c.category as BiomarkerCategory] ?? c.category;
69-
// Only trust the glossary's caps for scaling the bar. A
70-
// payload-supplied cap for an unknown category is often just the
71-
// deduction echoed back at itself, which would render a
72-
// meaningless always-full bar.
73-
const knownCap = CATEGORY_CAP[c.category as BiomarkerCategory];
74-
const cap = knownCap ?? c.cap;
72+
// Prefer the server-supplied cap: it is the value the scorer
73+
// actually enforced, so a cap retune in scoring.py renders
74+
// correctly without a UI release. The glossary constant is only a
75+
// fallback for older-server payloads that predate the `cap` field.
76+
const cap = c.cap ?? CATEGORY_CAP[c.category as BiomarkerCategory];
7577
const pct = Math.min(
7678
100,
77-
(Math.abs(c.applied_deduction) / Math.max(Math.abs(cap), 0.01)) * 100,
79+
(Math.abs(c.applied_deduction) /
80+
Math.max(Math.abs(cap ?? c.applied_deduction), 0.01)) *
81+
100,
7882
);
7983
return (
8084
<div
@@ -89,19 +93,20 @@ export function ScoreBreakdown({
8993
className="cursor-help tabular-nums text-[var(--color-text-tertiary)]"
9094
title="The cap is the most this category is allowed to subtract from the 10-point score, no matter how many findings it has."
9195
>
92-
{c.applied_deduction.toFixed(2)} / cap −{cap.toFixed(1)}
96+
{c.applied_deduction.toFixed(2)}
97+
{cap != null ? <> / cap −{cap.toFixed(1)}</> : null}
9398
{c.capped ? <span className="ml-1 text-[var(--color-warning)]" title="Raw deductions exceeded the cap; only the cap was subtracted.">(capped)</span> : null}
9499
</span>
95100
</div>
96101
<div className="mt-1.5 h-1.5 w-full overflow-hidden rounded-full bg-[var(--color-bg-muted)]">
97102
<div
98103
className={
99-
knownCap != null
104+
cap != null
100105
? "h-full bg-[var(--color-error)]/70"
101106
: "h-full bg-[var(--color-text-tertiary)]/40"
102107
}
103108
title={
104-
knownCap == null
109+
cap == null
105110
? "No defined cap for this category — bar scale is approximate."
106111
: undefined
107112
}

tests/unit/health/test_scoring_dimensions.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,25 @@ def test_perf_io_in_loop_deduction():
239239
assert scores["performance"] == 9.3
240240

241241

242+
def test_perf_two_medium_findings_deduct_uncapped():
243+
"""Two MEDIUM io_in_loop hits deduct in full (2 x 0.7 = 1.4, under the cap)."""
244+
scores, _ = score_file([_r("io_in_loop", Severity.MEDIUM) for _ in range(2)])
245+
assert scores["performance"] == 8.6
246+
247+
242248
def test_perf_category_cap_bounds_dimension():
243-
"""The single 1.0 performance cap bounds the whole pillar."""
249+
"""The single 2.0 performance cap bounds the whole pillar."""
244250
findings = [_r("io_in_loop", Severity.MEDIUM) for _ in range(10)]
245251
scores, _ = score_file(findings)
246-
# 10 * 0.7 = 7.0 raw -> capped at 1.0 -> 9.0, never lower.
247-
assert scores["performance"] == 9.0
252+
# 10 * 0.7 = 7.0 raw -> capped at 2.0 -> 8.0, never lower.
253+
assert scores["performance"] == 8.0
254+
255+
256+
def test_perf_cap_bounds_high_severity_pileup():
257+
"""Six HIGH hits (6 x 1.2 = 7.2 raw) still bottom out at the 2.0 cap."""
258+
findings = [_r("io_in_loop", Severity.HIGH) for _ in range(6)]
259+
scores, _ = score_file(findings)
260+
assert scores["performance"] == 8.0
248261

249262

250263
def test_perf_bonus_markers_advisory_weight():

0 commit comments

Comments
 (0)