Scoring and Maturity
nfr-review computes a design maturity score (0–100) from the findings in each scan. The score provides a single number for tracking quality over time, comparing repositories, and setting quality gates in CI.
The MaturityScore model
Section titled “The MaturityScore model”# nfr-review:skip(python-dormant-classes) reason: returned by compute_maturity_score
class MaturityScore(BaseModel):
"""Design maturity score with category breakdown."""
overall: int = Field(ge=0, le=100)
grade: str # A/B/C/D/F
category_scores: dict[str, int] # category -> score
finding_counts: dict[str, int] # severity -> count
rules_coverage: float # fraction of rules that ran vs totalThe score combines:
overall— weighted average of category scores (0–100)grade— letter grade derived from the overall scorecategory_scores— per-category breakdown (each 0–100)finding_counts— number of findings at each severityrules_coverage— fraction of rules that ran vs total available
Grade scale
Section titled “Grade scale”def _grade(score: int) -> str:
if score >= 90:
return "A"
if score >= 75:
return "B"
if score >= 60:
return "C"
if score >= 45:
return "D"
return "F"| Grade | Score range | Interpretation |
|---|---|---|
| A | 90–100 | Excellent — minimal findings, strong practices |
| B | 75–89 | Good — some gaps but fundamentally sound |
| C | 60–74 | Fair — several areas need attention |
| D | 45–59 | Poor — significant quality concerns |
| F | 0–44 | Failing — urgent remediation needed |
How scores are calculated
Section titled “How scores are calculated”1. Severity deductions
Section titled “1. Severity deductions”Each finding deducts points from its category’s score based on severity:
DEFAULT_SEVERITY_DEDUCTIONS: dict[str, int] = {
"critical": 15,
"high": 8,
"medium": 3,
"low": 1,
"info": 0,
}A category starts at 100. A single critical finding drops it to 85.
Three medium findings bring it to 91. Deductions are additive — a category
with one high and two medium findings scores 100 - 8 - 3 - 3 = 86.
2. Category weights
Section titled “2. Category weights”Categories contribute to the overall score according to their weight:
DEFAULT_CATEGORY_WEIGHTS: dict[str, float] = {
"security": 1.0,
"reliability": 1.0,
"performance": 1.0,
"maintainability": 1.0,
"OTEL": 1.0,
"structure": 1.0,
}By default all categories have equal weight. You can adjust weights in your config to emphasise what matters most to your project:
scoring: category_weights: security: 2.0 # security matters twice as much performance: 0.5 # less emphasis on performance3. Overall score
Section titled “3. Overall score”The overall score is a weighted average of all category scores:
overall = Σ(category_score × weight) / Σ(weight)Categories with higher weight pull the overall score more. A project scoring 90 in security (weight 2.0) and 60 in performance (weight 0.5) gets:
(90 × 2.0 + 60 × 0.5) / (2.0 + 0.5) = 210 / 2.5 = 84 → Grade B4. Rules coverage
Section titled “4. Rules coverage”Rules coverage tracks what fraction of applicable rules actually ran:
coverage = rules_run / (rules_run + rules_skipped)A low coverage number means many rules couldn’t run — usually because collectors were skipped or required technology wasn’t detected. The coverage value appears in JSONL output and reports for transparency.
Trend tracking
Section titled “Trend tracking”nfr-review can compare the current score against a baseline to show improvement or regression:
# Save a baselinenfr-review report --format jsonl -o baseline/
# Later, compare against itnfr-review report --baseline baseline/report.jsonlThe trend comparison shows:
| Field | Meaning |
|---|---|
delta |
Point change (positive = improved) |
direction |
"improved", "regressed", or "stable" |
category_deltas |
Per-category point changes |
This enables CI gates like “fail if the score regressed by more than 5 points” or dashboards tracking quality trajectories over time.
Category aliases
Section titled “Category aliases”Some rules map to category names that differ from the scoring categories. nfr-review normalises these with aliases:
| Rule category | Scores under |
|---|---|
observability |
reliability |
ops |
maintainability |
This ensures all findings contribute to the six core scoring categories regardless of what name the rule’s keyword mapping produced.
Customising scoring
Section titled “Customising scoring”You can override every aspect of scoring in nfr-review.yaml:
scoring: category_weights: security: 2.0 reliability: 1.5 performance: 1.0 maintainability: 1.0 OTEL: 0.5 structure: 0.5
severity_deductions: critical: 20 # harsher on critical issues high: 10 medium: 3 low: 1 info: 0
category_aliases: observability: reliability ops: maintainabilityThe next chapter covers project hygiene checks — a separate layer of analysis that examines documentation, licensing, CI automation, and dependency health.