Coverage for packages / core / common / utils.py: 59%
32 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-08 08:37 +1200
« prev ^ index » next coverage.py v7.13.5, created at 2026-05-08 08:37 +1200
1"""Common utility functions for HarnessElo."""
3from collections.abc import Sequence
4from datetime import date, datetime
5from typing import Any
8def parse_date(date_str: str) -> date:
9 """Parse date string in YYYY-MM-DD format.
11 Args:
12 date_str: Date string to parse
14 Returns:
15 Parsed date object
17 Raises:
18 ValueError: If date string is invalid
19 """
20 try:
21 return datetime.strptime(date_str, "%Y-%m-%d").date()
22 except ValueError as e:
23 raise ValueError(
24 f"Invalid date format '{date_str}', expected YYYY-MM-DD"
25 ) from e
28def format_date(d: date) -> str:
29 """Format date as YYYY-MM-DD string.
31 Args:
32 d: Date to format
34 Returns:
35 Formatted date string
36 """
37 return d.strftime("%Y-%m-%d")
40def get_distance_bucket(
41 distance_m: int | None,
42 buckets: Sequence[int],
43 mode: str = "thresholds",
44 bucket_size: int | None = None,
45) -> str:
46 """Get distance bucket label for a given distance.
48 Args:
49 distance_m: Distance in meters (can be None)
50 buckets: List of bucket thresholds in ascending order
51 mode: Bucketing mode ("thresholds" or "fixed")
52 bucket_size: Fixed bucket size in meters (required if mode is "fixed")
54 Returns:
55 Bucket label (e.g., "<1700", "1700-2000", ">2400")
56 """
57 if distance_m is None:
58 return "unknown"
60 if mode == "fixed":
61 if not bucket_size or bucket_size <= 0:
62 return "unknown"
63 start = (distance_m // bucket_size) * bucket_size
64 end = start + bucket_size - 1
65 return f"{start}-{end}"
67 for i, threshold in enumerate(buckets):
68 if distance_m < threshold:
69 if i == 0:
70 return f"<{threshold}"
71 else:
72 return f"{buckets[i-1]}-{threshold}"
74 # Distance is greater than all thresholds
75 return f">{buckets[-1]}"
78def safe_get(d: dict[str, Any], *keys: str, default: Any = None) -> Any:
79 """Safely get nested dictionary value.
81 Args:
82 d: Dictionary to query
83 *keys: Sequence of keys to traverse
84 default: Default value if key path not found
86 Returns:
87 Value at key path or default
89 Example:
90 >>> data = {"a": {"b": {"c": 123}}}
91 >>> safe_get(data, "a", "b", "c")
92 123
93 >>> safe_get(data, "a", "x", "y", default=0)
94 0
95 """
96 current = d
97 for key in keys:
98 if not isinstance(current, dict) or key not in current:
99 return default
100 current = current[key]
101 return current