Scoring & Calculation Reference
This document describes the exact formulas behind every computed score in Calyntro. All metrics are derived exclusively from Git commit history and, optionally, ticket metadata from a configured issue tracker. No source code parsing, test runners, or external quality tools are required.
Hotspot Scores
Calyntro provides two complementary hotspot analyses. Both answer the question “which files are most likely to cause problems?” but from different structural angles.
Age/Churn Hotspot
Captures files that have been changing for a long time (structural instability) or are changing very rapidly right now (current incident signal).
Activity Score — measures current intensity:
activity_score = churn / max(age_days, 1)
High value: many lines changed in a short window. Penalises old files that suddenly reappear.
Structural Score — measures long-term instability:
structural_score = log₁₊(churn) × age_days
High value: a file that has been continuously modified over a long period.
log₁₊ dampens the effect of extreme outliers.
Both scores are min-max normalised within the current result set before concentration classification is applied:
normalised = (score − min_score) / (max_score − min_score)
Concentration thresholds (consistent with the critical_hotspot warning):
Complexity Hotspot (Git-only)
Combines weighted churn with Cognitive Complexity — the structural dimension that most directly predicts maintainability cost.
Step 1 — weighted churn per file:
weighted_churn = Σ (added_lines + deleted_lines) × w
where w is determined per commit by keyword matching on the commit message:
Commit message pattern (case-insens.) |
Weight |
|---|---|
contains |
1.5 |
contains |
0.5 |
all other commits |
1.0 |
Step 2 — hotspot score:
hotspot_score = weighted_churn × cognitive_complexity
cognitive_complexity is the value from the latest commit within the
selected time window (arg_max by author_date), ensuring that
complexity reflects the current state of the file, not an average over history.
Endpoint: POST /v1/analyze/hotspots/complexity
Tracker-Enriched Complexity Hotspot
The tracker-enriched variant replaces keyword heuristics with resolved ticket types from the configured issue tracker. The formula structure is identical; only the weight derivation changes.
Prerequisite: a ticket_provider block in config.yaml and
consistent ticket ID references in commit messages.
Three provider types are supported:
|
Use case |
Commit reference pattern |
|---|---|---|
|
Jira Cloud / Server with auth |
|
|
Public Jira Server (no auth, e.g. jira.mongodb.org) |
|
|
GitHub repositories (public or private with token) |
|
Example configurations:
# Jira Cloud (authenticated)
ticket_provider:
type: jira
project_prefix: PESWA
base_url: https://company.atlassian.net
email: user@company.com
api_token: xxxxx
# Public Jira Server (no credentials required)
ticket_provider:
type: jira_public
project_prefix: SERVER
base_url: https://jira.mongodb.org
# GitHub Issues (token optional — raises rate limit from 60 to 5,000 req/hr)
ticket_provider:
type: github_issues
owner: astral-sh
repo: uv
token: ghp_xxxxx # optional
For github_issues, issue labels are mapped to canonical ticket types:
bug / defect → Bug; enhancement / improvement → Improvement;
feature / new feature → New Feature; any other label → raw label name;
no labels → unresolvable (ticket_type = NULL).
Step 1 — ticket type resolution (at import time):
The importer scans every commit message for ticket references matching the configured provider pattern (e.g.
PESWA-\d+for Jira,#\d+for GitHub).Extracted ticket IDs are resolved via the tracker API — batched JQL for Jira, paginated issue list for GitHub Issues (cached in memory per import run).
The resolved type (
Bug,Story,Task,Epic, …) is stored incommit_tickets.ticket_type.Commits without a matching ticket reference have
ticket_type = NULL.
This enrichment runs once per import — there is no runtime dependency on the tracker.
Step 2 — ticket-type weights (at query time):
w = CASE
WHEN ticket_type IN ('bug', 'defect', 'error') → 2.0
WHEN ticket_type IS NOT NULL → 1.0
ELSE (no ticket reference) → 1.2
END
Normalisation is case-insensitive and applied inline in SQL at query time. Raw ticket types are preserved in storage; no information is lost.
Step 3 — hotspot score:
hotspot_score = ticket_weighted_churn × cognitive_complexity
Identical formula to the Git-only variant — the only difference is the churn weights. This makes the two results directly comparable.
Step 4 — coverage (per response):
coverage_pct = commits_with_type / commits_total × 100
commits_with_type counts distinct commits where at least one ticket’s type
was successfully resolved. A low coverage_pct does not break the analysis —
uncovered commits receive weight 1.2 (untracked) — but it limits the signal
quality of the bug/feature distinction.
Interpretation guide:
Coverage |
Interpretation |
|---|---|
≥ 80 % |
High confidence — ticket-type ranking is reliable |
40 – 79 % |
Directional signal — compare with base hotspot |
< 40 % |
Low coverage — base hotspot is the safer reference |
Endpoint: POST /v1/analyze/hotspots/complexity/tracker
Diagnostic endpoint: GET /v1/analysis/tracker/coverage
Before presenting tracker-enriched results, call the coverage diagnostic to evaluate data quality. It returns three layers:
Level 1 — Reach
commits_total all commits in the window
commits_no_ref commits with no ticket ID pattern (true untracked work,
or indicator that project_prefix is misconfigured)
Level 2 — Regexp quality
commits_with_ref commits where a ticket ID was extracted
tickets_extracted distinct ticket IDs found across all commits
Level 3 — API resolution
commits_with_resolved_type commits where the type was confirmed
tickets_resolved ticket IDs confirmed by the tracker
resolution_rate_pct tickets_resolved / tickets_extracted × 100
resolution_rate_pct is the decisive quality signal for the regexp:
Rate |
Interpretation |
|---|---|
≥ 90 % |
Regexp matches well — enrichment is reliable |
70–89 % |
False positives or deleted tickets — check prefix |
< 70 % |
Regexp misconfigured — use base hotspot as reference |
The confidence field (high / medium / low / none) combines
coverage and resolution into a single signal for UI display.
Comparing the two methodologies: Files that rank significantly higher in the tracker view than in the Git-only view are structurally risky and disproportionately driven by confirmed defects — the highest-priority refactoring targets.
Knowledge Risk (Silo Ratio)
A file is classified as a silo when one developer’s share of total commits meets or exceeds the configured threshold (default: 80 %).
ownership_share = developer_commits / total_file_commits
The silo ratio for a module is the fraction of its files that are silos:
silo_ratio = silo_files / total_files_in_module
Risk levels:
Level |
Ownership share |
|---|---|
Low |
80 – 89 % |
Medium |
90 – 94 % |
High |
95 – 99 % |
Critical |
100 % |
The threshold is configurable per deployment. Lowering it to 70 % surfaces earlier-stage concentration risk; raising it to 90 % focuses attention on near-complete silos only.
Code Map Risk Score
The Code Map treemap colours each module by a composite risk score:
risk_score = 0.6 × normalised(cognitive_complexity)
+ 0.4 × normalised(absolute_churn)
Both inputs are min-max normalised across the current module set. The score is therefore always relative — it shows which modules are riskiest compared to each other within the selected period, not against an absolute threshold.
Colour scale:
Colour |
Range |
Signal |
|---|---|---|
Green |
0–25 |
Low risk |
Yellow |
25–50 |
Moderate — monitor |
Orange |
50–75 |
Elevated — schedule review |
Red |
75–100 |
Critical — prioritise refactoring |
Dashboard Warnings
Warnings are pre-computed signals surfacing the most actionable risks. All scores used for threshold evaluation are normalised within the current dataset.
defect_attractor requires a configured issue tracker. If no ticket_type
data is present in the database, the warning type is silently skipped — no
error, no empty entries.
General Notes
Time windows: All analyses accept
start_date/end_dateparameters. When omitted, the full import history is used.Component filter: Passing a
component_namerestricts the analysis to files whose path prefix matches the component. Normalisation (min-max, averages) is always relative to the filtered set.Complexity source: Cyclomatic and Cognitive Complexity are computed by
rust-code-analysis-cli(most languages),csharpmetrics(C#), orqmlmetrics(QML) at import time. The latest value per file within the requested window is used (arg_maxby commit date).Authorship resolution: Commit authors are resolved to canonical developer names via the
aliasestable before ownership calculations. Team membership uses time-bounded records (valid_from/valid_to) so historical attribution remains accurate after team restructuring.