File size: 1,694 Bytes
d40d01e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
"""Helpers partagés par les tests ``tests/measurements/``.

Centralise des utilitaires de test réutilisés par plusieurs fichiers
sprint, en particulier le garde-fou anti-hallucination du moteur
narratif.
"""

from __future__ import annotations

import re
from typing import Any


def numbers_in_payload(payload: Any) -> set[str]:
    """Collecte toutes les représentations numériques d'un payload de Fact.

    Inclut les variantes usuelles produites par ``str.format`` :
    ``5``, ``5.0``, ``5.00``, ``5.000``, ``5.0000``, etc., pour tolérer
    les patterns ``{x}`` et ``{x:.2f}`` dans les templates narratifs.

    Le walk est récursif (dict / list / tuple) et parse également les
    chaînes via ``\\d+(?:\\.\\d+)?`` pour couvrir les payloads où une
    valeur numérique a été pré-formatée en string.
    """
    out: set[str] = set()

    def _add_variants(v: Any) -> None:
        try:
            f = float(v)
        except (TypeError, ValueError):
            return
        out.add(str(v))
        out.add(str(f))
        if f == int(f):
            out.add(str(int(f)))
        for dec in (1, 2, 3, 4):
            out.add(f"{f:.{dec}f}")

    def _walk(x: Any) -> None:
        if isinstance(x, dict):
            for v in x.values():
                _walk(v)
        elif isinstance(x, (list, tuple)):
            for v in x:
                _walk(v)
        elif isinstance(x, bool):
            return
        elif isinstance(x, (int, float)):
            _add_variants(x)
        elif isinstance(x, str):
            for n in re.findall(r"\d+(?:\.\d+)?", x):
                _add_variants(n)

    _walk(payload)
    return out


__all__ = ["numbers_in_payload"]