AccuracyReportRenderer.java

1
// SPDX-License-Identifier: Apache-2.0
2
// Copyright 2026 Egothor
3
// Copyright 2026 Accenture
4
package org.egothor.methodatlas.eval;
5
6
import java.util.ArrayList;
7
import java.util.List;
8
import java.util.Locale;
9
import java.util.OptionalDouble;
10
11
/**
12
 * Renders an {@link AccuracyEvaluator.Evaluation} as Markdown or JSON.
13
 *
14
 * <h2>Determinism</h2>
15
 *
16
 * <p>
17
 * The output is a pure function of the evaluation: lines end with {@code \n} on
18
 * every platform (never the platform line separator), numbers are formatted with
19
 * {@link Locale#ROOT} to four decimal places (never a locale decimal comma), tags
20
 * are listed in sorted order, and the report contains no timestamp, path or host
21
 * information. Two runs over the same inputs are therefore byte-identical on any
22
 * operating system, in line with the cross-platform determinism contract of the
23
 * scan output.
24
 * </p>
25
 *
26
 * <p>
27
 * An undefined metric (zero denominator, see {@link AccuracyMetrics}) is rendered
28
 * as {@code n/a} in Markdown and as JSON {@code null}.
29
 * </p>
30
 *
31
 * <p>
32
 * This class is stateless and thread-safe.
33
 * </p>
34
 *
35
 * @see AccuracyEvaluator
36
 * @since 6.1.0
37
 */
38
public final class AccuracyReportRenderer {
39
40
    /** JSON report schema version; bump on any breaking field change. */
41
    public static final int JSON_SCHEMA_VERSION = 1;
42
43
    private static final char LF = '\n';
44
    private static final String NOT_AVAILABLE = "n/a";
45
    private static final String JSON_NULL = "null";
46
    private static final String NUMBER_FORMAT = "%.4f";
47
    private static final String JSON_SEP = ", ";
48
    private static final String EMPTY_CELL = " |";
49
    private static final String LEFT = "---";
50
    private static final String RIGHT = "---:";
51
    private static final int BUILDER_CAPACITY = 2048;
52
    private static final char[] HEX = "0123456789abcdef".toCharArray();
53
    private static final int CONTROL_LIMIT = 0x20;
54
    private static final int NIBBLE_SHIFT = 4;
55
    private static final int NIBBLE_MASK = 0xF;
56
57
    private AccuracyReportRenderer() {
58
        // static utility; no instances
59
    }
60
61
    /**
62
     * Renders the evaluation as a Markdown document.
63
     *
64
     * @param ev evaluation to render; must not be {@code null}
65
     * @return Markdown text with {@code \n} line endings, ending in a newline
66
     */
67
    public static String markdown(AccuracyEvaluator.Evaluation ev) {
68
        AccuracyMetrics.Result m = ev.metrics();
69
        AccuracyMetrics.Counts r = m.relevance();
70
        StringBuilder sb = new StringBuilder(BUILDER_CAPACITY);
71
        line(sb, "# MethodAtlas AI accuracy evaluation\n");
72
73
        line(sb, "## Coverage\n");
74
        row(sb, "Item", "Count");
75
        row(sb, LEFT, RIGHT);
76
        row(sb, "Prediction rows read", String.valueOf(ev.predictionRows()));
77
        row(sb, "Evaluated (labelled and predicted)", String.valueOf(ev.evaluated()));
78
        row(sb, "Predictions without a label", String.valueOf(ev.unlabelledPredictions()));
79
        row(sb, "Labelled methods without an AI prediction", String.valueOf(ev.unpredictedLabels()));
80
        row(sb, "Label entries matching no scanned method", String.valueOf(ev.staleLabelEntries()));
81
        line(sb, "");
82
83
        line(sb, "## securityRelevant\n");
84
        line(sb, "Samples: " + m.relevanceSamples() + "\n");
85
        row(sb, "", "Truth: relevant", "Truth: not relevant");
86
        row(sb, LEFT, RIGHT, RIGHT);
87
        row(sb, "**Predicted: relevant**", "TP " + r.tp(), "FP " + r.fp());
88
        row(sb, "**Predicted: not relevant**", "FN " + r.fn(), "TN " + r.tn());
89
        line(sb, "");
90
        row(sb, "Metric", "Value");
91
        row(sb, LEFT, RIGHT);
92
        row(sb, "Precision", format(r.precision()));
93
        row(sb, "Recall", format(r.recall()));
94
        row(sb, "F1", format(r.f1()));
95
        row(sb, "Accuracy", format(r.accuracy()));
96
        row(sb, "Support", String.valueOf(r.support()));
97
        line(sb, "");
98
99
        line(sb, "## Taxonomy tags\n");
100
        line(sb, "Samples: " + m.tagSamples() + "\n");
101
        row(sb, "Tag", "Precision", "Recall", "F1", "Support", "TP", "FP", "FN");
102
        row(sb, LEFT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT, RIGHT);
103
        for (AccuracyMetrics.TagResult t : m.tags()) {
104
            tagRow(sb, t.tag(), t.counts());
105
        }
106
        tagRow(sb, "**micro-average**", m.tagsMicro());
107
        row(sb, "**macro-average**", "", "", format(m.tagsMacroF1()), "", "", "", "");
108 1 1. markdown : replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::markdown → KILLED
        return sb.toString();
109
    }
110
111
    /**
112
     * Renders the evaluation as a JSON document.
113
     *
114
     * <p>
115
     * Keys appear in a fixed order. Undefined metrics are {@code null}.
116
     * </p>
117
     *
118
     * @param ev evaluation to render; must not be {@code null}
119
     * @return JSON text with {@code \n} line endings, ending in a newline
120
     */
121
    public static String json(AccuracyEvaluator.Evaluation ev) {
122
        AccuracyMetrics.Result m = ev.metrics();
123
        List<String> tagObjects = new ArrayList<>(m.tags().size());
124
        for (AccuracyMetrics.TagResult t : m.tags()) {
125
            tagObjects.add("    {\"tag\": " + quote(t.tag()) + JSON_SEP + countFields(t.counts(), false) + "}");
126
        }
127 2 1. json : removed conditional - replaced equality check with false → SURVIVED
2. json : removed conditional - replaced equality check with true → KILLED
        String tags = tagObjects.isEmpty() ? "[]" : "[\n" + String.join(",\n", tagObjects) + "\n  ]";
128
129
        StringBuilder sb = new StringBuilder(BUILDER_CAPACITY);
130
        line(sb, "{");
131
        line(sb, "  \"schemaVersion\": " + JSON_SCHEMA_VERSION + ",");
132
        line(sb, "  \"coverage\": {");
133
        line(sb, "    \"predictionRows\": " + ev.predictionRows() + ",");
134
        line(sb, "    \"evaluated\": " + ev.evaluated() + ",");
135
        line(sb, "    \"unlabelledPredictions\": " + ev.unlabelledPredictions() + ",");
136
        line(sb, "    \"unpredictedLabels\": " + ev.unpredictedLabels() + ",");
137
        line(sb, "    \"staleLabelEntries\": " + ev.staleLabelEntries());
138
        line(sb, "  },");
139
        line(sb, "  \"securityRelevant\": {" + countFields(m.relevance(), true) + "},");
140
        line(sb, "  \"tags\": " + tags + ",");
141
        line(sb, "  \"tagsMicro\": {" + countFields(m.tagsMicro(), false) + "},");
142
        line(sb, "  \"tagsMacroF1\": " + jsonNumber(m.tagsMacroF1()));
143
        line(sb, "}");
144 1 1. json : replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::json → KILLED
        return sb.toString();
145
    }
146
147
    /** Appends {@code text} followed by a single LF. */
148
    private static void line(StringBuilder sb, String text) {
149
        sb.append(text).append(LF);
150
    }
151
152
    /** Appends one Markdown table row; empty cells render as {@code | |}. */
153
    private static void row(StringBuilder sb, String... cells) {
154
        sb.append('|');
155
        for (String cell : cells) {
156 2 1. row : removed conditional - replaced equality check with false → KILLED
2. row : removed conditional - replaced equality check with true → KILLED
            sb.append(cell.isEmpty() ? EMPTY_CELL : " " + cell + EMPTY_CELL);
157
        }
158
        sb.append(LF);
159
    }
160
161
    private static void tagRow(StringBuilder sb, String name, AccuracyMetrics.Counts c) {
162
        row(sb, name, format(c.precision()), format(c.recall()), format(c.f1()),
163
                String.valueOf(c.support()), String.valueOf(c.tp()), String.valueOf(c.fp()),
164
                String.valueOf(c.fn()));
165
    }
166
167
    /** Returns the fields of a counts object without braces; {@code full} adds tn and accuracy. */
168
    private static String countFields(AccuracyMetrics.Counts c, boolean full) {
169
        List<String> fields = new ArrayList<>();
170
        fields.add("\"tp\": " + c.tp());
171
        fields.add("\"fp\": " + c.fp());
172
        fields.add("\"fn\": " + c.fn());
173 2 1. countFields : removed conditional - replaced equality check with true → SURVIVED
2. countFields : removed conditional - replaced equality check with false → KILLED
        if (full) {
174
            fields.add("\"tn\": " + c.tn());
175
        }
176
        fields.add("\"support\": " + c.support());
177
        fields.add("\"precision\": " + jsonNumber(c.precision()));
178
        fields.add("\"recall\": " + jsonNumber(c.recall()));
179
        fields.add("\"f1\": " + jsonNumber(c.f1()));
180 2 1. countFields : removed conditional - replaced equality check with true → SURVIVED
2. countFields : removed conditional - replaced equality check with false → SURVIVED
        if (full) {
181
            fields.add("\"accuracy\": " + jsonNumber(c.accuracy()));
182
        }
183 1 1. countFields : replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::countFields → KILLED
        return String.join(JSON_SEP, fields);
184
    }
185
186
    private static String format(OptionalDouble value) {
187 3 1. format : removed conditional - replaced equality check with false → KILLED
2. format : removed conditional - replaced equality check with true → KILLED
3. format : replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::format → KILLED
        return value.isPresent() ? String.format(Locale.ROOT, NUMBER_FORMAT, value.getAsDouble()) : NOT_AVAILABLE;
188
    }
189
190
    private static String jsonNumber(OptionalDouble value) {
191 3 1. jsonNumber : removed conditional - replaced equality check with false → KILLED
2. jsonNumber : removed conditional - replaced equality check with true → KILLED
3. jsonNumber : replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::jsonNumber → KILLED
        return value.isPresent() ? String.format(Locale.ROOT, NUMBER_FORMAT, value.getAsDouble()) : JSON_NULL;
192
    }
193
194
    private static String quote(String s) {
195 1 1. quote : Replaced integer addition with subtraction → SURVIVED
        StringBuilder sb = new StringBuilder(s.length() + 2);
196
        sb.append('"');
197 3 1. quote : removed conditional - replaced comparison check with false → KILLED
2. quote : changed conditional boundary → KILLED
3. quote : removed conditional - replaced comparison check with true → KILLED
        for (int i = 0; i < s.length(); i++) {
198
            appendEscaped(sb, s.charAt(i));
199
        }
200 1 1. quote : replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::quote → KILLED
        return sb.append('"').toString();
201
    }
202
203
    private static void appendEscaped(StringBuilder sb, char ch) {
204
        switch (ch) {
205
            case '"' -> sb.append("\\\"");
206
            case '\\' -> sb.append("\\\\");
207
            case '\n' -> sb.append("\\n");
208
            case '\r' -> sb.append("\\r");
209
            case '\t' -> sb.append("\\t");
210
            default -> {
211 3 1. appendEscaped : removed conditional - replaced comparison check with true → SURVIVED
2. appendEscaped : changed conditional boundary → SURVIVED
3. appendEscaped : removed conditional - replaced comparison check with false → KILLED
                if (ch < CONTROL_LIMIT) {
212 3 1. appendEscaped : Replaced Shift Right with Shift Left → SURVIVED
2. appendEscaped : Replaced bitwise AND with OR → KILLED
3. appendEscaped : Replaced bitwise AND with OR → KILLED
                    sb.append("\\u00").append(HEX[(ch >> NIBBLE_SHIFT) & NIBBLE_MASK]).append(HEX[ch & NIBBLE_MASK]);
213
                } else {
214
                    sb.append(ch);
215
                }
216
            }
217
        }
218
    }
219
}

Mutations

108

1.1
Location : markdown
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::markdown → KILLED

127

1.1
Location : json
Killed by : none
removed conditional - replaced equality check with false → SURVIVED
Covering tests

2.2
Location : json
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonEscapesSpecialCharactersInTagNames()]
removed conditional - replaced equality check with true → KILLED

144

1.1
Location : json
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::json → KILLED

156

1.1
Location : row
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : row
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
removed conditional - replaced equality check with true → KILLED

173

1.1
Location : countFields
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonIsWellFormedWithStableFieldsAndNullsForUndefined()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : countFields
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

180

1.1
Location : countFields
Killed by : none
removed conditional - replaced equality check with true → SURVIVED
Covering tests

2.2
Location : countFields
Killed by : none
removed conditional - replaced equality check with false → SURVIVED Covering tests

183

1.1
Location : countFields
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::countFields → KILLED

187

1.1
Location : format
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:markdownContainsTheDocumentedNumbers()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : format
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
removed conditional - replaced equality check with true → KILLED

3.3
Location : format
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::format → KILLED

191

1.1
Location : jsonNumber
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonIsWellFormedWithStableFieldsAndNullsForUndefined()]
removed conditional - replaced equality check with false → KILLED

2.2
Location : jsonNumber
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
removed conditional - replaced equality check with true → KILLED

3.3
Location : jsonNumber
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:emptyEvaluationRendersUndefinedMetricsWithoutErrors()]
replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::jsonNumber → KILLED

195

1.1
Location : quote
Killed by : none
Replaced integer addition with subtraction → SURVIVED
Covering tests

197

1.1
Location : quote
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonEscapesSpecialCharactersInTagNames()]
removed conditional - replaced comparison check with false → KILLED

2.2
Location : quote
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonEscapesSpecialCharactersInTagNames()]
changed conditional boundary → KILLED

3.3
Location : quote
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonEscapesSpecialCharactersInTagNames()]
removed conditional - replaced comparison check with true → KILLED

200

1.1
Location : quote
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonEscapesSpecialCharactersInTagNames()]
replaced return value with "" for org/egothor/methodatlas/eval/AccuracyReportRenderer::quote → KILLED

211

1.1
Location : appendEscaped
Killed by : none
removed conditional - replaced comparison check with true → SURVIVED
Covering tests

2.2
Location : appendEscaped
Killed by : none
changed conditional boundary → SURVIVED Covering tests

3.3
Location : appendEscaped
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonEscapesSpecialCharactersInTagNames()]
removed conditional - replaced comparison check with false → KILLED

212

1.1
Location : appendEscaped
Killed by : none
Replaced Shift Right with Shift Left → SURVIVED
Covering tests

2.2
Location : appendEscaped
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonEscapesSpecialCharactersInTagNames()]
Replaced bitwise AND with OR → KILLED

3.3
Location : appendEscaped
Killed by : org.egothor.methodatlas.eval.AccuracyReportRendererTest.[engine:junit-jupiter]/[class:org.egothor.methodatlas.eval.AccuracyReportRendererTest]/[method:jsonEscapesSpecialCharactersInTagNames()]
Replaced bitwise AND with OR → KILLED

Active mutators

Tests examined


Report generated by PIT 1.30.1 support