GitHub Actions commited on
Commit
a1edd1c
·
1 Parent(s): 1878b82

Deploy from GitHub Actions

Browse files
.devcontainer/devcontainer.json ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "name": "Predictive Maintenance MVP",
3
+ "image": "mcr.microsoft.com/devcontainers/python:3.10-bullseye",
4
+ "customizations": {
5
+ "vscode": {
6
+ "extensions": [
7
+ "ms-python.python",
8
+ "ms-python.vscode-pylance",
9
+ "ms-toolsai.jupyter",
10
+ "vsciot-vscode.vscode-arduino"
11
+ ]
12
+ }
13
+ },
14
+ "forwardPorts": [5000, 7860],
15
+ "postCreateCommand": "pip install --user -r requirements.txt",
16
+ "remoteUser": "vscode"
17
+ }
.gitattributes DELETED
@@ -1,35 +0,0 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
LICENSE ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Creative Commons Legal Code
2
+
3
+ CC0 1.0 Universal
4
+
5
+ CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
6
+ LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
7
+ ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
8
+ INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
9
+ REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
10
+ PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
11
+ THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
12
+ HEREUNDER.
13
+
14
+ Statement of Purpose
15
+
16
+ The laws of most jurisdictions throughout the world automatically confer
17
+ exclusive Copyright and Related Rights (defined below) upon the creator
18
+ and subsequent owner(s) (each and all, an "owner") of an original work of
19
+ authorship and/or a database (each, a "Work").
20
+
21
+ Certain owners wish to permanently relinquish those rights to a Work for
22
+ the purpose of contributing to a commons of creative, cultural and
23
+ scientific works ("Commons") that the public can reliably and without fear
24
+ of later claims of infringement build upon, modify, incorporate in other
25
+ works, reuse and redistribute as freely as possible in any form whatsoever
26
+ and for any purposes, including without limitation commercial purposes.
27
+ These owners may contribute to the Commons to promote the ideal of a free
28
+ culture and the further production of creative, cultural and scientific
29
+ works, or to gain reputation or greater distribution for their Work in
30
+ part through the use and efforts of others.
31
+
32
+ For these and/or other purposes and motivations, and without any
33
+ expectation of additional consideration or compensation, the person
34
+ associating CC0 with a Work (the "Affirmer"), to the extent that he or she
35
+ is an owner of Copyright and Related Rights in the Work, voluntarily
36
+ elects to apply CC0 to the Work and publicly distribute the Work under its
37
+ terms, with knowledge of his or her Copyright and Related Rights in the
38
+ Work and the meaning and intended legal effect of CC0 on those rights.
39
+
40
+ 1. Copyright and Related Rights. A Work made available under CC0 may be
41
+ protected by copyright and related or neighboring rights ("Copyright and
42
+ Related Rights"). Copyright and Related Rights include, but are not
43
+ limited to, the following:
44
+
45
+ i. the right to reproduce, adapt, distribute, perform, display,
46
+ communicate, and translate a Work;
47
+ ii. moral rights retained by the original author(s) and/or performer(s);
48
+ iii. publicity and privacy rights pertaining to a person's image or
49
+ likeness depicted in a Work;
50
+ iv. rights protecting against unfair competition in regards to a Work,
51
+ subject to the limitations in paragraph 4(a), below;
52
+ v. rights protecting the extraction, dissemination, use and reuse of data
53
+ in a Work;
54
+ vi. database rights (such as those arising under Directive 96/9/EC of the
55
+ European Parliament and of the Council of 11 March 1996 on the legal
56
+ protection of databases, and under any national implementation
57
+ thereof, including any amended or successor version of such
58
+ directive); and
59
+ vii. other similar, equivalent or corresponding rights throughout the
60
+ world based on applicable law or treaty, and any national
61
+ implementations thereof.
62
+
63
+ 2. Waiver. To the greatest extent permitted by, but not in contravention
64
+ of, applicable law, Affirmer hereby overtly, fully, permanently,
65
+ irrevocably and unconditionally waives, abandons, and surrenders all of
66
+ Affirmer's Copyright and Related Rights and associated claims and causes
67
+ of action, whether now known or unknown (including existing as well as
68
+ future claims and causes of action), in the Work (i) in all territories
69
+ worldwide, (ii) for the maximum duration provided by applicable law or
70
+ treaty (including future time extensions), (iii) in any current or future
71
+ medium and for any number of copies, and (iv) for any purpose whatsoever,
72
+ including without limitation commercial, advertising or promotional
73
+ purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
74
+ member of the public at large and to the detriment of Affirmer's heirs and
75
+ successors, fully intending that such Waiver shall not be subject to
76
+ revocation, rescission, cancellation, termination, or any other legal or
77
+ equitable action to disrupt the quiet enjoyment of the Work by the public
78
+ as contemplated by Affirmer's express Statement of Purpose.
79
+
80
+ 3. Public License Fallback. Should any part of the Waiver for any reason
81
+ be judged legally invalid or ineffective under applicable law, then the
82
+ Waiver shall be preserved to the maximum extent permitted taking into
83
+ account Affirmer's express Statement of Purpose. In addition, to the
84
+ extent the Waiver is so judged Affirmer hereby grants to each affected
85
+ person a royalty-free, non transferable, non sublicensable, non exclusive,
86
+ irrevocable and unconditional license to exercise Affirmer's Copyright and
87
+ Related Rights in the Work (i) in all territories worldwide, (ii) for the
88
+ maximum duration provided by applicable law or treaty (including future
89
+ time extensions), (iii) in any current or future medium and for any number
90
+ of copies, and (iv) for any purpose whatsoever, including without
91
+ limitation commercial, advertising or promotional purposes (the
92
+ "License"). The License shall be deemed effective as of the date CC0 was
93
+ applied by Affirmer to the Work. Should any part of the License for any
94
+ reason be judged legally invalid or ineffective under applicable law, such
95
+ partial invalidity or ineffectiveness shall not invalidate the remainder
96
+ of the License, and in such case Affirmer hereby affirms that he or she
97
+ will not (i) exercise any of his or her remaining Copyright and Related
98
+ Rights in the Work or (ii) assert any associated claims and causes of
99
+ action with respect to the Work, in either case contrary to Affirmer's
100
+ express Statement of Purpose.
101
+
102
+ 4. Limitations and Disclaimers.
103
+
104
+ a. No trademark or patent rights held by Affirmer are waived, abandoned,
105
+ surrendered, licensed or otherwise affected by this document.
106
+ b. Affirmer offers the Work as-is and makes no representations or
107
+ warranties of any kind concerning the Work, express, implied,
108
+ statutory or otherwise, including without limitation warranties of
109
+ title, merchantability, fitness for a particular purpose, non
110
+ infringement, or the absence of latent or other defects, accuracy, or
111
+ the present or absence of errors, whether or not discoverable, all to
112
+ the greatest extent permissible under applicable law.
113
+ c. Affirmer disclaims responsibility for clearing rights of other persons
114
+ that may apply to the Work or any use thereof, including without
115
+ limitation any person's Copyright and Related Rights in the Work.
116
+ Further, Affirmer disclaims responsibility for obtaining any necessary
117
+ consents, permissions or other rights required for any use of the
118
+ Work.
119
+ d. Affirmer understands and acknowledges that Creative Commons is not a
120
+ party to this document and has no duty or obligation with respect to
121
+ this CC0 or use of the Work.
README.md CHANGED
@@ -1,14 +1,9 @@
1
- ---
2
- title: Enginevibration
3
- emoji: 😻
4
  colorFrom: red
5
- colorTo: green
6
  sdk: gradio
7
- sdk_version: 5.39.0
8
  app_file: app.py
9
  pinned: false
10
- license: cc0-1.0
11
- short_description: deploys the mvp for engine vibration for zodiac boat engine
12
- ---
13
-
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ title: Predictive Maintenance Detector
2
+ emoji: 🚨
 
3
  colorFrom: red
4
+ colorTo: yellow
5
  sdk: gradio
6
+ sdk_version: 3.41.2
7
  app_file: app.py
8
  pinned: false
9
+ ---
 
 
 
 
app.py ADDED
@@ -0,0 +1,89 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- app.py (Regression Version) ---
2
+ import gradio as gr
3
+ import numpy as np
4
+ import pandas as pd
5
+ import os
6
+ import xgboost as xgb
7
+
8
+ # --- Load Model and Threshold ---
9
+ base_dir = os.path.dirname(os.path.abspath(__file__))
10
+ model_path = os.path.join(base_dir, 'models', 'vibration_regressor.json')
11
+ threshold_path = os.path.join(base_dir, 'models', 'regression_anomaly_threshold.npy')
12
+
13
+ try:
14
+ model = xgb.XGBRegressor()
15
+ model.load_model(model_path)
16
+ anomaly_threshold = np.load(threshold_path)
17
+ print("Model and threshold loaded successfully.")
18
+ except Exception as e:
19
+ print(f"CRITICAL ERROR: Could not load files. Details: {e}")
20
+ model = None
21
+ anomaly_threshold = None
22
+
23
+ description = """
24
+ <div align="center">
25
+ <img src="https://images.pexels.com/photos/6510296/pexels-photo-6510296.jpeg" width="600px" />
26
+ </div>
27
+ **Zodiac 1.0: Regression-Based Engine Diagnostics.**
28
+ This advanced model predicts the **normal** vibration for your engine based on its current operating conditions. Enter the conditions and the actual vibration reading from your sensor to check for anomalies.
29
+ """
30
+
31
+ def predict_anomaly(rpm, temp, fuel, sea_state, actual_vibration):
32
+ """Predicts if the actual vibration is an anomaly given the conditions."""
33
+ if model is None:
34
+ return "Error: Model not loaded.", "", ""
35
+
36
+ try:
37
+ # Map sea state string to integer
38
+ sea_state_map = {"Calm": 0, "Choppy": 1, "Stormy": 2}
39
+ sea_state_int = sea_state_map.get(sea_state, 0)
40
+
41
+ # Create a DataFrame with the exact feature names the model was trained on
42
+ input_data = pd.DataFrame([{
43
+ 'rpm': rpm,
44
+ 'ambient_temp_c': temp,
45
+ 'fuel_level_percent': fuel,
46
+ 'sea_state': sea_state_int
47
+ }])
48
+
49
+ # Predict the expected normal vibration
50
+ predicted_vibration = model.predict(input_data)[0]
51
+
52
+ # Calculate the error
53
+ error = abs(actual_vibration - predicted_vibration)
54
+
55
+ status = "🚨 ANOMALY DETECTED 🚨" if error > anomaly_threshold else "✅ Machine State: Normal"
56
+
57
+ details = f"Predicted Normal Vibration: {predicted_vibration:.2f}\n"
58
+ details += f"Actual Measured Vibration: {actual_vibration:.2f}\n"
59
+ details += f"Calculated Error: {error:.4f}\n"
60
+ details += f"Anomaly Threshold: {anomaly_threshold:.4f}"
61
+
62
+ return status, details
63
+ except Exception as e:
64
+ return f"An error occurred: {e}", ""
65
+
66
+ # --- Create the Gradio Interface with sliders and dropdowns for conditions ---
67
+ demo = gr.Interface(
68
+ fn=predict_anomaly,
69
+ inputs=[
70
+ gr.Slider(800, 5000, value=2500, label="Engine RPM"),
71
+ gr.Slider(25, 40, value=32, label="Ambient Temperature (°C)"),
72
+ gr.Slider(0, 100, value=75, label="Fuel Level (%)"),
73
+ gr.Dropdown(["Calm", "Choppy", "Stormy"], value="Calm", label="Current Sea State"),
74
+ gr.Number(label="Actual Measured Vibration (from your sensor)")
75
+ ],
76
+ outputs=[
77
+ gr.Textbox(label="Prediction Status"),
78
+ gr.Textbox(label="Analysis Details")
79
+ ],
80
+ title="Zodiac 1.0 Advanced Engine Diagnostics",
81
+ description=description,
82
+ theme=gr.themes.Soft()
83
+ )
84
+
85
+ if __name__ == "__main__":
86
+ if model is not None:
87
+ demo.launch()
88
+ else:
89
+ print("Gradio app will not launch because the model could not be loaded.")
data/raw_data.csv ADDED
The diff for this file is too large to render. See raw diff
 
data_logger.py ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- data_logger.py ---
2
+ from flask import Flask, request
3
+ import pandas as pd
4
+ import os
5
+ from datetime import datetime
6
+
7
+ app = Flask(__name__)
8
+
9
+ # Define the data directory and the full path for the CSV file
10
+ DATA_DIR = 'data'
11
+ CSV_FILE = os.path.join(DATA_DIR, 'raw_data.csv')
12
+
13
+ @app.route('/data', methods=['POST'])
14
+ def receive_data():
15
+ try:
16
+ # Ensure the data directory exists
17
+ os.makedirs(DATA_DIR, exist_ok=True)
18
+
19
+ data = request.get_json()
20
+ log_data = {
21
+ 'timestamp': [datetime.now().strftime('%Y-%m-%d %H:%M:%S')],
22
+ 'ax': [data.get('ax')], 'ay': [data.get('ay')], 'az': [data.get('az')],
23
+ 'gx': [data.get('gx')], 'gy': [data.get('gy')], 'gz': [data.get('gz')],
24
+ 'temperature_c': [data.get('temperature_c')]
25
+ }
26
+ df_new = pd.DataFrame(log_data)
27
+
28
+ if not os.path.exists(CSV_FILE):
29
+ df_new.to_csv(CSV_FILE, index=False, header=True)
30
+ else:
31
+ df_new.to_csv(CSV_FILE, mode='a', index=False, header=False)
32
+ return "Data received successfully", 200
33
+ except Exception as e:
34
+ return "Error", 400
35
+
36
+ if __name__ == '__main__':
37
+ app.run(host='0.0.0.0', port=5000, debug=True)
generate_data.py ADDED
@@ -0,0 +1,52 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- generate_data.py (Regression Version) ---
2
+ import pandas as pd
3
+ import numpy as np
4
+ import os
5
+ from datetime import datetime, timedelta
6
+
7
+ def generate_data(filename="raw_data.csv", total_rows=10000):
8
+ """
9
+ Generates a dataset where vibration is a function of multiple operating conditions.
10
+ """
11
+ data_dir = 'data'
12
+ os.makedirs(data_dir, exist_ok=True)
13
+ filepath = os.path.join(data_dir, filename)
14
+
15
+ print(f"Generating regression dataset with {total_rows} rows...")
16
+
17
+ # Simulate different conditions
18
+ rpms = np.random.randint(800, 4500, total_rows)
19
+ ambient_temps = np.random.uniform(28, 35, total_rows) # Tropical temps
20
+ fuel_levels = np.random.uniform(10, 100, total_rows) # Percentage
21
+ # 0=Calm, 1=Choppy, 2=Stormy
22
+ sea_states = np.random.choice([0, 1, 2], total_rows, p=[0.6, 0.3, 0.1])
23
+
24
+ # --- The Core Logic: Vibration is a function of conditions ---
25
+ # Base vibration increases with the square of RPM
26
+ base_vibration = 9.8 + (rpms / 1000)**2
27
+
28
+ # Temperature adds a small linear effect
29
+ temp_effect = (ambient_temps - 28) * 0.05
30
+
31
+ # Sea state adds random noise
32
+ sea_effect = np.random.normal(0, sea_states * 0.2)
33
+
34
+ # A developing fault adds a slow, growing trend over time
35
+ fault_trend = np.linspace(0, 1, total_rows) * 3.0
36
+
37
+ # Combine all effects to get the final measured vibration
38
+ az_vibration = base_vibration + temp_effect + sea_effect + fault_trend
39
+
40
+ df = pd.DataFrame({
41
+ 'rpm': rpms,
42
+ 'ambient_temp_c': ambient_temps,
43
+ 'fuel_level_percent': fuel_levels,
44
+ 'sea_state': sea_states,
45
+ 'az_vibration_actual': az_vibration
46
+ })
47
+
48
+ df.to_csv(filepath, index=False)
49
+ print(f"Successfully generated and saved data to '{filepath}'")
50
+
51
+ if __name__ == "__main__":
52
+ generate_data()
hardware/esp32_sensor_code.ino ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // --- hardware/esp32_sensor_code.ino ---
2
+ #include <WiFi.h>
3
+ #include <HTTPClient.h>
4
+ #include <Wire.h>
5
+ #include <Adafruit_MPU6050.h>
6
+ #include <Adafruit_Sensor.h>
7
+ #include <OneWire.h>
8
+ #include <DallasTemperature.h>
9
+
10
+ // --- Configuration ---
11
+ const char* ssid = "YOUR_WIFI_SSID";
12
+ const char* password = "YOUR_WIFI_PASSWORD";
13
+ String serverName = "https://your-codespace-url-5000.app.github.dev/data";
14
+ #define ONEWIRE_BUS 4
15
+
16
+ // --- Global Objects ---
17
+ Adafruit_MPU6050 mpu;
18
+ OneWire oneWire(ONEWIRE_BUS);
19
+ DallasTemperature sensors(&oneWire);
20
+
21
+ void connectToWiFi() { /* ... connection logic ... */ }
22
+ void setup() { /* ... sensor and wifi init ... */ }
23
+
24
+ void loop() {
25
+ if(WiFi.status() == WL_CONNECTED) {
26
+ HTTPClient http;
27
+ sensors_event_t a, g, temp_event;
28
+ mpu.getEvent(&a, &g, &temp_event);
29
+ sensors.requestTemperatures();
30
+ float temperatureC = sensors.getTempCByIndex(0);
31
+
32
+ String jsonPayload = "{\"ax\":" + String(a.acceleration.x) + ",\"ay\":" + String(a.acceleration.y) + ",\"az\":" + String(a.acceleration.z) + ",\"gx\":" + String(g.gyro.x) + ",\"gy\":" + String(g.gyro.y) + ",\"gz\":" + String(g.gyro.z) + ",\"temperature_c\":" + String(temperatureC) + "}";
33
+
34
+ http.begin(serverName);
35
+ http.addHeader("Content-Type", "application/json");
36
+ http.POST(jsonPayload);
37
+ http.end();
38
+ }
39
+ delay(2000);
40
+ }
models/regression_anomaly_threshold.npy ADDED
Binary file (136 Bytes). View file
 
models/vibration_regressor.json ADDED
The diff for this file is too large to render. See raw diff
 
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ pandas
2
+ numpy
3
+ scikit-learn
4
+ gradio
5
+ xgboost
train_model.py ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # --- train_model.py (Regression Version) ---
2
+ import pandas as pd
3
+ import numpy as np
4
+ import os
5
+ import xgboost as xgb
6
+ from sklearn.model_selection import train_test_split
7
+
8
+ def main():
9
+ """Trains an XGBoost model to predict normal vibration."""
10
+ print("--- Starting Regression Model Training ---")
11
+
12
+ data_path = os.path.join('data', 'raw_data.csv')
13
+ if not os.path.exists(data_path):
14
+ print(f"Error: {data_path} not found. Please run generate_data.py")
15
+ return
16
+
17
+ df = pd.read_csv(data_path)
18
+ df.dropna(inplace=True)
19
+
20
+ # --- Feature & Target Definition ---
21
+ # The model will learn to predict the 'actual' vibration based on the conditions
22
+ features = ['rpm', 'ambient_temp_c', 'fuel_level_percent', 'sea_state']
23
+ target = 'az_vibration_actual'
24
+
25
+ X = df[features]
26
+ y = df[target]
27
+
28
+ # --- Model Training ---
29
+ # We will train on the 'healthy' portion of the data (first 60%)
30
+ # This teaches the model what "normal" looks like before the fault gets bad
31
+ healthy_cutoff = int(len(df) * 0.6)
32
+ X_train = X.iloc[:healthy_cutoff]
33
+ y_train = y.iloc[:healthy_cutoff]
34
+
35
+ # Initialize and train the XGBoost Regressor
36
+ model = xgb.XGBRegressor(
37
+ objective='reg:squarederror',
38
+ n_estimators=1000,
39
+ learning_rate=0.05,
40
+ max_depth=5,
41
+ subsample=0.8,
42
+ colsample_bytree=0.8,
43
+ random_state=42,
44
+ n_jobs=-1 # Use all available CPU cores
45
+ )
46
+
47
+ print("\nStarting model training...")
48
+ model.fit(X_train, y_train, verbose=False)
49
+
50
+ # --- Save Model and Calculate Anomaly Threshold ---
51
+ model_dir = 'models'
52
+ os.makedirs(model_dir, exist_ok=True)
53
+ model.save_model(os.path.join(model_dir, 'vibration_regressor.json'))
54
+ print(f"\nModel saved to: {model_dir}/vibration_regressor.json")
55
+
56
+ # The key step: Use the trained model to predict what the vibration *should* have been
57
+ # for the healthy data, then find the error.
58
+ y_pred_healthy = model.predict(X_train)
59
+ errors = np.abs(y_train - y_pred_healthy)
60
+
61
+ # The anomaly threshold is a level of error that is unusual for a healthy machine
62
+ anomaly_threshold = np.percentile(errors, 99) * 1.5 # 99th percentile, with a buffer
63
+
64
+ np.save(os.path.join(model_dir, 'regression_anomaly_threshold.npy'), anomaly_threshold)
65
+ print(f"Anomaly threshold ({anomaly_threshold:.4f}) saved to: {model_dir}/regression_anomaly_threshold.npy")
66
+
67
+ if __name__ == "__main__":
68
+ main()