Coverage for src / monte_neo / cli / progress.py: 61%
44 statements
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
« prev ^ index » next coverage.py v7.13.1, created at 2026-01-28 16:27 +0200
1"""Progress tracking module.
3Progress bars and ETA estimation.
4"""
6from __future__ import annotations
8import time
10from rich.progress import (
11 BarColumn,
12 Progress,
13 SpinnerColumn,
14 TaskID,
15 TextColumn,
16 TimeElapsedColumn,
17 TimeRemainingColumn,
18)
20from monte_neo.utils.console import console
23class ProgressTracker:
24 """Track progress with ETA estimation.
26 V0.0.5 Development Stages:
27 1. Metal Foundation: C++/Metal bridge and BaseKernel. [DONE]
28 2. Indicators Library: SMA, EMA, RSI, ATR in MSL. [DONE]
29 3. Optimization: GPU Grid Search and Walk-Forward. [DONE]
30 4. Production Gate: Robustness scoring and Certification. [DONE]
31 """
33 def __init__(self) -> None:
34 """Initialize tracker."""
35 self._progress: Progress | None = None
36 self._task_id: TaskID | None = None
37 self._start_time: float = 0
38 self._total: int = 0
40 def start(self, total: int, description: str = "Processing...") -> None:
41 """Start progress tracking.
43 Args:
44 total: Total number of items.
45 description: Progress description.
46 """
47 self._total = total
48 self._start_time = time.time()
50 self._progress = Progress(
51 SpinnerColumn(),
52 TextColumn("[bold blue]{task.description}"),
53 BarColumn(bar_width=40),
54 TextColumn("[progress.percentage]{task.percentage:>3.0f}%"),
55 TextColumn("•"),
56 TimeElapsedColumn(),
57 TextColumn("•"),
58 TimeRemainingColumn(),
59 TextColumn("[dim]{task.fields[status]}"),
60 console=console,
61 transient=True,
62 )
64 self._progress.start()
65 self._task_id = self._progress.add_task(
66 description,
67 total=total,
68 status="",
69 )
71 def update(self, current: int, total: int, status: str = "") -> None:
72 """Update progress.
74 Args:
75 current: Current position.
76 total: Total items.
77 status: Status message.
78 """
79 if self._progress and self._task_id is not None:
80 # Ensure we don't exceed 100% in display
81 val = min(current, total)
82 self._progress.update(
83 self._task_id,
84 completed=val,
85 total=total,
86 status=status,
87 refresh=True if val >= total else False,
88 )
90 def stop(self) -> None:
91 """Stop progress tracking."""
92 if self._progress:
93 self._progress.stop()
94 self._progress = None
95 self._task_id = None
97 def get_eta_minutes(self, current: int) -> float:
98 """Get estimated time remaining in minutes.
100 Args:
101 current: Current position.
103 Returns:
104 Estimated minutes remaining.
105 """
106 if current == 0:
107 return 0
109 elapsed = time.time() - self._start_time
110 rate = current / elapsed
111 remaining = self._total - current
113 if rate > 0:
114 return (remaining / rate) / 60
116 return 0
119def estimate_generation_time(
120 data_size: int,
121 iterations: int,
122 mc_methods: int,
123) -> str:
124 """Estimate generation time.
126 Args:
127 data_size: Number of data points.
128 iterations: Generation iterations.
129 mc_methods: Number of MC methods.
131 Returns:
132 Human-readable time estimate.
133 """
134 # Rough estimation based on typical performance
135 base_time = 0.001 # seconds per iteration
136 mc_factor = 1 + (mc_methods * 0.5)
137 data_factor = data_size / 1000
139 total_seconds = base_time * iterations * mc_factor * data_factor
141 if total_seconds < 60:
142 return f"~{int(total_seconds)} seconds"
143 elif total_seconds < 3600:
144 return f"~{int(total_seconds / 60)} minutes"
145 else:
146 return f"~{total_seconds / 3600:.1f} hours"