Download src/utils/scheduler.py from SyedaArisha/predictive-maintenance-rag-system: direct link, hf CLI and curl.
- Browser
- Download file 7.83 kB
-
https://huggingface.co/SyedaArisha/predictive-maintenance-rag-system/resolve/main/src/utils/scheduler.py
- Command line
-
hf download hf://SyedaArisha/predictive-maintenance-rag-system/src/utils/scheduler.py
-
curl -L -o scheduler.py https://huggingface.co/SyedaArisha/predictive-maintenance-rag-system/resolve/main/src/utils/scheduler.py
7.83 kB
| """ | |
| Production Scheduling Adjustment Module | |
| Author: Antigravity AI | |
| Date: August 2026 | |
| This module handles closed-loop production schedule adjustments. | |
| It evaluates a production schedule against ML predictions (failure probabilities | |
| and Remaining Useful Life), identifies at-risk assets, and dynamically reschedules/reallocates | |
| jobs to healthy backup machines of the same model. | |
| """ | |
| import os | |
| import logging | |
| import pandas as pd | |
| try: | |
| from utils.project_paths import LOGS_DIR | |
| except ImportError: | |
| from project_paths import LOGS_DIR | |
| # Setup logging | |
| os.makedirs(LOGS_DIR, exist_ok=True) | |
| logging.basicConfig( | |
| level=logging.INFO, | |
| format='%(asctime)s [%(levelname)s] %(message)s', | |
| handlers=[ | |
| logging.StreamHandler(), | |
| logging.FileHandler(os.path.join(LOGS_DIR, "scheduler.log")) | |
| ] | |
| ) | |
| logger = logging.getLogger(__name__) | |
| def generate_mock_schedule(output_path: str): | |
| """Generate a realistic baseline production schedule for an FMCG manufacturing plant.""" | |
| logger.info(f"Generating mock baseline production schedule at {output_path}...") | |
| # 50 production jobs running on machines 1 to 20 | |
| jobs = [] | |
| for i in range(1, 51): | |
| mid = (i % 20) + 1 # machines 1 to 20 | |
| job_id = f"JOB_FMCG_{i:03d}" | |
| duration = float((i * 7) % 24 + 4) # 4 to 27 hours | |
| priority = "High" if (i % 3 == 0) else "Medium" if (i % 3 == 1) else "Low" | |
| jobs.append({ | |
| 'job_id': job_id, | |
| 'machine_id': mid, | |
| 'duration_hours': duration, | |
| 'priority': priority, | |
| 'status': 'Scheduled', | |
| 'notes': 'Original schedule assignment.' | |
| }) | |
| df = pd.DataFrame(jobs) | |
| os.makedirs(os.path.dirname(output_path), exist_ok=True) | |
| df.to_csv(output_path, index=False) | |
| logger.info("Mock schedule created successfully.") | |
| class ProductionScheduler: | |
| def __init__(self, schedule_path: str, machines_metadata_path: str): | |
| self.schedule_path = schedule_path | |
| self.machines_path = machines_metadata_path | |
| if not os.path.exists(self.schedule_path): | |
| generate_mock_schedule(self.schedule_path) | |
| if not os.path.exists(self.machines_path): | |
| raise FileNotFoundError(f"Missing machine metadata file: {self.machines_path}") | |
| self.schedule = pd.read_csv(self.schedule_path) | |
| self.machines = pd.read_csv(self.machines_path) | |
| def adjust_schedule(self, predictions: dict, output_adjusted_path: str, | |
| failure_threshold: float = 0.7, min_rul_hours: float = 48.0) -> pd.DataFrame: | |
| """ | |
| Adjust schedule by rerouting jobs from at-risk machines to healthy backup machines. | |
| Parameters: | |
| - predictions: dict like { machine_id: { 'failure_probability': float, 'rul': float } } | |
| - output_adjusted_path: file path to save the modified schedule. | |
| """ | |
| logger.info("Starting production schedule adjustment run...") | |
| adjusted_df = self.schedule.copy() | |
| # Build machine health lookup maps | |
| at_risk_machines = set() | |
| healthy_machines = {} # model -> list of (machine_id, failure_prob, RUL) | |
| # Initialize model groups from machines metadata | |
| model_groups = {} | |
| for _, row in self.machines.iterrows(): | |
| mid = row['machineID'] | |
| model = row['model'] | |
| model_groups[mid] = model | |
| for _, row in self.machines.iterrows(): | |
| mid = row['machineID'] | |
| model = row['model'] | |
| # Retrieve predictions (default to healthy if no predictions available) | |
| pred = predictions.get(mid, {'failure_probability': 0.0, 'rul': 500.0}) | |
| f_prob = pred.get('failure_probability', 0.0) | |
| rul = pred.get('rul', 500.0) | |
| # Determine if machine is at risk | |
| is_risk = False | |
| reasons = [] | |
| if f_prob > failure_threshold: | |
| is_risk = True | |
| reasons.append(f"high failure probability ({f_prob*100:.1f}%)") | |
| if rul is not None and rul < min_rul_hours: | |
| is_risk = True | |
| reasons.append(f"low remaining useful life ({rul:.1f} hours)") | |
| if is_risk: | |
| at_risk_machines.add(mid) | |
| logger.warning(f"Machine {mid} ({model}) is AT RISK: {', '.join(reasons)}.") | |
| else: | |
| if model not in healthy_machines: | |
| healthy_machines[model] = [] | |
| healthy_machines[model].append((mid, f_prob, rul)) | |
| # Sort healthy backup machines by lowest failure probability, then highest RUL | |
| for model in healthy_machines: | |
| healthy_machines[model].sort(key=lambda x: (x[1], -x[2] if x[2] is not None else 0)) | |
| reassigned_count = 0 | |
| # Round-robin counters per model for load balancing | |
| rr_counters = {model: 0 for model in healthy_machines} | |
| # Adjust assignments | |
| for idx, row in adjusted_df.iterrows(): | |
| mid = row['machine_id'] | |
| if mid in at_risk_machines: | |
| model = model_groups.get(mid, 'Unknown') | |
| backups = healthy_machines.get(model, []) | |
| if backups: | |
| # Round-robin: pick next healthy machine in rotation | |
| rr_idx = rr_counters.get(model, 0) % len(backups) | |
| backup_id, backup_f_prob, backup_rul = backups[rr_idx] | |
| rr_counters[model] = rr_idx + 1 | |
| adjusted_df.at[idx, 'machine_id'] = backup_id | |
| adjusted_df.at[idx, 'status'] = 'Rescheduled' | |
| adjusted_df.at[idx, 'notes'] = ( | |
| f"Reallocated from at-risk Machine {mid} to healthy backup Machine {backup_id} " | |
| f"(Model: {model}). Risk mitigation active." | |
| ) | |
| logger.info(f"Job {row['job_id']} reassigned from Machine {mid} to Machine {backup_id}.") | |
| reassigned_count += 1 | |
| else: | |
| adjusted_df.at[idx, 'status'] = 'Risk Alert' | |
| adjusted_df.at[idx, 'notes'] = ( | |
| f"Machine {mid} is at risk, but no healthy backup machines of model {model} were available!" | |
| ) | |
| logger.error(f"Failed to reassign Job {row['job_id']}: No healthy backup machines available for model {model}!") | |
| os.makedirs(os.path.dirname(output_adjusted_path), exist_ok=True) | |
| adjusted_df.to_csv(output_adjusted_path, index=False) | |
| logger.info(f"Production schedule adjustment complete. Reallocated {reassigned_count} jobs.") | |
| logger.info(f"Adjusted schedule saved to {output_adjusted_path}") | |
| return adjusted_df | |
| if __name__ == '__main__': | |
| # Test block | |
| base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')) | |
| sch_path = os.path.join(base_dir, 'data', 'raw', 'production_schedule.csv') | |
| mach_path = os.path.join(base_dir, 'data', 'raw', 'PdM_machines.csv') | |
| adj_path = os.path.join(base_dir, 'data', 'processed', 'adjusted_schedule.csv') | |
| # Mock some machine risk predictions | |
| mock_preds = { | |
| 3: {'failure_probability': 0.85, 'rul': 12.0}, # At-risk | |
| 8: {'failure_probability': 0.12, 'rul': 350.0}, # Healthy | |
| 12: {'failure_probability': 0.92, 'rul': 4.0} # At-risk | |
| } | |
| scheduler = ProductionScheduler(sch_path, mach_path) | |
| scheduler.adjust_schedule(mock_preds, adj_path) | |