• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

bramp / build-along / 20120615911

10 Dec 2025 10:24PM UTC coverage: 89.767% (+0.08%) from 89.689%
20120615911

push

github

bramp
Fix typo in fixtures README: .sh -> .py for regenerate script

11790 of 13134 relevant lines covered (89.77%)

0.9 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.67
/src/build_a_long/pdf_extract/classifier/steps/step_count_classifier.py
1
"""
2
Step count classifier.
3

4
Purpose
5
-------
6
Detect step-count text like "2x" that appears in substep callout boxes.
7
These are similar to part counts but use a larger font size (typically 16pt),
8
between part count size and step number size.
9

10
Debugging
11
---------
12
Enable DEBUG logs with LOG_LEVEL=DEBUG.
13
"""
14

15
import logging
1✔
16
from typing import ClassVar
1✔
17

18
from build_a_long.pdf_extract.classifier.candidate import Candidate
1✔
19
from build_a_long.pdf_extract.classifier.classification_result import (
1✔
20
    ClassificationResult,
21
)
22
from build_a_long.pdf_extract.classifier.rule_based_classifier import (
1✔
23
    RuleBasedClassifier,
24
)
25
from build_a_long.pdf_extract.classifier.rules import (
1✔
26
    FontSizeRangeRule,
27
    IsInstanceFilter,
28
    PartCountTextRule,
29
    Rule,
30
)
31
from build_a_long.pdf_extract.classifier.text import (
1✔
32
    extract_part_count_value,
33
)
34
from build_a_long.pdf_extract.extractor.lego_page_elements import (
1✔
35
    StepCount,
36
)
37
from build_a_long.pdf_extract.extractor.page_blocks import Text
1✔
38

39
log = logging.getLogger(__name__)
1✔
40

41

42
class StepCountClassifier(RuleBasedClassifier):
1✔
43
    """Classifier for step counts (substep counts like "2x").
44

45
    These are count labels that appear inside substep callout boxes,
46
    indicating how many times to build the sub-assembly.
47
    They use a font size between part counts and step numbers.
48
    """
49

50
    output: ClassVar[str] = "step_count"
1✔
51
    requires: ClassVar[frozenset[str]] = frozenset()
1✔
52

53
    @property
1✔
54
    def min_score(self) -> float:
1✔
55
        return self.config.step_count.min_score
1✔
56

57
    @property
1✔
58
    def rules(self) -> list[Rule]:
1✔
59
        config = self.config
1✔
60
        step_count_config = config.step_count
1✔
61
        hints = config.font_size_hints
1✔
62

63
        return [
1✔
64
            # Must be text
65
            IsInstanceFilter(Text),
66
            # Check if text matches count pattern (e.g., "2x", "4x")
67
            PartCountTextRule(
68
                weight=step_count_config.text_weight,
69
                name="text_score",
70
                required=True,
71
            ),
72
            # Score font size: should be >= part_count_size and <= step_number_size
73
            FontSizeRangeRule(
74
                min_size=hints.part_count_size,
75
                max_size=hints.step_number_size,
76
                tolerance=1.0,
77
                weight=step_count_config.font_size_weight,
78
                name="font_size_score",
79
            ),
80
        ]
81

82
    def build(self, candidate: Candidate, result: ClassificationResult) -> StepCount:
1✔
83
        """Construct a StepCount element from a candidate.
84

85
        The candidate may include additional source blocks (e.g., text outline
86
        effects) beyond the primary Text block.
87
        """
88
        # Get the primary text block (first in source_blocks)
89
        assert len(candidate.source_blocks) >= 1
1✔
90
        block = candidate.source_blocks[0]
1✔
91
        assert isinstance(block, Text)
1✔
92

93
        # Parse the count value
94
        value = extract_part_count_value(block.text)
1✔
95
        if value is None:
1✔
96
            raise ValueError(f"Could not parse step count from text: '{block.text}'")
×
97

98
        return StepCount(count=value, bbox=block.bbox)
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc