Skip to content

Reports

report

Build composable line-oriented reports for all supported pipelines.

Section builders deliberately omit run-level metadata such as dates or timestamps. This allows the full pipeline to reuse the typo-generation and AutoCorrect2 report bodies without duplicating document metadata.

create_typo_generation_report

create_typo_generation_report(
    result: TypoGenerationResult,
) -> list[str]

Create the typo-generation report body.

Parameters:

Name Type Description Default
result TypoGenerationResult

Aggregated typo-generation result.

required

Returns:

Type Description
list[str]

Report body as individual text lines.

Source code in hotstring\report.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
def create_typo_generation_report(result: TypoGenerationResult) -> list[str]:
    """Create the typo-generation report body.

    Args:
        result:
            Aggregated typo-generation result.

    Returns:
        Report body as individual text lines.
    """
    horizontal_weight, vertical_weight = result.config.horizontal_vs_vertical
    lines = [
        "TYPO GENERATION",
        "-" * 80,
        f"Source words: {result.source_word_count}",
        f"Generation tasks: {len(result.tasks)}",
        f"Language: {result.config.language}",
        f"Use excluding set: {result.config.use_excluding_set}",
        f"Horizontal/vertical keyboard weights: {horizontal_weight}/{vertical_weight}",
        f"Successful raw samples: {result.generated_sample_count}",
        f"Unique noisy words: {result.unique_noisy_word_count}",
        f"Valid candidates: {len(result.candidates)}",
        f"Internal clashes: {len(result.clashes)}",
        "",
        "TASKS",
        "-" * 80,
    ]

    for index, task in enumerate(result.tasks, start=1):
        lines.extend(
            (
                f"Task {index}",
                f"  Distribution: {task.distribution.distribution}",
                f"  Typo rate: {task.typo_rate}",
                f"  Generation attempts per word: {task.generation_attempts_per_word}",
                f"  Minimum word length: {task.minimum_word_length}",
            )
        )

    lines.extend(("", "VALID TYPO CANDIDATES", "-" * 80))
    if result.candidates:
        lines.extend(f"{noisy!r} -> {target!r}" for noisy, target in result.candidates.items())
    else:
        lines.append("None")

    lines.extend(("", "INTERNAL CLASHES", "-" * 80))
    if result.clashes:
        for noisy_word, targets in result.clashes.items():
            lines.append(f"{noisy_word!r}")
            lines.extend(f"  -> {target!r}" for target in targets)
    else:
        lines.append("None")
    return lines

create_autocorrect2_report

create_autocorrect2_report(
    result: AutoCorrect2CheckResult,
) -> list[str]

Create the AutoCorrect2 conflict-check report body.

Candidate names are displayed with their semantic trigger, while an existing conflict's rendered source line uses its canonical AHK trigger.

Parameters:

Name Type Description Default
result AutoCorrect2CheckResult

AutoCorrect2 candidate-check result.

required

Returns:

Type Description
list[str]

Report body as individual text lines.

Source code in hotstring\report.py
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
def create_autocorrect2_report(result: AutoCorrect2CheckResult) -> list[str]:
    """Create the AutoCorrect2 conflict-check report body.

    Candidate names are displayed with their semantic trigger, while an
    existing conflict's rendered source line uses its canonical AHK trigger.

    Args:
        result:
            AutoCorrect2 candidate-check result.

    Returns:
        Report body as individual text lines.
    """
    lines = [
        "AUTOCORRECT2 CONFLICT CHECK",
        "-" * 80,
        f"Candidates checked: {result.candidate_count}",
        f"Accepted candidates: {len(result.accepted)}",
        f"Rejected candidates: {len(result.rejected)}",
        "",
        "ACCEPTED",
        "-" * 80,
    ]

    if result.accepted:
        lines.extend(
            f"{candidate.semantic_trigger!r} -> {candidate.replacement!r}"
            for candidate in result.accepted
        )
    else:
        lines.append("None")

    lines.extend(("", "REJECTED", "-" * 80))
    if not result.rejected:
        lines.append("None")
        return lines

    for assessment in result.rejected:
        candidate = assessment.candidate
        lines.append(f"{candidate.semantic_trigger!r} -> {candidate.replacement!r}")
        for conflict in assessment.conflicts:
            lines.extend(
                (
                    f"  Existing: {conflict.existing.render()}",
                    f"  Source:   {conflict.existing.source}",
                    f"  Type:     {conflict.kind.value}",
                    f"  Reason:   {conflict.reason}",
                    "",
                )
            )
    return lines

create_full_pipeline_report

create_full_pipeline_report(
    typo_result: TypoGenerationResult,
    autocorrect2_result: AutoCorrect2CheckResult,
) -> list[str]

Create a full report by composing both stage-specific report bodies.

Source code in hotstring\report.py
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
def create_full_pipeline_report(
    typo_result: TypoGenerationResult,
    autocorrect2_result: AutoCorrect2CheckResult,
) -> list[str]:
    """Create a full report by composing both stage-specific report bodies."""
    lines = create_typo_generation_report(typo_result)
    lines.extend(("", "=" * 80, ""))
    lines.extend(create_autocorrect2_report(autocorrect2_result))
    lines.extend(
        (
            "",
            "FINAL SUMMARY",
            "-" * 80,
            f"Internal typo clashes removed: {len(typo_result.clashes)}",
            f"AutoCorrect2 conflicts removed: {len(autocorrect2_result.rejected)}",
            f"Final accepted hotstrings: {len(autocorrect2_result.accepted)}",
        )
    )
    return lines

build_report_document

build_report_document(
    title: str, body_lines: Sequence[str]
) -> list[str]

Wrap a report body with run-level document framing.

Source code in hotstring\report.py
145
146
147
148
149
def build_report_document(title: str, body_lines: Sequence[str]) -> list[str]:
    """Wrap a report body with run-level document framing."""
    if not title:
        raise ValueError("Report title cannot be empty.")
    return [title, "=" * 80, "", *body_lines]