.. _scoring: 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. .. contents:: On this page :local: :depth: 2 --------- 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: .. code-block:: text 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: .. code-block:: text 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: .. code-block:: text normalised = (score − min_score) / (max_score − min_score) Concentration thresholds (consistent with the ``critical_hotspot`` warning): +----------+----------------------+ | Level | Normalised score | +==========+======================+ | Critical | ≥ 0.9 | +----------+----------------------+ | High | ≥ 0.7 and < 0.9 | +----------+----------------------+ 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:** .. code-block:: text 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 ``fix``, ``bug``, or | 1.5 | | ``issue`` | | +---------------------------------------+--------+ | contains ``refactor`` or ``format`` | 0.5 | +---------------------------------------+--------+ | all other commits | 1.0 | +---------------------------------------+--------+ **Step 2 — hotspot score:** .. code-block:: text 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: .. list-table:: :header-rows: 1 :widths: 20 35 45 * - ``type`` - Use case - Commit reference pattern * - ``jira`` - Jira Cloud / Server with auth - ``PROJ-1234`` (configured ``project_prefix``) * - ``jira_public`` - Public Jira Server (no auth, e.g. jira.mongodb.org) - ``SERVER-1234`` (configured ``project_prefix``) * - ``github_issues`` - GitHub repositories (public or private with token) - ``#123`` (``project_prefix`` is fixed to ``"#"``) Example configurations: .. code-block:: yaml # 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):** 1. The importer scans every commit message for ticket references matching the configured provider pattern (e.g. ``PESWA-\d+`` for Jira, ``#\d+`` for GitHub). 2. 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). 3. The resolved type (``Bug``, ``Story``, ``Task``, ``Epic``, …) is stored in ``commit_tickets.ticket_type``. 4. 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):** .. code-block:: text 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:** .. code-block:: text 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):** .. code-block:: text 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: .. code-block:: text 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 %**). .. code-block:: text ownership_share = developer_commits / total_file_commits The **silo ratio** for a module is the fraction of its files that are silos: .. code-block:: text 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: .. code-block:: text 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. +------------------------+------------------------------------------------+ | Reason code | Threshold | +========================+================================================+ | ``critical_hotspot`` | Normalised hotspot score ≥ 0.9 (critical) or | | | ≥ 0.7 (high) | +------------------------+------------------------------------------------+ | ``silo_risk`` | Ownership share ≥ 95 % (critical) or ≥ 80 % | | | (high) | +------------------------+------------------------------------------------+ | ``complexity_spike`` | Cognitive complexity > 3.0 × module average | | | (critical) or > 2.0 × (high) | +------------------------+------------------------------------------------+ | ``churn_outlier`` | Churn > 5 × module average (critical) or | | | > 3 × (high) | +------------------------+------------------------------------------------+ | ``legacy_reactivated`` | File age > 365 days AND churn in top quartile | | | → always high severity | +------------------------+------------------------------------------------+ | ``defect_attractor`` | Bug-linked commits ≥ 50 % of total (critical) | | | or ≥ 30 % (high); minimum 5 commits per file | +------------------------+------------------------------------------------+ ``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_date`` parameters. When omitted, the full import history is used. * **Component filter**: Passing a ``component_name`` restricts 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#), or ``qmlmetrics`` (QML) at import time. The latest value per file within the requested window is used (``arg_max`` by commit date). * **Authorship resolution**: Commit authors are resolved to canonical developer names via the ``aliases`` table before ownership calculations. Team membership uses time-bounded records (``valid_from`` / ``valid_to``) so historical attribution remains accurate after team restructuring.