Implement Trader V4 training artifact pipeline
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import Any
|
||||
|
||||
from trader_training.io_utils import read_json, utc_now_text, write_json, write_text
|
||||
|
||||
|
||||
def promote_artifact_bundle(args: Any) -> None:
|
||||
artifact_root = args.artifact_root
|
||||
validation_path = artifact_root.parent / "artifact_validation_result.json"
|
||||
validation = read_json(validation_path)
|
||||
if validation.get("status") != "PASS":
|
||||
_refuse(artifact_root, args.reason, "validation result is not PASS", validation)
|
||||
if validation.get("release_gate_status") != "PASS":
|
||||
_refuse(artifact_root, args.reason, "release gate is not PASS", validation)
|
||||
|
||||
promotion = {
|
||||
"promoted_at": utc_now_text(),
|
||||
"reason": args.reason,
|
||||
"validation_result_path": str(validation_path),
|
||||
}
|
||||
|
||||
bundle_path = artifact_root / "manifests" / "model_bundle_manifest.json"
|
||||
bundle = read_json(bundle_path)
|
||||
|
||||
model_manifest_path = artifact_root / "manifests" / "model_manifest.json"
|
||||
model_rows = read_json(model_manifest_path)
|
||||
|
||||
calibration_manifest_path = artifact_root / "manifests" / "calibration_manifest.json"
|
||||
calibration_rows = read_json(calibration_manifest_path)
|
||||
|
||||
pm_manifest_path = artifact_root / "manifests" / "position_manager_manifest.json"
|
||||
pm_manifest = read_json(pm_manifest_path)
|
||||
|
||||
export_manifest_path = artifact_root / "manifests" / "training_export_manifest.json"
|
||||
export_manifest = read_json(export_manifest_path)
|
||||
|
||||
# Check every manifest before writing any ACTIVE status, so a bad bundle
|
||||
# cannot be left half-promoted if one file fails late.
|
||||
_require_candidate("model_bundle_manifest", bundle.get("status"))
|
||||
for row in model_rows:
|
||||
_require_candidate(f"model_manifest.{row.get('model_name')}", row.get("status"))
|
||||
for row in calibration_rows:
|
||||
_require_candidate(f"calibration_manifest.{row.get('model_name')}", row.get("status"))
|
||||
_require_candidate("position_manager_manifest", pm_manifest.get("status"))
|
||||
_require_candidate("training_export_manifest", export_manifest.get("status"))
|
||||
|
||||
bundle["status"] = "ACTIVE"
|
||||
bundle["promotion_json"] = promotion
|
||||
for row in model_rows:
|
||||
row["status"] = "ACTIVE"
|
||||
row["promotion_json"] = promotion
|
||||
for row in calibration_rows:
|
||||
row["status"] = "ACTIVE"
|
||||
row["promotion_json"] = promotion
|
||||
pm_manifest["status"] = "ACTIVE"
|
||||
pm_manifest["promotion_json"] = promotion
|
||||
export_manifest["status"] = "ACTIVE"
|
||||
export_manifest["promotion_json"] = promotion
|
||||
|
||||
write_json(bundle_path, bundle)
|
||||
write_json(model_manifest_path, model_rows)
|
||||
write_json(calibration_manifest_path, calibration_rows)
|
||||
write_json(pm_manifest_path, pm_manifest)
|
||||
write_json(export_manifest_path, export_manifest)
|
||||
|
||||
result = {"status": "ACTIVE", "artifact_root": str(artifact_root), "promotion": promotion}
|
||||
write_json(artifact_root.parent / "artifact_promotion_result.json", result)
|
||||
write_text(
|
||||
artifact_root.parent / "artifact_promotion_report.md",
|
||||
"\n".join(
|
||||
[
|
||||
"# Artifact Promotion Report",
|
||||
"",
|
||||
"- status: ACTIVE",
|
||||
f"- artifact_root: {artifact_root}",
|
||||
f"- reason: {args.reason}",
|
||||
f"- promoted_at: {promotion['promoted_at']}",
|
||||
"",
|
||||
]
|
||||
),
|
||||
)
|
||||
logging.info("trader.training.artifact_promoted status=ACTIVE path=%s reason=%s", artifact_root, args.reason)
|
||||
|
||||
|
||||
def _require_candidate(name: str, status: str | None) -> None:
|
||||
if status != "CANDIDATE":
|
||||
raise SystemExit(f"artifact promotion refused: {name} status must be CANDIDATE, actual={status}")
|
||||
|
||||
|
||||
def _refuse(artifact_root: Any, reason: str, message: str, validation: dict[str, Any]) -> None:
|
||||
result = {
|
||||
"status": "REFUSED",
|
||||
"artifact_root": str(artifact_root),
|
||||
"reason": reason,
|
||||
"message": message,
|
||||
"validation_status": validation.get("status"),
|
||||
"release_gate_status": validation.get("release_gate_status"),
|
||||
"release_gate_reasons": validation.get("release_gate_reasons", []),
|
||||
"refused_at": utc_now_text(),
|
||||
}
|
||||
write_json(artifact_root.parent / "artifact_promotion_result.json", result)
|
||||
write_text(
|
||||
artifact_root.parent / "artifact_promotion_report.md",
|
||||
"\n".join(
|
||||
[
|
||||
"# Artifact Promotion Report",
|
||||
"",
|
||||
"- status: REFUSED",
|
||||
f"- artifact_root: {artifact_root}",
|
||||
f"- reason: {reason}",
|
||||
f"- message: {message}",
|
||||
f"- validation_status: {result['validation_status']}",
|
||||
f"- release_gate_status: {result['release_gate_status']}",
|
||||
f"- release_gate_reasons: {result['release_gate_reasons']}",
|
||||
f"- refused_at: {result['refused_at']}",
|
||||
"",
|
||||
]
|
||||
),
|
||||
)
|
||||
logging.warning(
|
||||
"trader.training.artifact_promotion_refused status=REFUSED path=%s reason=%s message=%s releaseGate=%s",
|
||||
artifact_root,
|
||||
reason,
|
||||
message,
|
||||
result["release_gate_status"],
|
||||
)
|
||||
raise SystemExit(f"artifact promotion refused: {message}, reasons={result['release_gate_reasons']}")
|
||||
Reference in New Issue
Block a user