Add EMOS retraining snapshot limit and verbose logging

This commit is contained in:
2569718930@qq.com
2026-04-19 04:25:15 +08:00
parent 260550763f
commit 855b1df8dc
4 changed files with 120 additions and 10 deletions
@@ -67,8 +67,21 @@ def _write_json(path: str, payload: Dict[str, Any]) -> None:
json.dump(payload, fh, ensure_ascii=False, indent=2)
def _run_python(args: List[str]) -> Dict[str, Any]:
def _run_python(args: List[str], *, stream: bool = False) -> Dict[str, Any]:
command = [sys.executable, *args]
if stream:
completed = subprocess.run(
command,
cwd=PROJECT_ROOT,
text=True,
check=False,
)
return {
"command": command,
"returncode": completed.returncode,
"stdout": "",
"stderr": "",
}
completed = subprocess.run(
command,
cwd=PROJECT_ROOT,
@@ -178,6 +191,17 @@ def main() -> int:
action="store_true",
help="Run focused probability tests before promotion.",
)
parser.add_argument(
"--verbose",
action="store_true",
help="Print child script progress while training and evaluating.",
)
parser.add_argument(
"--snapshot-limit",
type=int,
default=_env_int("POLYWEATHER_EMOS_TRAINING_SNAPSHOT_LIMIT", 0),
help="Optional max number of recent probability snapshots to load from SQLite.",
)
parser.add_argument(
"--min-samples",
type=int,
@@ -211,14 +235,20 @@ def main() -> int:
evaluation_path = os.path.join(candidate_dir, "evaluation_report.json")
decision_path = os.path.join(candidate_dir, "decision_report.json")
fit_result = _run_python(
[
fit_args = [
"scripts/fit_probability_calibration.py",
"--output",
candidate_path,
"--version",
version,
]
]
if args.verbose:
fit_args.append("--verbose")
if args.snapshot_limit and args.snapshot_limit > 0:
fit_args.extend(["--snapshot-limit", str(args.snapshot_limit)])
fit_result = _run_python(
fit_args,
stream=args.verbose,
)
if fit_result["returncode"] != 0:
payload = {
@@ -239,7 +269,8 @@ def main() -> int:
candidate_path,
"--output",
evaluation_path,
]
],
stream=args.verbose,
)
if eval_result["returncode"] != 0:
payload = {
+67 -3
View File
@@ -34,6 +34,21 @@ def _sf(value):
return None
def _env_int(name, default=None):
try:
value = os.getenv(name)
if value is None or str(value).strip() == "":
return default
return int(value)
except Exception:
return default
def _log(enabled, message):
if enabled:
print(f"[fit_probability_calibration] {message}", flush=True)
def _load_json_if_exists(path):
if not path or not os.path.exists(path):
return {}
@@ -96,9 +111,28 @@ def _load_training_feature_history():
return {}
def _load_snapshot_rows(path):
def _load_snapshot_rows(path, limit=None):
if get_state_storage_mode() == STATE_STORAGE_SQLITE:
return ProbabilitySnapshotRepository().load_all_rows()
repo = ProbabilitySnapshotRepository()
if limit is not None and int(limit) > 0:
with repo.db.connect() as conn:
rows = conn.execute(
"""
SELECT payload_json
FROM probability_training_snapshots_store
ORDER BY id DESC
LIMIT ?
""",
(int(limit),),
).fetchall()
out = []
for row in reversed(rows):
try:
out.append(json.loads(row["payload_json"]))
except Exception:
continue
return out
return repo.load_all_rows()
rows = []
if not path or not os.path.exists(path):
return rows
@@ -410,14 +444,42 @@ def main():
default=None,
help="Optional explicit calibration version.",
)
parser.add_argument(
"--snapshot-limit",
type=int,
default=_env_int("POLYWEATHER_EMOS_TRAINING_SNAPSHOT_LIMIT"),
help="Optional max number of recent probability snapshots to load from SQLite.",
)
parser.add_argument(
"--verbose",
action="store_true",
help="Print data loading and fitting progress.",
)
args = parser.parse_args()
_log(args.verbose, "loading daily records")
history = _load_history_with_fallback(args.history_file)
_log(args.verbose, f"loaded daily record cities={len(history or {})}")
_log(args.verbose, "loading training feature history")
training_feature_history = _load_training_feature_history()
_log(args.verbose, f"loaded training feature cities={len(training_feature_history or {})}")
_log(args.verbose, "loading truth history")
truth_history = _load_truth_history()
_log(args.verbose, f"loaded truth cities={len(truth_history or {})}")
_log(args.verbose, "loading settlement history")
settlement_history = _load_json_if_exists(args.settlement_history)
snapshot_rows = _load_snapshot_rows(args.snapshot_file)
_log(args.verbose, f"loaded settlement history cities={len(settlement_history or {})}")
_log(
args.verbose,
"loading probability snapshots"
+ (f" limit={args.snapshot_limit}" if args.snapshot_limit else ""),
)
snapshot_rows = _load_snapshot_rows(args.snapshot_file, limit=args.snapshot_limit)
_log(args.verbose, f"loaded probability snapshots={len(snapshot_rows or [])}")
_log(args.verbose, "loading legacy training archive")
legacy_training_samples = _load_legacy_training_samples()
_log(args.verbose, f"loaded legacy training samples={len(legacy_training_samples or [])}")
_log(args.verbose, "extracting EMOS samples")
samples, filled_actual_from_history = _extract_samples(
history,
training_feature_history=training_feature_history,
@@ -426,6 +488,7 @@ def main():
snapshot_rows=snapshot_rows,
)
samples = merge_samples_with_legacy_archive(samples, legacy_training_samples)
_log(args.verbose, f"fitting calibration samples={len(samples or [])}")
calibration = fit_calibration(samples, version=args.version)
if not samples:
calibration = default_calibration_payload(
@@ -447,6 +510,7 @@ def main():
with open(args.output, "w", encoding="utf-8") as fh:
json.dump(calibration, fh, ensure_ascii=False, indent=2)
_log(args.verbose, "done")
print(
"saved calibration to {path} with {count} samples".format(
path=args.output,