-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathrun.py
More file actions
117 lines (95 loc) · 3.77 KB
/
Copy pathrun.py
File metadata and controls
117 lines (95 loc) · 3.77 KB
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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import argparse
import yaml
import pandas as pd
import numpy as np
import logging
import sys
import json
import time
import traceback
def setup_logging(log_file: str):
"""Configures Python logging to output to a file and the console."""
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(levelname)s - %(message)s",
handlers=[
logging.FileHandler(log_file),
logging.StreamHandler(sys.stdout)
]
)
def parse_args():
parser = argparse.ArgumentParser(description="MLOps Batch Job")
parser.add_argument('--input', required=True, help="Path to input CSV data")
parser.add_argument('--config', required=True, help="Path to YAML config")
parser.add_argument('--output', required=True, help="Path to metrics JSON")
parser.add_argument('--log-file', required=True, help="Path to write logs")
return parser.parse_args()
def write_metrics(output_path: str, payload: dict):
"""Writes the JSON payload and prints it to stdout."""
with open(output_path, 'w') as f:
json.dump(payload, f, indent=4)
# The assessment specifically asks to print final metrics JSON to stdout
print(json.dumps(payload, indent=4))
def load_and_validate_config(config_path: str) -> dict:
with open(config_path, 'r') as f:
config = yaml.safe_load(f)
required_keys = ['seed', 'window', 'version']
for key in required_keys:
if key not in config:
raise ValueError(f"Config is missing '{key}'")
np.random.seed(config['seed'])
logging.info(f"Config loaded and validated. Seed: {config['seed']}, Window: {config['window']}")
return config
def process_data(filepath: str, window: int) -> pd.DataFrame:
df = pd.read_csv(filepath)
if 'close' not in df.columns or df.empty:
raise ValueError("Invalid dataset: Missing 'close' column or empty data.")
logging.info(f"Successfully loaded {len(df)} rows.")
logging.info("Computing rolling mean...")
df['rolling_mean'] = df['close'].rolling(window=window).mean()
logging.info("Generating signals...")
df['signal'] = (df['close'] > df['rolling_mean']).astype(int)
return df
def main():
start_time = time.time()
# 1. We must parse arguments before we can set up the log file
args = parse_args()
setup_logging(args.log_file)
logging.info("Job started.")
version = "unknown"
try:
# 2. Load Config
config = load_and_validate_config(args.config)
version = config.get('version', 'unknown')
# 3. Process Data
df = process_data(args.input, config['window'])
# 4. Compute Metrics
latency_ms = int((time.time() - start_time) * 1000)
signal_rate = df['signal'].mean()
success_metrics = {
"version": version,
"rows_processed": len(df),
"metric": "signal_rate",
"value": round(float(signal_rate), 4),
"latency_ms": latency_ms,
"seed": config['seed'],
"status": "success"
}
# 5. Write Output
logging.info("Writing success metrics...")
write_metrics(args.output, success_metrics)
logging.info("Job completed successfully.")
sys.exit(0)
except Exception as e:
# Catch ANY error, log the stack trace, and write the error metrics JSON
logging.error(f"Job failed: {str(e)}")
logging.debug(traceback.format_exc())
error_metrics = {
"version": version,
"status": "error",
"error_message": str(e)
}
write_metrics(args.output, error_metrics)
sys.exit(1) # Must exit with a non-zero failure code
if __name__ == "__main__":
main()