Skip to content

Search

sereto.tui.search

FuzzyMatcher

Source code in sereto/tui/search.py
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
class FuzzyMatcher:
    def __init__(self, query: list[str]) -> None:
        self.query = [q.lower() for q in query if q.strip()]

    def highlight(self, text: list[str]) -> Text:
        """Highlight fuzzy matches in the given text."""
        combined = "; ".join(text)
        combined_lower = combined.lower()
        result_text = Text(combined)

        for q in self.query:
            if q in combined_lower:
                start = 0
                while True:
                    idx = combined_lower.find(q, start)
                    if idx == -1:
                        break
                    end = idx + len(q)
                    result_text.stylize("bold yellow", idx, end)
                    start = end
                continue

            for span in Matcher(q).highlight(combined).spans:
                span_text = combined[span.start : span.end]
                if len(span_text) <= len(q) + 2:
                    result_text.stylize("bold yellow", span.start, span.end)

        return result_text

highlight(text)

Highlight fuzzy matches in the given text.

Source code in sereto/tui/search.py
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
def highlight(self, text: list[str]) -> Text:
    """Highlight fuzzy matches in the given text."""
    combined = "; ".join(text)
    combined_lower = combined.lower()
    result_text = Text(combined)

    for q in self.query:
        if q in combined_lower:
            start = 0
            while True:
                idx = combined_lower.find(q, start)
                if idx == -1:
                    break
                end = idx + len(q)
                result_text.stylize("bold yellow", idx, end)
                start = end
            continue

        for span in Matcher(q).highlight(combined).spans:
            span_text = combined[span.start : span.end]
            if len(span_text) <= len(q) + 2:
                result_text.stylize("bold yellow", span.start, span.end)

    return result_text

ParsedSearchQuery dataclass

Structured representation of the raw search bar input.

Source code in sereto/tui/search.py
58
59
60
61
62
63
64
65
66
67
68
@dataclass(frozen=True)
class ParsedSearchQuery:
    """Structured representation of the raw search bar input."""

    raw: str
    free_terms: list[str] = field(default_factory=list)
    field_terms: dict[str, list[str]] = field(default_factory=dict)

    @property
    def has_terms(self) -> bool:
        return bool(self.free_terms or any(self.field_terms.values()))

SearchDiagnostics dataclass

Structured scoring details for one ranked result.

Attributes:

Name Type Description
free_text_score float

Aggregate contribution from untagged free-text terms.

clause_score float

Aggregate contribution from explicit field clauses.

phrase_bonus float

Extra score added when the full free-text phrase appears in a high-value field such as name or keyword.

final_score float

Final score after combining weighted contributions and bonuses.

passed_threshold bool

Whether the final score passed the display cutoff.

field_scores dict[str, float]

Weighted field contributions keyed by field name.

Source code in sereto/tui/search.py
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
@dataclass(frozen=True)
class SearchDiagnostics:
    """Structured scoring details for one ranked result.

    Attributes:
        free_text_score: Aggregate contribution from untagged free-text terms.
        clause_score: Aggregate contribution from explicit field clauses.
        phrase_bonus: Extra score added when the full free-text phrase appears
            in a high-value field such as name or keyword.
        final_score: Final score after combining weighted contributions and
            bonuses.
        passed_threshold: Whether the final score passed the display cutoff.
        field_scores: Weighted field contributions keyed by field name.
    """

    free_text_score: float = 0.0
    clause_score: float = 0.0
    phrase_bonus: float = 0.0
    final_score: float = 0.0
    passed_threshold: bool = False
    field_scores: dict[str, float] = field(default_factory=dict)

SearchDocument dataclass

Searchable representation of one indexed finding template.

Source code in sereto/tui/search.py
71
72
73
74
75
76
@dataclass(frozen=True)
class SearchDocument[T]:
    """Searchable representation of one indexed finding template."""

    payload: T
    fields: dict[str, list[str]]

SearchField dataclass

Description of one searchable field in the finding index.

Attributes:

Name Type Description
name str

Canonical field name used internally and in the query syntax.

label str

Human-readable label used in the UI and explanations.

aliases tuple[str, ...]

Alternative operator names accepted in the query, such as short forms like n for name.

default_weight float

Relative importance of this field for free-text terms that do not explicitly target a field.

Free-text search evaluates the term against every field and then multiplies the field score by default_weight. A larger value makes the field more likely to win for that term. These values are not probabilities and do not need to add up to 1.

clause_weight float

Relative importance of this field when the user writes an explicit field clause such as impact:rce.

Clause scores are combined using a weighted average, so this value only needs to express importance relative to the other fields. It also does not need to add up to 1 with the other weights.

previewable bool

Whether matches in this field should also be highlighted in the finding preview screen.

Source code in sereto/tui/search.py
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
@dataclass(frozen=True)
class SearchField:
    """Description of one searchable field in the finding index.

    Attributes:
        name: Canonical field name used internally and in the query syntax.
        label: Human-readable label used in the UI and explanations.
        aliases: Alternative operator names accepted in the query, such as
            short forms like `n` for `name`.
        default_weight: Relative importance of this field for free-text terms
            that do not explicitly target a field.

            Free-text search evaluates the term against every field and then
            multiplies the field score by `default_weight`. A larger value
            makes the field more likely to win for that term. These values are
            not probabilities and do not need to add up to `1`.
        clause_weight: Relative importance of this field when the user writes
            an explicit field clause such as `impact:rce`.

            Clause scores are combined using a weighted average, so this value
            only needs to express importance relative to the other fields. It
            also does not need to add up to `1` with the other weights.
        previewable: Whether matches in this field should also be highlighted
            in the finding preview screen.
    """

    name: str
    label: str
    aliases: tuple[str, ...]
    default_weight: float = 0.0
    clause_weight: float = 1.0
    previewable: bool = False

    def all_names(self) -> tuple[str, ...]:
        return (self.name, *self.aliases)

SearchFieldRegistry

Registry of supported search fields and their query operators.

Source code in sereto/tui/search.py
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
class SearchFieldRegistry:
    """Registry of supported search fields and their query operators."""

    def __init__(self, fields: list[SearchField]) -> None:
        self.fields = fields
        self.by_name = {field.name: field for field in fields}

    def resolve(self, value: str, allow_prefix: bool = False) -> SearchField | None:
        """Resolve a query operator to a registered search field."""
        normalized = _normalize(value)
        if not normalized:
            return None

        for search_field in self.fields:
            if normalized in (_normalize(alias) for alias in search_field.all_names()):
                return search_field

        if not allow_prefix:
            return None

        matches = [search_field for search_field in self.fields if self._matches_prefix(search_field, normalized)]
        return matches[0] if len(matches) == 1 else None

    def previewable_fields(self) -> list[SearchField]:
        return [search_field for search_field in self.fields if search_field.previewable]

    @staticmethod
    def _matches_prefix(search_field: SearchField, prefix: str) -> bool:
        return any(_normalize(value).startswith(prefix) for value in search_field.all_names() + (search_field.label,))

resolve(value, allow_prefix=False)

Resolve a query operator to a registered search field.

Source code in sereto/tui/search.py
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
def resolve(self, value: str, allow_prefix: bool = False) -> SearchField | None:
    """Resolve a query operator to a registered search field."""
    normalized = _normalize(value)
    if not normalized:
        return None

    for search_field in self.fields:
        if normalized in (_normalize(alias) for alias in search_field.all_names()):
            return search_field

    if not allow_prefix:
        return None

    matches = [search_field for search_field in self.fields if self._matches_prefix(search_field, normalized)]
    return matches[0] if len(matches) == 1 else None

SearchReason dataclass

Explanation of why a document matched a query.

Source code in sereto/tui/search.py
79
80
81
82
83
84
85
@dataclass(frozen=True)
class SearchReason:
    """Explanation of why a document matched a query."""

    field_name: str
    label: str
    score: float

SearchResult dataclass

Ranked search result together with its scoring metadata.

Source code in sereto/tui/search.py
88
89
90
91
92
93
94
95
@dataclass(frozen=True)
class SearchResult[T]:
    """Ranked search result together with its scoring metadata."""

    document: SearchDocument[T]
    score: float
    reasons: list[SearchReason]
    diagnostics: "SearchDiagnostics"

build_matchers(query, fields)

Create field-specific matchers for highlighting query terms.

Source code in sereto/tui/search.py
261
262
263
264
265
266
267
268
269
270
271
def build_matchers(query: ParsedSearchQuery, fields: SearchFieldRegistry) -> dict[str, FuzzyMatcher]:
    """Create field-specific matchers for highlighting query terms."""
    matchers: dict[str, FuzzyMatcher] = {}

    for search_field in fields.fields:
        values = [*query.field_terms.get(search_field.name, [])]
        values.extend(query.free_terms)
        if values:
            matchers[search_field.name] = FuzzyMatcher(values)

    return matchers

is_search_debug_visible()

Return whether inline search diagnostics should be shown in the TUI.

Source code in sereto/tui/search.py
15
16
17
18
def is_search_debug_visible() -> bool:
    """Return whether inline search diagnostics should be shown in the TUI."""

    return get_log_config().level in {LogLevel.DEBUG, LogLevel.TRACE}

parse_search_query(query, fields)

Parse the raw search string into free-text terms and field clauses.

Free-text terms are used for the default search mode, which primarily targets finding names, keywords, and group hints. Tokens in the form field:value are parsed as explicit field clauses when field matches a registered field name or alias.

Source code in sereto/tui/search.py
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
def parse_search_query(query: str, fields: SearchFieldRegistry) -> ParsedSearchQuery:
    """Parse the raw search string into free-text terms and field clauses.

    Free-text terms are used for the default search mode, which primarily
    targets finding names, keywords, and group hints. Tokens in the form
    `field:value` are
    parsed as explicit field clauses when `field` matches a registered
    field name or alias.
    """
    tokens = _split_query(query)
    free_terms: list[str] = []
    field_terms: dict[str, list[str]] = {search_field.name: [] for search_field in fields.fields}

    for token in tokens:
        if ":" not in token:
            if token.strip():
                free_terms.append(token)
            continue

        raw_field, raw_value = token.split(":", 1)
        resolved = fields.resolve(raw_field)
        if resolved is None:
            free_terms.append(token)
            continue

        value = raw_value.strip()
        if not value:
            continue

        field_terms[resolved.name].append(value)

    return ParsedSearchQuery(
        raw=query,
        free_terms=free_terms,
        field_terms={key: values for key, values in field_terms.items() if values},
    )

rank_documents(documents, query, fields)

Rank documents by combining free-text and field-specific matches.

The ranking pipeline has two main inputs:

  • free-text terms, weighted using SearchField.default_weight
  • explicit field clauses, weighted using SearchField.clause_weight

The returned score is a relative ranking value in the 0 to 100 range. The weighting values are tuning multipliers and are not expected to form a normalized distribution.

Source code in sereto/tui/search.py
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
def rank_documents[T](
    documents: list[SearchDocument[T]],
    query: ParsedSearchQuery,
    fields: SearchFieldRegistry,
) -> list[SearchResult[T]]:
    """Rank documents by combining free-text and field-specific matches.

    The ranking pipeline has two main inputs:

    - free-text terms, weighted using `SearchField.default_weight`
    - explicit field clauses, weighted using `SearchField.clause_weight`

    The returned score is a relative ranking value in the `0` to `100`
    range. The weighting values are tuning multipliers and are not expected to
    form a normalized distribution.
    """
    if not query.has_terms:
        return []

    results: list[SearchResult[T]] = []
    for document in documents:
        result = _rank_document(document, query, fields)
        if result is not None:
            results.append(result)

    return sorted(results, key=lambda result: result.score, reverse=True)

should_display_result(score)

Return whether a ranked result is strong enough to display.

Source code in sereto/tui/search.py
328
329
330
def should_display_result(score: float) -> bool:
    """Return whether a ranked result is strong enough to display."""
    return score >= _MATCH_THRESHOLD

summarize_query(query, fields)

Render the parsed query back into a compact user-facing summary.

Source code in sereto/tui/search.py
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
def summarize_query(query: ParsedSearchQuery, fields: SearchFieldRegistry) -> Text:
    """Render the parsed query back into a compact user-facing summary."""
    if not query.has_terms:
        return Text("Search name, keywords, and group hints by default.", style="dim")

    parts: list[Text] = []
    if query.free_terms:
        parts.append(Text("Text: ", style="bold cyan"))
        parts.append(Text(", ".join(query.free_terms), style="white"))

    for field_name, values in query.field_terms.items():
        field = fields.by_name[field_name]
        if parts:
            parts.append(Text("  |  ", style="dim"))
        parts.append(Text(f"{field.label}: ", style="bold green"))
        parts.append(Text(", ".join(values), style="white"))

    return Text.assemble(*parts)

supported_operator_text(fields)

Render the static help text listing the supported field operators.

Source code in sereto/tui/search.py
322
323
324
325
def supported_operator_text(fields: SearchFieldRegistry) -> Text:
    """Render the static help text listing the supported field operators."""
    field_list = ", ".join(f"{field.name}:" for field in fields.fields)
    return Text(f"Operators: {field_list}", style="dim")