File size: 2,422 Bytes
2c527f4
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
"""Utility helpers for parsing and formatting values."""

from __future__ import annotations

import math
import re
from datetime import datetime
from typing import Any

import numpy as np


def to_float(value: Any, default: float = np.nan) -> float:
    """Convert mixed values to float and tolerate noisy numeric strings."""
    if value is None:
        return default
    if isinstance(value, (int, float)):
        if isinstance(value, float) and math.isnan(value):
            return default
        return float(value)

    text = str(value).strip()
    if not text:
        return default

    text = text.replace("'", "").replace("_", "")
    text = re.sub(r"[^0-9.,-]", "", text)

    if text.count(",") > 0 and text.count(".") > 0:
        text = text.replace(",", "")
    elif text.count(",") > 0 and text.count(".") == 0:
        text = text.replace(",", ".")

    try:
        return float(text)
    except ValueError:
        return default


def to_int(value: Any, default: int | None = None) -> int | None:
    parsed = to_float(value, np.nan)
    if np.isnan(parsed):
        return default
    return int(round(parsed))


def count_equipment_items(value: Any) -> int:
    """Count comma/semicolon separated equipment entries."""
    if value is None:
        return 0
    text = str(value).strip()
    if not text or text.lower() in {"none", "nan", "na"}:
        return 0

    separators = re.split(r"[,;|]", text)
    return len([part for part in separators if part.strip()])


def normalize_label(value: str | None, fallback: str = "unknown") -> str:
    if not value:
        return fallback
    text = str(value).strip()
    if not text:
        return fallback
    return text


def compute_age_from_year(year: Any) -> float:
    current_year = datetime.now().year
    year_int = to_int(year)
    if year_int is None:
        return np.nan
    age = current_year - year_int
    if age < 0 or age > 80:
        return np.nan
    return float(age)


def bool_from_yes_no(value: Any) -> str:
    if value is None:
        return "unknown"
    text = str(value).strip().lower()
    if text in {"1", "true", "yes", "ja", "y"}:
        return "yes"
    if text in {"0", "false", "no", "nein", "n"}:
        return "no"
    return "unknown"


def format_currency_chf(amount: float) -> str:
    if amount is None or np.isnan(amount):
        return "n/a"
    return f"CHF {amount:,.0f}".replace(",", "'")