Safey04 commited on
Commit
810a275
·
verified ·
1 Parent(s): 753114a

Upload handler.py with huggingface_hub

Browse files
Files changed (1) hide show
  1. handler.py +207 -0
handler.py ADDED
@@ -0,0 +1,207 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ import pandas as pd
4
+ import logging
5
+ from model import PoultryTFTModel
6
+ import numpy as np
7
+
8
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
9
+ logger = logging.getLogger(__name__)
10
+
11
+ class EndpointHandler:
12
+ def __init__(self, path="."):
13
+ """
14
+ Initializes the model handler for a Hugging Face Inference Endpoint.
15
+ It loads two separate models: one for mortality and one for weight prediction.
16
+
17
+ Args:
18
+ path (str): The path to the model directory where assets are stored.
19
+ """
20
+ # --- Environment Variables for Model Configuration ---
21
+ # Get model filenames from environment variables with defaults.
22
+ mortality_model_name = os.environ.get("MORTALITY_MODEL_NAME", "ml_models/tft_poultry_mortality.ckpt")
23
+ mortality_data_name = os.environ.get("MORTALITY_DATA_NAME", "ts_dataset/timeseris_dataset_mortality.pt")
24
+ weight_model_name = os.environ.get("WEIGHT_MODEL_NAME", "ml_models/tft_poultry_weight.ckpt")
25
+ weight_data_name = os.environ.get("WEIGHT_DATA_NAME", "ts_dataset/timeseris_dataset_weight.pt")
26
+
27
+ # --- Load Mortality Model ---
28
+ logger.info("--- Loading Mortality Model ---")
29
+ mortality_model_path = os.path.join(path, mortality_model_name)
30
+ mortality_data_path = os.path.join(path, mortality_data_name)
31
+ self.mortality_model = PoultryTFTModel(
32
+ model_path=mortality_model_path,
33
+ training_data_path=mortality_data_path,
34
+ target_column="cummlative_daily_mortality_percentage_ma3"
35
+ )
36
+
37
+ # --- Load Weight Model ---
38
+ logger.info("\n--- Loading Weight Model ---")
39
+ weight_model_path = os.path.join(path, weight_model_name)
40
+ weight_data_path = os.path.join(path, weight_data_name)
41
+ self.weight_model = PoultryTFTModel(
42
+ model_path=weight_model_path,
43
+ training_data_path=weight_data_path,
44
+ target_column="weight_combined_3"
45
+ )
46
+
47
+ logger.info("\nModel handler initialized successfully with both models.")
48
+
49
+ def __call__(self, data: dict) -> list:
50
+ """
51
+ Processes an inference request. For any given input CSV, it predicts BOTH
52
+ mortality and weight and returns the combined results.
53
+
54
+ Args:
55
+ data (dict): The input data from the request. This is the raw binary CSV content.
56
+
57
+ Returns:
58
+ list: A list of dictionaries with combined prediction results for both targets.
59
+ """
60
+ # --- Extract Input Data ---
61
+ if isinstance(data, dict):
62
+ csv_bytes = data.pop("inputs", None)
63
+ else:
64
+ csv_bytes = data
65
+
66
+ if not csv_bytes:
67
+ return [{"error": "Missing CSV data in payload."}]
68
+
69
+ # If the input is a string (from JSON), encode it
70
+ if isinstance(csv_bytes, str):
71
+ csv_bytes = csv_bytes.encode('utf-8')
72
+
73
+ tmp_path = None
74
+ try:
75
+ # Write the raw bytes to a single temporary file for both models to use
76
+ with tempfile.NamedTemporaryFile(mode='wb', delete=False, suffix=".csv") as tmp:
77
+ tmp.write(csv_bytes)
78
+ tmp_path = tmp.name
79
+
80
+ logger.info(f"CSV data written to temporary file: {tmp_path}")
81
+
82
+ # --- Helper function for mapping predictions ---
83
+ def _map_predictions(test_df, predictions_df, col_name):
84
+ """Maps predictions to the last N days of each cycle based on age."""
85
+ unique_cycles = test_df['cycle_id'].unique()
86
+
87
+
88
+ pred_values_df = predictions_df
89
+ cycle_to_predictions = {}
90
+ for i, cycle_id in enumerate(unique_cycles):
91
+ if i < len(pred_values_df):
92
+ predictions = pred_values_df.iloc[i].values
93
+ cycle_to_predictions[cycle_id] = predictions
94
+
95
+ if not cycle_to_predictions:
96
+ return pd.DataFrame(columns=['cycle_id', 'age', col_name])
97
+
98
+ prediction_horizon = len(next(iter(cycle_to_predictions.values())))
99
+
100
+ last_n_days_data = []
101
+ for cycle_id in unique_cycles:
102
+ if cycle_id in cycle_to_predictions:
103
+ cycle_data = test_df[test_df['cycle_id'] == cycle_id].copy().sort_values('age')
104
+ last_n_days_data.append(cycle_data.tail(prediction_horizon))
105
+
106
+ if not last_n_days_data:
107
+ return pd.DataFrame(columns=['cycle_id', 'age', col_name])
108
+
109
+ last_n_days_df = pd.concat(last_n_days_data, ignore_index=True)
110
+
111
+ predictions_list = []
112
+ for _, row in last_n_days_df.iterrows():
113
+ cycle_id = row['cycle_id']
114
+ prediction = np.nan
115
+ if cycle_id in cycle_to_predictions:
116
+ cycle_data_full = test_df[test_df['cycle_id'] == cycle_id].sort_values('age')
117
+ ages_in_cycle = cycle_data_full['age'].values
118
+ current_age = row['age']
119
+ age_positions = np.where(ages_in_cycle == current_age)[0]
120
+
121
+ if len(age_positions) > 0:
122
+ position_from_end = len(ages_in_cycle) - age_positions[-1] - 1
123
+ horizon = len(cycle_to_predictions[cycle_id])
124
+ if position_from_end < horizon:
125
+ prediction_idx = (horizon - 1) - position_from_end
126
+ prediction = cycle_to_predictions[cycle_id][prediction_idx]
127
+ predictions_list.append(prediction)
128
+
129
+ last_n_days_df[col_name] = predictions_list
130
+ return last_n_days_df[['cycle_id', 'age', col_name]]
131
+
132
+ # --- Predict Mortality ---
133
+ logger.info("--- Predicting mortality ---")
134
+ mortality_test_df, mortality_preds_df = self.mortality_model.predict(tmp_path)
135
+ mortality_preds_mapped_df = _map_predictions(mortality_test_df, mortality_preds_df, "mortality_predicted")
136
+
137
+ # --- Predict Weight ---
138
+ logger.info("--- Predicting weight ---")
139
+ weight_test_df, weight_preds_df = self.weight_model.predict(tmp_path)
140
+ weight_preds_mapped_df = _map_predictions(mortality_test_df, weight_preds_df, "weight_predicted")
141
+
142
+ # --- Merge Predictions with Original Data ---
143
+ logger.info("--- Merging predictions with original data ---")
144
+
145
+ combined_df = mortality_test_df
146
+
147
+ predictions_df = pd.merge(
148
+ mortality_preds_mapped_df,
149
+ weight_preds_mapped_df,
150
+ on=['cycle_id', 'age'],
151
+ how='outer'
152
+ )
153
+
154
+ combined_df = pd.merge(
155
+ combined_df,
156
+ predictions_df,
157
+ on=['cycle_id', 'age'],
158
+ how='left'
159
+ )
160
+
161
+ combined_df['mortality_predicted'] = combined_df['mortality_predicted'].where(pd.notna(combined_df['mortality_predicted']), None)
162
+ combined_df['weight_predicted'] = combined_df['weight_predicted'].where(pd.notna(combined_df['weight_predicted']), None)
163
+ combined_df["age"] = combined_df["age"].astype(int)
164
+ # --- Format Output ---
165
+ logger.info("--- Formatting output ---")
166
+ output_json = {}
167
+ for org_code, org_df in combined_df.groupby('organization_code'):
168
+ output_json[org_code] = {}
169
+ for farm, farm_df in org_df.groupby('farms'):
170
+ house_data = []
171
+ # Ensure farm_df is not empty before getting max age
172
+ if not farm_df.empty:
173
+ max_age = farm_df['age'].max()
174
+ for age in range(max_age + 1):
175
+ age_data = farm_df[farm_df['age'] == age]
176
+ if not age_data.empty:
177
+ row = age_data.iloc[0]
178
+ house_data.append({
179
+ "age": int(age),
180
+ "weight_actual": row.get('weight_combined_3'),
181
+ "weight_predicted": row.get('weight_predicted'),
182
+ "mortality_actual": row.get('cummlative_daily_mortality_percentage_ma3'),
183
+ "mortality_predicted": row.get('mortality_predicted'),
184
+ })
185
+ else:
186
+ house_data.append({
187
+ "age": int(age),
188
+ "weight_actual": None,
189
+ "weight_predicted": None,
190
+ "mortality_actual": None,
191
+ "mortality_predicted": None,
192
+ })
193
+ output_json[org_code][farm] = house_data
194
+
195
+ result = output_json
196
+ logger.info("Prediction successful, returning formatted JSON output.")
197
+
198
+ except Exception as e:
199
+ logger.error(f"An error occurred during prediction: {e}", exc_info=True)
200
+ return [{"error": str(e)}]
201
+ finally:
202
+ # Clean up the temporary file
203
+ if tmp_path and os.path.exists(tmp_path):
204
+ os.remove(tmp_path)
205
+ logger.info(f"Temporary file {tmp_path} deleted.")
206
+
207
+ return result