Gaurav069 commited on
Commit
c8157f6
·
verified ·
1 Parent(s): c52cb8f

Upload 16 files

Browse files
Files changed (3) hide show
  1. AnomalyDetection.joblib +3 -0
  2. app.py +109 -27
  3. main.ipynb +126 -9
AnomalyDetection.joblib ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bc12a2bc1e9baf2ddea04cf92a42f1f745debf5f365456f84377dad16cc40b78
3
+ size 1131848
app.py CHANGED
@@ -4,22 +4,21 @@ import joblib
4
  import numpy as np
5
  import pandas as pd
6
 
 
 
 
7
  st.set_page_config(
8
  page_title="NASA Turbofan Playground",
9
  page_icon="🧊",
10
  initial_sidebar_state="expanded",
11
- menu_items={
12
- 'Get Help': 'https://www.extremelycoolapp.com/help',
13
- 'Report a bug': "https://www.extremelycoolapp.com/bug",
14
- 'About': "# This is a header. This is an *extremely* cool app!"
15
- }
16
  )
17
 
18
  # Set the background image
19
  background_image = """
20
  <style>
21
  [data-testid="stAppViewContainer"] > .main {
22
- background-image: url("https://w.wallhaven.cc/full/q6/wallhaven-q6vpxr.png");
23
  background-size: 100vw 100vh; # This sets the size to cover 100% of the viewport width and height
24
  background-position: center;
25
  background-repeat: no-repeat;
@@ -40,13 +39,13 @@ html_code = """
40
 
41
  <style>
42
  @keyframes rainbow-text-animation {
43
- 0% { color: red; }
44
- 16.67% { color: orange; }
45
- 33.33% { color: yellow; }
46
- 50% { color: green; }
47
- 66.67% { color: blue; }
48
- 83.33% { color: indigo; }
49
- 100% { color: violet; }
50
  }
51
 
52
  .title-container {
@@ -57,24 +56,24 @@ html_code = """
57
  }
58
 
59
  .neon-text {
60
- font-family: Times New Roman, sans-serif;
61
  font-size: 4em;
62
  margin: 0;
63
  animation: rainbow-text-animation 5s infinite linear;
64
- text-shadow: 0 0 5px rgba(255, 255, 255, 0.8),
65
- 0 0 10px rgba(255, 255, 255, 0.7),
66
- 0 0 20px rgba(255, 255, 255, 0.6),
67
- 0 0 40px rgba(255, 0, 255, 0.6),
68
- 0 0 80px rgba(255, 0, 255, 0.6),
69
- 0 0 90px rgba(255, 0, 255, 0.6),
70
- 0 0 100px rgba(255, 0, 255, 0.6),
71
- 0 0 150px rgba(255, 0, 255, 0.6);
72
  }
73
  </style>
74
  """
75
 
76
  st.markdown(html_code, unsafe_allow_html=True)
77
- st.divider()
78
 
79
  st.markdown(
80
  """
@@ -163,16 +162,59 @@ for col, limits in columns.items():
163
 
164
  # Main page
165
  # st.title("Turbofan Engine RUL Prediction")
166
- st.markdown('<p class="message-box">Turbofan Engine RUL Prediction</p>', unsafe_allow_html=True)
167
- st.markdown('<p class="success-message2">Provide the necessary inputs in the sidebar to predict the Remaining Useful Life (RUL) of the turbofan engine.</p>', unsafe_allow_html=True)
168
  # st.write("Provide the necessary inputs in the sidebar to predict the Remaining Useful Life (RUL) of the turbofan engine.")
169
 
170
  # Prepare the input for prediction
171
  input_data = [inputs[col] for col in columns]
172
  input_data = [input_data] # Assuming the model expects a 2D array
173
 
 
174
  # Predict the RUL
175
- if st.sidebar.button("Predict RUL"):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
176
  prediction = model.predict(input_data)
177
  predicted_rul = int(prediction[0])
178
  input_cycles = inputs['cycles']
@@ -219,4 +261,44 @@ if st.sidebar.button("Predict RUL"):
219
 
220
  st.markdown(f'<p class="unsuccess-message">Potential anomaly detected in sensor: {anomaly_sensor}.</p>', unsafe_allow_html=True)
221
 
222
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import numpy as np
5
  import pandas as pd
6
 
7
+ from sklearn.ensemble import IsolationForest
8
+
9
+
10
  st.set_page_config(
11
  page_title="NASA Turbofan Playground",
12
  page_icon="🧊",
13
  initial_sidebar_state="expanded",
14
+
 
 
 
 
15
  )
16
 
17
  # Set the background image
18
  background_image = """
19
  <style>
20
  [data-testid="stAppViewContainer"] > .main {
21
+ background-image: url("https://w.wallhaven.cc/full/yx/wallhaven-yxldyk.jpg");
22
  background-size: 100vw 100vh; # This sets the size to cover 100% of the viewport width and height
23
  background-position: center;
24
  background-repeat: no-repeat;
 
39
 
40
  <style>
41
  @keyframes rainbow-text-animation {
42
+ 0% { color: white; }
43
+ 16.67% { color: grey; }
44
+ 33.33% { color: grey; }
45
+ 50% { color: black; }
46
+ 66.67% { color: grey; }
47
+ 83.33% { color: white; }
48
+ 100% { color: white; }
49
  }
50
 
51
  .title-container {
 
56
  }
57
 
58
  .neon-text {
59
+ font-family: Trebuchet MS , sans-serif;
60
  font-size: 4em;
61
  margin: 0;
62
  animation: rainbow-text-animation 5s infinite linear;
63
+ text-shadow: 0 0 5px rgba(0, 0, 0, 0.8),
64
+ 0 0 10px rgba(0, 0, 0, 0.7),
65
+ 0 0 20px rgba(0, 0, 0, 0.6),
66
+ 0 0 40px rgba(0, 0, 0, 0.6),
67
+ 0 0 80px rgba(0, 0, 0, 0.6),
68
+ 0 0 90px rgba(0, 0, 0, 0.6),
69
+ 0 0 100px rgba(0, 0, 0, 0.6),
70
+ 0 0 150px rgba(0, 0, 0, 0.6);
71
  }
72
  </style>
73
  """
74
 
75
  st.markdown(html_code, unsafe_allow_html=True)
76
+
77
 
78
  st.markdown(
79
  """
 
162
 
163
  # Main page
164
  # st.title("Turbofan Engine RUL Prediction")
165
+ # st.markdown('<p class="message-box">Turbofan Engine RUL Prediction and Anomaly Detection</p>', unsafe_allow_html=True)
166
+ # st.markdown('<p class="success-message2">Provide the necessary inputs in the sidebar to predict the Remaining Useful Life (RUL) of the turbofan engine.</p>', unsafe_allow_html=True)
167
  # st.write("Provide the necessary inputs in the sidebar to predict the Remaining Useful Life (RUL) of the turbofan engine.")
168
 
169
  # Prepare the input for prediction
170
  input_data = [inputs[col] for col in columns]
171
  input_data = [input_data] # Assuming the model expects a 2D array
172
 
173
+ inp_sensor_data = input_data[0][1:]
174
  # Predict the RUL
175
+ predict_rul = st.sidebar.button("Predict RUL")
176
+ if not predict_rul:
177
+
178
+ # Title and Description
179
+
180
+ st.write("Welcome to the NASA Turbofan Playground. This tool leverages machine learning "
181
+ "to predict Remaining Useful Life (RUL) and detect anomalies in turbofan engine sensor data.")
182
+ st.divider()
183
+ # About the Application
184
+ st.header("About the Application")
185
+ st.markdown("""
186
+ This application provides two main features:
187
+ - **Remaining Useful Life (RUL) Prediction:** Predicts when a turbofan engine is likely to fail based on its current sensor readings.
188
+ - **Sensor Anomaly Detection:** Identifies anomalies in the sensor data to detect potential issues before they lead to failures.
189
+ """)
190
+ st.divider()
191
+ # Key Features
192
+ st.header("Key Features")
193
+ st.markdown("""
194
+ - **RUL Prediction:** Input current sensor readings manually or via file upload to predict RUL.
195
+ - **Anomaly Detection:** Detect anomalies in sensor readings to monitor engine health.
196
+ """)
197
+ st.divider()
198
+ # Supported Sensors
199
+ st.header("Supported Sensors")
200
+ st.markdown("""
201
+ - **Temperature Sensors:** T24, T30, T50
202
+ - **Pressure Sensors:** P30, Ps30
203
+ - **Speed Sensors:** Nf, Nc, NRf, NRc
204
+ - **Bleed Air Sensors:** BPR, htBleed
205
+ - **Flow Sensors:** W31, W32
206
+ - **Other Parameters:** phi
207
+ """)
208
+ st.divider()
209
+ # Technology Stack
210
+ st.header("Technology Stack")
211
+ st.markdown("""
212
+ - **Frontend:** Streamlit
213
+ - **Backend:** Python
214
+ - **Machine Learning Models:** Random Forest (RUL Prediction), Isolation Forest (Anomaly Detection)
215
+ """)
216
+
217
+ if predict_rul:
218
  prediction = model.predict(input_data)
219
  predicted_rul = int(prediction[0])
220
  input_cycles = inputs['cycles']
 
261
 
262
  st.markdown(f'<p class="unsuccess-message">Potential anomaly detected in sensor: {anomaly_sensor}.</p>', unsafe_allow_html=True)
263
 
264
+
265
+
266
+ # Load the pre-trained model
267
+ model_path = 'AnomalyDetection.joblib'
268
+ iso_forest = joblib.load(model_path)
269
+
270
+ # Define sensor columns based on the dataset (excluding 'RUL' since it's not used for anomaly detection)
271
+ sensor_columns = ['T24', 'T30', 'T50', 'P30', 'Nf', 'Nc', 'Ps30', 'phi', 'NRf', 'NRc', 'BPR', 'htBleed', 'W31', 'W32']
272
+
273
+
274
+
275
+ # Button to run anomaly detection with the provided sensor data
276
+ if st.sidebar.button("Detect Anomaly of Sensors"):
277
+ # Convert inputs to DataFrame
278
+ input_data = pd.DataFrame([inp_sensor_data], columns=sensor_columns)
279
+
280
+ # Predict anomaly
281
+ anomaly_score = iso_forest.decision_function(input_data)
282
+ anomaly_flag = iso_forest.predict(input_data)
283
+
284
+ # Display results
285
+ st.write(f"Anomaly Score: {anomaly_score[0]:.4f}")
286
+ if anomaly_flag[0] == -1:
287
+ st.write("Anomaly Detected!")
288
+ else:
289
+ st.write("No Anomaly Detected.")
290
+
291
+ # Identify contributing sensors
292
+ sensor_importance = {}
293
+ for sensor in sensor_columns:
294
+ input_temp = input_data.copy()
295
+ input_temp[sensor] = 0 # Remove the influence of this sensor
296
+ temp_scores = iso_forest.decision_function(input_temp)
297
+ importance = np.mean(np.abs(anomaly_score - temp_scores))
298
+ sensor_importance[sensor] = importance
299
+
300
+ sorted_sensors = sorted(sensor_importance.items(), key=lambda x: x[1], reverse=True)
301
+
302
+ st.write("Sensors contributing to anomalies in descending order of importance: ")
303
+ for sensor, importance in sorted_sensors:
304
+ st.write(f"{sensor}: {importance:.4f}")
main.ipynb CHANGED
@@ -3411,7 +3411,7 @@
3411
  },
3412
  {
3413
  "cell_type": "code",
3414
- "execution_count": 23,
3415
  "metadata": {},
3416
  "outputs": [
3417
  {
@@ -3698,7 +3698,7 @@
3698
  "[20631 rows x 16 columns]"
3699
  ]
3700
  },
3701
- "execution_count": 23,
3702
  "metadata": {},
3703
  "output_type": "execute_result"
3704
  }
@@ -3710,7 +3710,7 @@
3710
  },
3711
  {
3712
  "cell_type": "code",
3713
- "execution_count": 24,
3714
  "metadata": {},
3715
  "outputs": [
3716
  {
@@ -3997,7 +3997,7 @@
3997
  "[13096 rows x 16 columns]"
3998
  ]
3999
  },
4000
- "execution_count": 24,
4001
  "metadata": {},
4002
  "output_type": "execute_result"
4003
  }
@@ -10952,6 +10952,7 @@
10952
  }
10953
  ],
10954
  "source": [
 
10955
  "RUL = 200\n",
10956
  "current_cycle = 175\n",
10957
  "percentage_rul = current_cycle/RUL \n",
@@ -10973,7 +10974,7 @@
10973
  }
10974
  ],
10975
  "source": [
10976
- "\n",
10977
  "if percentage_rul <.4:\n",
10978
  " print(\"The Remaining Useful is more than 60%, \\nEngine Health is Excellent\")\n",
10979
  "elif percentage_rul <.6:\n",
@@ -10984,19 +10985,135 @@
10984
  " print(\"Warning the Remaining Useful is critical and less than 20%, \\nEngine maintainance Required\")"
10985
  ]
10986
  },
 
 
 
 
 
 
 
10987
  {
10988
  "cell_type": "code",
10989
- "execution_count": null,
10990
  "metadata": {},
10991
  "outputs": [],
10992
- "source": []
 
 
 
10993
  },
10994
  {
10995
  "cell_type": "code",
10996
- "execution_count": null,
10997
  "metadata": {},
10998
  "outputs": [],
10999
- "source": []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11000
  }
11001
  ],
11002
  "metadata": {
 
3411
  },
3412
  {
3413
  "cell_type": "code",
3414
+ "execution_count": 2,
3415
  "metadata": {},
3416
  "outputs": [
3417
  {
 
3698
  "[20631 rows x 16 columns]"
3699
  ]
3700
  },
3701
+ "execution_count": 2,
3702
  "metadata": {},
3703
  "output_type": "execute_result"
3704
  }
 
3710
  },
3711
  {
3712
  "cell_type": "code",
3713
+ "execution_count": 3,
3714
  "metadata": {},
3715
  "outputs": [
3716
  {
 
3997
  "[13096 rows x 16 columns]"
3998
  ]
3999
  },
4000
+ "execution_count": 3,
4001
  "metadata": {},
4002
  "output_type": "execute_result"
4003
  }
 
10952
  }
10953
  ],
10954
  "source": [
10955
+ "# example\n",
10956
  "RUL = 200\n",
10957
  "current_cycle = 175\n",
10958
  "percentage_rul = current_cycle/RUL \n",
 
10974
  }
10975
  ],
10976
  "source": [
10977
+ "# rules\n",
10978
  "if percentage_rul <.4:\n",
10979
  " print(\"The Remaining Useful is more than 60%, \\nEngine Health is Excellent\")\n",
10980
  "elif percentage_rul <.6:\n",
 
10985
  " print(\"Warning the Remaining Useful is critical and less than 20%, \\nEngine maintainance Required\")"
10986
  ]
10987
  },
10988
+ {
10989
+ "cell_type": "markdown",
10990
+ "metadata": {},
10991
+ "source": [
10992
+ "## Creating a Anomaly Detection Model for sensor anomaly"
10993
+ ]
10994
+ },
10995
  {
10996
  "cell_type": "code",
10997
+ "execution_count": 12,
10998
  "metadata": {},
10999
  "outputs": [],
11000
+ "source": [
11001
+ "sensor_columns = ['T24', 'T30', 'T50', 'P30', 'Nf', 'Nc', 'Ps30', 'phi', 'NRf',\n",
11002
+ " 'NRc', 'BPR', 'htBleed', 'W31', 'W32']"
11003
+ ]
11004
  },
11005
  {
11006
  "cell_type": "code",
11007
+ "execution_count": 13,
11008
  "metadata": {},
11009
  "outputs": [],
11010
+ "source": [
11011
+ "from sklearn.ensemble import IsolationForest"
11012
+ ]
11013
+ },
11014
+ {
11015
+ "cell_type": "code",
11016
+ "execution_count": 18,
11017
+ "metadata": {},
11018
+ "outputs": [
11019
+ {
11020
+ "name": "stdout",
11021
+ "output_type": "stream",
11022
+ "text": [
11023
+ "Sensors contributing to anomalies in descending order of importance:\n",
11024
+ "P30: 0.0486\n",
11025
+ "W31: 0.0473\n",
11026
+ "W32: 0.0464\n",
11027
+ "phi: 0.0404\n",
11028
+ "T30: 0.0383\n",
11029
+ "Nf: 0.0350\n",
11030
+ "BPR: 0.0315\n",
11031
+ "NRf: 0.0313\n",
11032
+ "T24: 0.0297\n",
11033
+ "htBleed: 0.0290\n",
11034
+ "T50: 0.0269\n",
11035
+ "Ps30: 0.0219\n",
11036
+ "NRc: 0.0207\n",
11037
+ "Nc: 0.0202\n"
11038
+ ]
11039
+ },
11040
+ {
11041
+ "data": {
11042
+ "image/png": "iVBORw0KGgoAAAANSUhEUgAAA1sAAAIjCAYAAAD1OgEdAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjkuMCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy80BEi2AAAACXBIWXMAAA9hAAAPYQGoP6dpAABURElEQVR4nO3deVxV1f7/8fdRBkUZnJi+Iqg5z1NEOSaJSl6nyjFxSjPU0uyqtxzrhmmZNpjVdagbptnXJk2vOJei5UDmkKlp5BXQnBBLFFi/P/pxvp3AAWR7QF7Px+M8Yq+99t6ffbaU79bea9uMMUYAAAAAgAJVwtkFAAAAAMCdiLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAKBA2m01Tp051dhnFSkhIiAYOHGj5cY4fPy6bzabFixfb2wYOHKiyZctafuxs/PkCUBQRtgDgNpk3b55sNptCQ0OdXUqRcPz4cQ0aNEjVq1dXqVKl5O/vr9atW2vKlCnOLs0Sbdu2lc1mk81mU4kSJeTl5aVatWrp0UcfVVxcXIEd58svvyy0oaUw1wYA+WEzxhhnFwEAxcF9992nkydP6vjx4zp8+LDuuusuZ5dUoGw2m6ZMmVIgf1k+cuSIWrRoodKlS2vw4MEKCQlRUlKSdu/erdWrV+vy5cu3XnAh07ZtWx09elQxMTGSpEuXLunIkSNasWKFfvrpJz3yyCP64IMP5Orqat8mPT1dJUqUcGi7kZEjR+rNN99UXv7zb4xRenq6XF1dVbJkSUl/jGx9/PHHSktLu+n93Eptly9flouLi1xcXArseABgNf6NBQC3wbFjx7Rt2zatWLFCw4cPV2xs7B07QlMQXn31VaWlpSkhIUHBwcEO606dOnVba7l06ZLKlClzW47l7e2t/v37O7TNmDFDo0eP1rx58xQSEqKXXnrJvs7d3d3SejIyMpSVlSU3NzeVKlXK0mPdiLOPDwD5wW2EAHAbxMbGqly5coqMjNRDDz2k2NjYHH2yn4t5+eWX9c4776h69epyd3dXixYt9O233+bov2HDBrVq1UplypSRj4+PunbtqoMHDzr0mTp1qmw2m3788Uf1799f3t7eqlSpkiZNmiRjjH755Rd17dpVXl5e8vf31yuvvOKw/ZUrVzR58mQ1a9ZM3t7eKlOmjFq1aqWNGzde93w3btwom82mTz75JMe6JUuWyGazKT4+/prbHz16VJUrV84RtCTJ19c3R9vq1avVpk0beXp6ysvLSy1atNCSJUsc+ixfvlzNmjVT6dKlVbFiRfXv31///e9/HfpkP4d09OhRde7cWZ6enurXr58kKSsrS3PmzFG9evVUqlQp+fn5afjw4Tp37pzDPnbu3KmIiAhVrFhRpUuXVtWqVTV48OBrf1k3ULJkSb322muqW7eu3njjDV24cMG+7q/PbF29elXTpk1TjRo1VKpUKVWoUEEtW7a034Y4cOBAvfnmm5Jkv2XRZrNJcvzzN2fOHPufvwMHDuT6zFa2n376SRERESpTpowCAwM1ffp0h5GpTZs2yWazadOmTQ7b/XWf16stu+2vo6Z79uxRp06d5OXlpbJly6p9+/bavn27Q5/FixfLZrNp69atGjt2rCpVqqQyZcqoe/fuOn369I0vAADcAka2AOA2iI2NVY8ePeTm5qY+ffrorbfe0rfffqsWLVrk6LtkyRJdvHhRw4cPl81m08yZM9WjRw/99NNP9tvF1q1bp06dOqlatWqaOnWqfv/9d73++uu67777tHv3boWEhDjss1evXqpTp45mzJihVatW6YUXXlD58uX19ttv6/7779dLL72k2NhYjRs3Ti1atFDr1q0lSampqfrXv/6lPn366LHHHtPFixe1YMECRURE6JtvvlHjxo1zPd+2bdsqKChIsbGx6t69e47vonr16goLC7vm9xUcHKx169Zpw4YNuv/++6/73S5evFiDBw9WvXr1NHHiRPn4+GjPnj1as2aN+vbta+8zaNAgtWjRQjExMUpJSdHcuXO1detW7dmzRz4+Pvb9ZWRkKCIiQi1bttTLL78sDw8PSdLw4cPt+xk9erSOHTumN954Q3v27NHWrVvl6uqqU6dOqUOHDqpUqZImTJggHx8fHT9+XCtWrLjuOdxIyZIl1adPH02aNElff/21IiMjc+03depUxcTEaOjQobr77ruVmpqqnTt3avfu3XrggQc0fPhwnTx5UnFxcfr3v/+d6z4WLVqky5cva9iwYXJ3d1f58uWVlZWVa9/MzEx17NhR99xzj2bOnKk1a9ZoypQpysjI0PTp0/N0jjdT25/t379frVq1kpeXl/7+97/L1dVVb7/9ttq2bavNmzfneDZy1KhRKleunKZMmaLjx49rzpw5GjlypJYtW5anOgEgTwwAwFI7d+40kkxcXJwxxpisrCxTuXJl8+STTzr0O3bsmJFkKlSoYM6ePWtv/+yzz4wk88UXX9jbGjdubHx9fc2ZM2fsbd99950pUaKEGTBggL1typQpRpIZNmyYvS0jI8NUrlzZ2Gw2M2PGDHv7uXPnTOnSpU1UVJRD3/T0dIc6z507Z/z8/MzgwYMd2iWZKVOm2JcnTpxo3N3dzfnz5+1tp06dMi4uLg79crNv3z5TunRpI8k0btzYPPnkk+bTTz81ly5dcuh3/vx54+npaUJDQ83vv//usC4rK8sYY8yVK1eMr6+vqV+/vkOflStXGklm8uTJ9raoqCgjyUyYMMFhX1999ZWRZGJjYx3a16xZ49D+ySefGEnm22+/ve755aZNmzamXr1611yfve+5c+fa24KDgx2uV6NGjUxkZOR1jxMdHW1y+89/9p8/Ly8vc+rUqVzXLVq0yN6W/V2NGjXK3paVlWUiIyONm5ubOX36tDHGmI0bNxpJZuPGjTfc57VqMybnn69u3boZNzc3c/ToUXvbyZMnjaenp2ndurW9bdGiRUaSCQ8Pt/+ZMMaYMWPGmJIlSzr8+QSAgsZthABgsdjYWPn5+aldu3aS/rgdqlevXlq6dKkyMzNz9O/Vq5fKlStnX27VqpWkP27XkqSkpCQlJCRo4MCBKl++vL1fw4YN9cADD+jLL7/Msc+hQ4fafy5ZsqSaN28uY4yGDBlib/fx8VGtWrXsx8nu6+bmJumP2+jOnj2rjIwMNW/eXLt3777ueQ8YMEDp6en6+OOP7W3Lli1TRkZGjueS/qpevXpKSEhQ//79dfz4cc2dO1fdunWTn5+f3n33XXu/uLg4Xbx4URMmTMjxTE/2LWg7d+7UqVOn9MQTTzj0iYyMVO3atbVq1aocxx8xYoTD8vLly+Xt7a0HHnhAv/76q/3TrFkzlS1b1n5bZfYI2cqVK3X16tXrnmNeZU+zfvHixWv28fHx0f79+3X48OF8H6dnz56qVKnSTfcfOXKk/WebzaaRI0fqypUrWrduXb5ruJHMzEytXbtW3bp1U7Vq1eztAQEB6tu3r77++mulpqY6bDNs2DCH2xJbtWqlzMxM/fzzz5bVCQCELQCwUGZmppYuXap27drp2LFjOnLkiI4cOaLQ0FClpKRo/fr1ObapUqWKw3J28Mp+Nij7L4e1atXKsW2dOnX066+/6tKlS9fdp7e3t0qVKqWKFSvmaP/rM0jvvfeeGjZsaH8GqFKlSlq1apXDs0O5qV27tlq0aOHwfFpsbKzuueeem5qJsWbNmvr3v/+tX3/9VXv37tWLL74oFxcXDRs2zP4X+aNHj0qS6tevf839XO/7ql27do6/bLu4uKhy5coObYcPH9aFCxfk6+urSpUqOXzS0tLsk3a0adNGPXv21LRp01SxYkV17dpVixYtUnp6+g3P90ayZ/3z9PS8Zp/p06fr/Pnzqlmzpho0aKBnnnlGe/fuzdNxqlatetN9S5Qo4RB2pD+um/THM1lWOX36tH777bdr/g5kZWXpl19+cWi/0e8VAFiBZ7YAwEIbNmxQUlKSli5dqqVLl+ZYHxsbqw4dOji0ZU+t/VfmFt7Ukds+b+Y4H3zwgQYOHKhu3brpmWeeka+vr0qWLKmYmBh70LmeAQMG6Mknn9SJEyeUnp6u7du364033shz7Q0aNFCDBg0UFhamdu3aKTY2VuHh4Xnaz81yd3dXiRKO/y8yKytLvr6+uU5sIsk+EmSz2fTxxx9r+/bt+uKLL/Sf//xHgwcP1iuvvKLt27ff0kuA9+3bJ0nXDaqtW7fW0aNH9dlnn2nt2rX617/+pVdffVXz5893GN28ntKlS+e7xtz8eTTpz3Ib1bWSFb9XAHAjhC0AsFBsbKx8fX3ts6z92YoVK/TJJ59o/vz5efoLbvYMfYcOHcqx7ocfflDFihULbKryjz/+WNWqVdOKFSsc/tJ8s9PW9+7dW2PHjtWHH36o33//Xa6ururVq1e+62nevLmkP26llKTq1atL+iOIXCuE/Pn7+utkG4cOHcp1xsO/ql69utatW6f77rvvpq7VPffco3vuuUf//Oc/tWTJEvXr109Lly696cDzV5mZmVqyZIk8PDzUsmXL6/YtX768Bg0apEGDBiktLU2tW7fW1KlT7ce+VvjJj6ysLP3000/20SxJ+vHHHyXJPklL9gjS+fPnHbbN7fa9m62tUqVK8vDwuObvQIkSJRQUFHRT+wIAK3EbIQBY5Pfff9eKFSv04IMP6qGHHsrxGTlypC5evKjPP/88T/sNCAhQ48aN9d577zn8BXbfvn1au3atOnfuXGDnkD0a8Of/+79jx47rTtv+ZxUrVlSnTp30wQcfKDY2Vh07dsxx62Juvvrqq1yfecp+Hi379rEOHTrI09NTMTExOV50nF1z8+bN5evrq/nz5zvczrd69WodPHjwmjP7/dkjjzyizMxMPf/88znWZWRk2K/DuXPncoyUZM/YmN9bCTMzMzV69GgdPHhQo0ePlpeX1zX7njlzxmG5bNmyuuuuuxyOnR3E/xp+8uvPI5XGGL3xxhtydXVV+/btJf0RdkuWLKktW7Y4bDdv3rwc+7rZ2kqWLKkOHTros88+c7hdMSUlRUuWLFHLli2v+z0BwO3CyBYAWOTzzz/XxYsX9be//S3X9ffcc48qVaqk2NjYPI/2zJo1S506dVJYWJiGDBlin/rd29s7x7uIbsWDDz6oFStWqHv37oqMjNSxY8c0f/581a1b1/4M0Y0MGDBADz30kCTlGlZy89JLL2nXrl3q0aOHGjZsKEnavXu33n//fZUvX15PPfWUJMnLy0uvvvqqhg4dqhYtWqhv374qV66cvvvuO/32229677335OrqqpdeekmDBg1SmzZt1KdPH/vU7yEhIRozZswN62nTpo2GDx+umJgYJSQkqEOHDnJ1ddXhw4e1fPlyzZ07Vw899JDee+89zZs3T927d1f16tV18eJFvfvuu/Ly8rqpEHzhwgV98MEHkqTffvtNR44c0YoVK3T06FH17t37ht9f3bp11bZtWzVr1kzly5fXzp079fHHHztMYtGsWTNJ0ujRoxUREaGSJUuqd+/eN6wtN6VKldKaNWsUFRWl0NBQrV69WqtWrdI//vEP+62V3t7eevjhh/X666/LZrOpevXqWrlyZa4vp85LbS+88ILi4uLUsmVLPfHEE3JxcdHbb7+t9PR0zZw5M1/nAwAFznkTIQLAna1Lly6mVKlSOaYr/7OBAwcaV1dX8+uvv9qnwp41a1aOfvrLtNfGGLNu3Tpz3333mdKlSxsvLy/TpUsXc+DAAYc+2VO/Z0/DnS0qKsqUKVMmx3H+Ov14VlaWefHFF01wcLBxd3c3TZo0MStXrjRRUVEmODj4hjUaY0x6eropV66c8fb2zjE9+7Vs3brVREdHm/r16xtvb2/j6upqqlSpYgYOHOgw1Xe2zz//3Nx777327+Luu+82H374oUOfZcuWmSZNmhh3d3dTvnx5069fP3PixImb+l6yvfPOO6ZZs2amdOnSxtPT0zRo0MD8/e9/NydPnjTGGLN7927Tp08fU6VKFePu7m58fX3Ngw8+aHbu3HnDc27Tpo2RZP+ULVvW1KhRw/Tv39+sXbs2123+OvX7Cy+8YO6++27j4+NjSpcubWrXrm3++c9/mitXrtj7ZGRkmFGjRplKlSoZm81mn2r9en/+rjX1e5kyZczRo0dNhw4djIeHh/Hz8zNTpkwxmZmZDtufPn3a9OzZ03h4eJhy5cqZ4cOHm3379uXY57VqMyb3P1+7d+82ERERpmzZssbDw8O0a9fObNu2zaFP9tTvf52O/1pT0gNAQbIZw5OhAADrZGRkKDAwUF26dNGCBQucXQ4AALcNz2wBACz16aef6vTp0xowYICzSwEA4LZiZAsAYIkdO3Zo7969ev7551WxYsUbvgQZAIA7DSNbAABLvPXWWxoxYoR8fX31/vvvO7scAABuO0a2AAAAAMACjGwBAAAAgAUIWwAAAABgAV5qfBOysrJ08uRJeXp6ymazObscAAAAAE5ijNHFixcVGBioEiWuP3ZF2LoJJ0+eVFBQkLPLAAAAAFBI/PLLL6pcufJ1+xC2boKnp6ekP75QLy8vJ1cDAAAAwFlSU1MVFBRkzwjXQ9i6Cdm3Dnp5eRG2AAAAANzU40VMkAEAAAAAFnBq2IqJiVGLFi3k6ekpX19fdevWTYcOHXLoc/nyZUVHR6tChQoqW7asevbsqZSUFIc+iYmJioyMlIeHh3x9ffXMM88oIyPDoc+mTZvUtGlTubu766677tLixYutPj0AAAAAxZhTw9bmzZsVHR2t7du3Ky4uTlevXlWHDh106dIle58xY8boiy++0PLly7V582adPHlSPXr0sK/PzMxUZGSkrly5om3btum9997T4sWLNXnyZHufY8eOKTIyUu3atVNCQoKeeuopDR06VP/5z39u6/kCAAAAKD5sxhjj7CKynT59Wr6+vtq8ebNat26tCxcuqFKlSlqyZIkeeughSdIPP/ygOnXqKD4+Xvfcc49Wr16tBx98UCdPnpSfn58kaf78+Ro/frxOnz4tNzc3jR8/XqtWrdK+ffvsx+rdu7fOnz+vNWvW3LCu1NRUeXt768KFCzyzBQAAABRjeckGheqZrQsXLkiSypcvL0natWuXrl69qvDwcHuf2rVrq0qVKoqPj5ckxcfHq0GDBvagJUkRERFKTU3V/v377X3+vI/sPtn7+Kv09HSlpqY6fAAAAAAgLwpN2MrKytJTTz2l++67T/Xr15ckJScny83NTT4+Pg59/fz8lJycbO/z56CVvT573fX6pKam6vfff89RS0xMjLy9ve0f3rEFAAAAIK8KTdiKjo7Wvn37tHTpUmeXookTJ+rChQv2zy+//OLskgAAAAAUMYXiPVsjR47UypUrtWXLFoe3MPv7++vKlSs6f/68w+hWSkqK/P397X2++eYbh/1lz1b45z5/ncEwJSVFXl5eKl26dI563N3d5e7uXiDnBgAAAKB4curIljFGI0eO1CeffKINGzaoatWqDuubNWsmV1dXrV+/3t526NAhJSYmKiwsTJIUFham77//XqdOnbL3iYuLk5eXl+rWrWvv8+d9ZPfJ3gcAAAAAFDSnzkb4xBNPaMmSJfrss89Uq1Yte7u3t7d9xGnEiBH68ssvtXjxYnl5eWnUqFGSpG3btkn6Y+r3xo0bKzAwUDNnzlRycrIeffRRDR06VC+++KKkP6Z+r1+/vqKjozV48GBt2LBBo0eP1qpVqxQREXHDOpmNEAAAAICUt2zg1LBls9lybV+0aJEGDhwo6Y+XGj/99NP68MMPlZ6eroiICM2bN89+i6Ak/fzzzxoxYoQ2bdqkMmXKKCoqSjNmzJCLy//dJblp0yaNGTNGBw4cUOXKlTVp0iT7MW6EsAUAAABAKkJhq6ggbAEAAACQivB7tgAAAADgTkHYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMACLjfuAgAAABRtIRNW5Xvb4zMiC7ASFCeMbAEAAACABQhbAAAAAGABbiMEAAAAriO/tyBy+yEY2QIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwgFPD1pYtW9SlSxcFBgbKZrPp008/dVhvs9ly/cyaNcveJyQkJMf6GTNmOOxn7969atWqlUqVKqWgoCDNnDnzdpweAAAAgGLMqWHr0qVLatSokd58881c1yclJTl8Fi5cKJvNpp49ezr0mz59ukO/UaNG2delpqaqQ4cOCg4O1q5duzRr1ixNnTpV77zzjqXnBgAAAKB4c3HmwTt16qROnTpdc72/v7/D8meffaZ27dqpWrVqDu2enp45+maLjY3VlStXtHDhQrm5ualevXpKSEjQ7NmzNWzYsFs/CQAAAADIRZF5ZislJUWrVq3SkCFDcqybMWOGKlSooCZNmmjWrFnKyMiwr4uPj1fr1q3l5uZmb4uIiNChQ4d07ty5XI+Vnp6u1NRUhw8AAAAA5IVTR7by4r333pOnp6d69Ojh0D569Gg1bdpU5cuX17Zt2zRx4kQlJSVp9uzZkqTk5GRVrVrVYRs/Pz/7unLlyuU4VkxMjKZNm2bRmQAAAAAoDopM2Fq4cKH69eunUqVKObSPHTvW/nPDhg3l5uam4cOHKyYmRu7u7vk61sSJEx32m5qaqqCgoPwVDgAAAKBYKhJh66uvvtKhQ4e0bNmyG/YNDQ1VRkaGjh8/rlq1asnf318pKSkOfbKXr/Wcl7u7e76DGgAAAABIRSRsLViwQM2aNVOjRo1u2DchIUElSpSQr6+vJCksLEzPPvusrl69KldXV0lSXFycatWqlesthAAAAEBBCJmwKl/bHZ8RWcCVwFmcOkFGWlqaEhISlJCQIEk6duyYEhISlJiYaO+Tmpqq5cuXa+jQoTm2j4+P15w5c/Tdd9/pp59+UmxsrMaMGaP+/fvbg1Tfvn3l5uamIUOGaP/+/Vq2bJnmzp3rcJsgAAAAABQ0p45s7dy5U+3atbMvZwegqKgoLV68WJK0dOlSGWPUp0+fHNu7u7tr6dKlmjp1qtLT01W1alWNGTPGIUh5e3tr7dq1io6OVrNmzVSxYkVNnjyZad8BAAAAWMpmjDHOLqKwS01Nlbe3ty5cuCAvLy9nlwMAAIA8yu8tfc7AbYSFW16yQZF5zxYAAAAAFCWELQAAAACwQJGYjRAAAACQitbtgAAjWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABZwatjasmWLunTposDAQNlsNn366acO6wcOHCibzebw6dixo0Ofs2fPql+/fvLy8pKPj4+GDBmitLQ0hz579+5Vq1atVKpUKQUFBWnmzJlWnxoAAACAYs6pYevSpUtq1KiR3nzzzWv26dixo5KSkuyfDz/80GF9v379tH//fsXFxWnlypXasmWLhg0bZl+fmpqqDh06KDg4WLt27dKsWbM0depUvfPOO5adFwAAAAC4OPPgnTp1UqdOna7bx93dXf7+/rmuO3jwoNasWaNvv/1WzZs3lyS9/vrr6ty5s15++WUFBgYqNjZWV65c0cKFC+Xm5qZ69eopISFBs2fPdghlAAAAAFCQCv0zW5s2bZKvr69q1aqlESNG6MyZM/Z18fHx8vHxsQctSQoPD1eJEiW0Y8cOe5/WrVvLzc3N3iciIkKHDh3SuXPncj1menq6UlNTHT4AAAAAkBeFOmx17NhR77//vtavX6+XXnpJmzdvVqdOnZSZmSlJSk5Olq+vr8M2Li4uKl++vJKTk+19/Pz8HPpkL2f3+auYmBh5e3vbP0FBQQV9agAAAADucE69jfBGevfubf+5QYMGatiwoapXr65Nmzapffv2lh134sSJGjt2rH05NTWVwAUAAAAgTwr1yNZfVatWTRUrVtSRI0ckSf7+/jp16pRDn4yMDJ09e9b+nJe/v79SUlIc+mQvX+tZMHd3d3l5eTl8AAAAACAvilTYOnHihM6cOaOAgABJUlhYmM6fP69du3bZ+2zYsEFZWVkKDQ2199myZYuuXr1q7xMXF6datWqpXLlyt/cEAAAAABQbTg1baWlpSkhIUEJCgiTp2LFjSkhIUGJiotLS0vTMM89o+/btOn78uNavX6+uXbvqrrvuUkREhCSpTp066tixox577DF988032rp1q0aOHKnevXsrMDBQktS3b1+5ublpyJAh2r9/v5YtW6a5c+c63CYIAAAAAAXNqWFr586datKkiZo0aSJJGjt2rJo0aaLJkyerZMmS2rt3r/72t7+pZs2aGjJkiJo1a6avvvpK7u7u9n3Exsaqdu3aat++vTp37qyWLVs6vEPL29tba9eu1bFjx9SsWTM9/fTTmjx5MtO+AwAAALCUzRhjnF1EYZeamipvb29duHCB57cAAACcKGTCKmeXYLnjMyKdXQKuIy/ZoEg9swUAAAAARQVhCwAAAAAsQNgCAAAAAAsQtgAAAADAAi7OLgAAAADA/8nvJCBMrFH4MLIFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFXJxdAAAAAIqfkAmrnF0CYDlGtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALuDi7AAAAABRdIRNWObsEoNBy6sjWli1b1KVLFwUGBspms+nTTz+1r7t69arGjx+vBg0aqEyZMgoMDNSAAQN08uRJh32EhITIZrM5fGbMmOHQZ+/evWrVqpVKlSqloKAgzZw583acHgAAAIBizKlh69KlS2rUqJHefPPNHOt+++037d69W5MmTdLu3bu1YsUKHTp0SH/7299y9J0+fbqSkpLsn1GjRtnXpaamqkOHDgoODtauXbs0a9YsTZ06Ve+8846l5wYAAACgeHPqbYSdOnVSp06dcl3n7e2tuLg4h7Y33nhDd999txITE1WlShV7u6enp/z9/XPdT2xsrK5cuaKFCxfKzc1N9erVU0JCgmbPnq1hw4YV3MkAAAAAwJ8UqQkyLly4IJvNJh8fH4f2GTNmqEKFCmrSpIlmzZqljIwM+7r4+Hi1bt1abm5u9raIiAgdOnRI586dy/U46enpSk1NdfgAAAAAQF4UmQkyLl++rPHjx6tPnz7y8vKyt48ePVpNmzZV+fLltW3bNk2cOFFJSUmaPXu2JCk5OVlVq1Z12Jefn599Xbly5XIcKyYmRtOmTbPwbAAAAADc6YpE2Lp69aoeeeQRGWP01ltvOawbO3as/eeGDRvKzc1Nw4cPV0xMjNzd3fN1vIkTJzrsNzU1VUFBQfkrHgAAAECxVOjDVnbQ+vnnn7VhwwaHUa3chIaGKiMjQ8ePH1etWrXk7++vlJQUhz7Zy9d6zsvd3T3fQQ0AAAAApEL+zFZ20Dp8+LDWrVunChUq3HCbhIQElShRQr6+vpKksLAwbdmyRVevXrX3iYuLU61atXK9hRAAAAAACoJTR7bS0tJ05MgR+/KxY8eUkJCg8uXLKyAgQA899JB2796tlStXKjMzU8nJyZKk8uXLy83NTfHx8dqxY4fatWsnT09PxcfHa8yYMerfv789SPXt21fTpk3TkCFDNH78eO3bt09z587Vq6++6pRzBgAAAFA82IwxxlkH37Rpk9q1a5ejPSoqSlOnTs0xsUW2jRs3qm3bttq9e7eeeOIJ/fDDD0pPT1fVqlX16KOPauzYsQ63Ae7du1fR0dH69ttvVbFiRY0aNUrjx4+/6TpTU1Pl7e2tCxcu3PA2RgAAgOIkZMIqZ5eA/+/4jEhnl1As5CUbODVsFRWELQAAgNwRtgoPwtbtkZdsUKif2QIAAACAooqwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWCBfYeunn34q6DoAAAAA4I6Sr7B11113qV27dvrggw90+fLlgq4JAAAAAIq8fIWt3bt3q2HDhho7dqz8/f01fPhwffPNNwVdGwAAAAAUWfkKW40bN9bcuXN18uRJLVy4UElJSWrZsqXq16+v2bNn6/Tp0wVdJwAAAAAUKbc0QYaLi4t69Oih5cuX66WXXtKRI0c0btw4BQUFacCAAUpKSiqoOgEAAACgSLmlsLVz50498cQTCggI0OzZszVu3DgdPXpUcXFxOnnypLp27VpQdQIAAABAkeKSn41mz56tRYsW6dChQ+rcubPef/99de7cWSVK/JHdqlatqsWLFyskJKQgawUAAACAIiNfYeutt97S4MGDNXDgQAUEBOTax9fXVwsWLLil4gAAAACgqMpX2Dp8+PAN+7i5uSkqKio/uwcAAACAIi9fz2wtWrRIy5cvz9G+fPlyvffee7dcFAAAAAAUdfkKWzExMapYsWKOdl9fX7344ou3XBQAAAAAFHX5CluJiYmqWrVqjvbg4GAlJibeclEAAAAAUNTlK2z5+vpq7969Odq/++47VahQ4ZaLAgAAAICiLl9hq0+fPho9erQ2btyozMxMZWZmasOGDXryySfVu3fvgq4RAAAAAIqcfM1G+Pzzz+v48eNq3769XFz+2EVWVpYGDBjAM1sAAAAAoHyGLTc3Ny1btkzPP/+8vvvuO5UuXVoNGjRQcHBwQdcHAAAAAEVSvsJWtpo1a6pmzZoFVQsAAAAA3DHyFbYyMzO1ePFirV+/XqdOnVJWVpbD+g0bNhRIcQAAAABQVOUrbD355JNavHixIiMjVb9+fdlstoKuCwAAAACKtHyFraVLl+qjjz5S586dC7oeAAAAALgj5Gvqdzc3N911110FXQsAAAAA3DHyFbaefvppzZ07V8aYgq4HAAAAAO4I+bqN8Ouvv9bGjRu1evVq1atXT66urg7rV6xYUSDFAQAA4PYImbDK2SUAd5x8hS0fHx917969oGsBAAAAgDtGvsLWokWLCroOAAAAALij5OuZLUnKyMjQunXr9Pbbb+vixYuSpJMnTyotLa3AigMAAACAoipfI1s///yzOnbsqMTERKWnp+uBBx6Qp6enXnrpJaWnp2v+/PkFXScAAAAAFCn5Gtl68skn1bx5c507d06lS5e2t3fv3l3r168vsOIAAAAAoKjK18jWV199pW3btsnNzc2hPSQkRP/9738LpDAAAAAAKMryNbKVlZWlzMzMHO0nTpyQp6fnLRcFAAAAAEVdvsJWhw4dNGfOHPuyzWZTWlqapkyZos6dOxdUbQAAAABQZOXrNsJXXnlFERERqlu3ri5fvqy+ffvq8OHDqlixoj788MOCrhEAAAAAipx8ha3KlSvru+++09KlS7V3716lpaVpyJAh6tevn8OEGQAAAABQXOX7PVsuLi7q37+/Zs6cqXnz5mno0KF5DlpbtmxRly5dFBgYKJvNpk8//dRhvTFGkydPVkBAgEqXLq3w8HAdPnzYoc/Zs2fVr18/eXl5ycfHR0OGDMnxrq+9e/eqVatWKlWqlIKCgjRz5sx8nTMAAAAA3Kx8jWy9//77110/YMCAm9rPpUuX1KhRIw0ePFg9evTIsX7mzJl67bXX9N5776lq1aqaNGmSIiIidODAAZUqVUqS1K9fPyUlJSkuLk5Xr17VoEGDNGzYMC1ZskSSlJqaqg4dOig8PFzz58/X999/r8GDB8vHx0fDhg3L45kDAAAAwM2xGWNMXjcqV66cw/LVq1f122+/yc3NTR4eHjp79mzeC7HZ9Mknn6hbt26S/hjVCgwM1NNPP61x48ZJki5cuCA/Pz8tXrxYvXv31sGDB1W3bl19++23at68uSRpzZo16ty5s06cOKHAwEC99dZbevbZZ5WcnGyfqn7ChAn69NNP9cMPP9xUbampqfL29taFCxfk5eWV53MDAAAo7EImrHJ2CbhFx2dEOruEYiEv2SBftxGeO3fO4ZOWlqZDhw6pZcuWBTZBxrFjx5ScnKzw8HB7m7e3t0JDQxUfHy9Jio+Pl4+Pjz1oSVJ4eLhKlCihHTt22Pu0bt3a4Z1gEREROnTokM6dO5frsdPT05WamurwAQAAAIC8yPczW39Vo0YNzZgxQ08++WSB7C85OVmS5Ofn59Du5+dnX5ecnCxfX1+H9S4uLipfvrxDn9z28edj/FVMTIy8vb3tn6CgoFs/IQAAAADFSoGFLemPoHPy5MmC3KVTTJw4URcuXLB/fvnlF2eXBAAAAKCIydcEGZ9//rnDsjFGSUlJeuONN3TfffcVSGH+/v6SpJSUFAUEBNjbU1JS1LhxY3ufU6dOOWyXkZGhs2fP2rf39/dXSkqKQ5/s5ew+f+Xu7i53d/cCOQ8AAAAAxVO+wlb2JBbZbDabKlWqpPvvv1+vvPJKQdSlqlWryt/fX+vXr7eHq9TUVO3YsUMjRoyQJIWFhen8+fPatWuXmjVrJknasGGDsrKyFBoaau/z7LPP6urVq3J1dZUkxcXFqVatWjkm+gAAAACAgpKvsJWVlVUgB09LS9ORI0fsy8eOHVNCQoLKly+vKlWq6KmnntILL7ygGjVq2Kd+DwwMtIe9OnXqqGPHjnrsscc0f/58Xb16VSNHjlTv3r0VGBgoSerbt6+mTZumIUOGaPz48dq3b5/mzp2rV199tUDOAQAAAAByk6+wVVB27typdu3a2ZfHjh0rSYqKitLixYv197//XZcuXdKwYcN0/vx5tWzZUmvWrLG/Y0uSYmNjNXLkSLVv314lSpRQz5499dprr9nXe3t7a+3atYqOjlazZs1UsWJFTZ48mXdsAQAAALBUvt6zlR2Kbsbs2bPzuvtCh/dsAQCAOx3v2Sr6eM/W7ZGXbJCvka09e/Zoz549unr1qmrVqiVJ+vHHH1WyZEk1bdrU3s9ms+Vn9wAAAABQ5OUrbHXp0kWenp5677337JNMnDt3ToMGDVKrVq309NNPF2iRAAAAAFDU5Os9W6+88opiYmIcZvMrV66cXnjhhQKbjRAAAAAAirJ8ha3U1FSdPn06R/vp06d18eLFWy4KAAAAAIq6fIWt7t27a9CgQVqxYoVOnDihEydO6H//9381ZMgQ9ejRo6BrBAAAAIAiJ1/PbM2fP1/jxo1T3759dfXq1T925OKiIUOGaNasWQVaIAAAAAAURfkKWx4eHpo3b55mzZqlo0ePSpKqV6+uMmXKFGhxAAAAAFBU5es2wmxJSUlKSkpSjRo1VKZMGeXjlV0AAAAAcEfKV9g6c+aM2rdvr5o1a6pz585KSkqSJA0ZMoRp3wEAAABA+byNcMyYMXJ1dVViYqLq1Kljb+/Vq5fGjh3L9O8AAADAbRYyYVW+tjs+I7KAK0G2fIWttWvX6j//+Y8qV67s0F6jRg39/PPPBVIYAAAAABRl+bqN8NKlS/Lw8MjRfvbsWbm7u99yUQAAAABQ1OUrbLVq1Urvv/++fdlmsykrK0szZ85Uu3btCqw4AAAAACiq8nUb4cyZM9W+fXvt3LlTV65c0d///nft379fZ8+e1datWwu6RgAAAAAocvI1slW/fn39+OOPatmypbp27apLly6pR48e2rNnj6pXr17QNQIAAABAkZPnka2rV6+qY8eOmj9/vp599lkragIAAACAIi/PI1uurq7au3evFbUAAAAAwB0jX7cR9u/fXwsWLCjoWgAAAADgjpGvCTIyMjK0cOFCrVu3Ts2aNVOZMmUc1s+ePbtAigMAAACAoipPYeunn35SSEiI9u3bp6ZNm0qSfvzxR4c+Nput4KoDAABAnoRMWOXsEgD8f3kKWzVq1FBSUpI2btwoSerVq5dee+01+fn5WVIcAAAAABRVeXpmyxjjsLx69WpdunSpQAsCAAAAgDtBvibIyPbX8AUAAAAA+EOewpbNZsvxTBbPaAEAAABATnl6ZssYo4EDB8rd3V2SdPnyZT3++OM5ZiNcsWJFwVUIAAAAAEVQnsJWVFSUw3L//v0LtBgAAAAAuFPkKWwtWrTIqjoAAAAA4I5ySxNkAAAAAAByR9gCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKFPmyFhITIZrPl+ERHR0uS2rZtm2Pd448/7rCPxMRERUZGysPDQ76+vnrmmWeUkZHhjNMBAAAAUEy4OLuAG/n222+VmZlpX963b58eeOABPfzww/a2xx57TNOnT7cve3h42H/OzMxUZGSk/P39tW3bNiUlJWnAgAFydXXViy++eHtOAgAAAECxU+jDVqVKlRyWZ8yYoerVq6tNmzb2Ng8PD/n7++e6/dq1a3XgwAGtW7dOfn5+aty4sZ5//nmNHz9eU6dOlZubm6X1AwAAACieCn3Y+rMrV67ogw8+0NixY2Wz2eztsbGx+uCDD+Tv768uXbpo0qRJ9tGt+Ph4NWjQQH5+fvb+ERERGjFihPbv368mTZrkOE56errS09Pty6mpqRaeFQAAQE4hE1Y5uwQAt6hIha1PP/1U58+f18CBA+1tffv2VXBwsAIDA7V3716NHz9ehw4d0ooVKyRJycnJDkFLkn05OTk51+PExMRo2rRp1pwEAAAAgGKhSIWtBQsWqFOnTgoMDLS3DRs2zP5zgwYNFBAQoPbt2+vo0aOqXr16vo4zceJEjR071r6cmpqqoKCg/BcOAAAAoNgpMmHr559/1rp16+wjVtcSGhoqSTpy5IiqV68uf39/ffPNNw59UlJSJOmaz3m5u7vL3d29AKoGAAAAUFwV+qnfsy1atEi+vr6KjIy8br+EhARJUkBAgCQpLCxM33//vU6dOmXvExcXJy8vL9WtW9eyegEAAAAUb0ViZCsrK0uLFi1SVFSUXFz+r+SjR49qyZIl6ty5sypUqKC9e/dqzJgxat26tRo2bChJ6tChg+rWratHH31UM2fOVHJysp577jlFR0czegUAAADAMkUibK1bt06JiYkaPHiwQ7ubm5vWrVunOXPm6NKlSwoKClLPnj313HPP2fuULFlSK1eu1IgRIxQWFqYyZcooKirK4b1cAAAAAFDQikTY6tChg4wxOdqDgoK0efPmG24fHBysL7/80orSAAAAACBXReaZLQAAAAAoSghbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAVcnF0AAADAnSxkwipnlwDASRjZAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAswEuNAQAAbgIvJwaQV4xsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGCBQh22pk6dKpvN5vCpXbu2ff3ly5cVHR2tChUqqGzZsurZs6dSUlIc9pGYmKjIyEh5eHjI19dXzzzzjDIyMm73qQAAAAAoZgr9e7bq1aundevW2ZddXP6v5DFjxmjVqlVavny5vL29NXLkSPXo0UNbt26VJGVmZioyMlL+/v7atm2bkpKSNGDAALm6uurFF1+87ecCAAAAoPgo9GHLxcVF/v7+OdovXLigBQsWaMmSJbr//vslSYsWLVKdOnW0fft23XPPPVq7dq0OHDigdevWyc/PT40bN9bzzz+v8ePHa+rUqXJzc7vdpwMAAACgmCjUtxFK0uHDhxUYGKhq1aqpX79+SkxMlCTt2rVLV69eVXh4uL1v7dq1VaVKFcXHx0uS4uPj1aBBA/n5+dn7REREKDU1Vfv377/mMdPT05WamurwAQAAAIC8KNRhKzQ0VIsXL9aaNWv01ltv6dixY2rVqpUuXryo5ORkubm5ycfHx2EbPz8/JScnS5KSk5Mdglb2+ux11xITEyNvb2/7JygoqGBPDAAAAMAdr1DfRtipUyf7zw0bNlRoaKiCg4P10UcfqXTp0pYdd+LEiRo7dqx9OTU1lcAFAAAAIE8K9cjWX/n4+KhmzZo6cuSI/P39deXKFZ0/f96hT0pKiv0ZL39//xyzE2Yv5/YcWDZ3d3d5eXk5fAAAAAAgLwr1yNZfpaWl6ejRo3r00UfVrFkzubq6av369erZs6ck6dChQ0pMTFRYWJgkKSwsTP/85z916tQp+fr6SpLi4uLk5eWlunXrOu08AACA84RMWOXsEgAUE4U6bI0bN05dunRRcHCwTp48qSlTpqhkyZLq06ePvL29NWTIEI0dO1bly5eXl5eXRo0apbCwMN1zzz2SpA4dOqhu3bp69NFHNXPmTCUnJ+u5555TdHS03N3dnXx2AAAAAO5khTpsnThxQn369NGZM2dUqVIltWzZUtu3b1elSpUkSa+++qpKlCihnj17Kj09XREREZo3b559+5IlS2rlypUaMWKEwsLCVKZMGUVFRWn69OnOOiUAAAAAxYTNGGOcXURhl5qaKm9vb124cIHntwAAKOK4jRBwdHxGpLNLKFLykg2K1AQZAAAAAFBUELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAsQNgCAAAAAAsQtgAAAADAAoQtAAAAALCAi7MLAAAAAOA8IRNW5Wu74zMiC7iSOw8jWwAAAABgAcIWAAAAAFiAsAUAAAAAFuCZLQAAUCTl9zkTALhdGNkCAAAAAAsQtgAAAADAAoQtAAAAALAAYQsAAAAALEDYAgAAAAALELYAAAAAwAKELQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMAChC0AAAAAsABhCwAAAAAs4OLsAgAAQPEVMmGVs0sAAMswsgUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWcHF2AQAAoOgLmbDK2SUAQKHDyBYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCnXYiomJUYsWLeTp6SlfX19169ZNhw4dcujTtm1b2Ww2h8/jjz/u0CcxMVGRkZHy8PCQr6+vnnnmGWVkZNzOUwEAAABQzBTqlxpv3rxZ0dHRatGihTIyMvSPf/xDHTp00IEDB1SmTBl7v8cee0zTp0+3L3t4eNh/zszMVGRkpPz9/bVt2zYlJSVpwIABcnV11YsvvnhbzwcAAABA8VGow9aaNWsclhcvXixfX1/t2rVLrVu3trd7eHjI398/132sXbtWBw4c0Lp16+Tn56fGjRvr+eef1/jx4zV16lS5ublZeg4AAAAAiqdCHbb+6sKFC5Kk8uXLO7THxsbqgw8+kL+/v7p06aJJkybZR7fi4+PVoEED+fn52ftHRERoxIgR2r9/v5o0aZLjOOnp6UpPT7cvp6amWnE6AAAUOiETVjm7BAC4YxSZsJWVlaWnnnpK9913n+rXr29v79u3r4KDgxUYGKi9e/dq/PjxOnTokFasWCFJSk5OdghakuzLycnJuR4rJiZG06ZNs+hMAAAAABQHRSZsRUdHa9++ffr6668d2ocNG2b/uUGDBgoICFD79u119OhRVa9ePV/HmjhxosaOHWtfTk1NVVBQUP4KBwAAAFAsFerZCLONHDlSK1eu1MaNG1W5cuXr9g0NDZUkHTlyRJLk7++vlJQUhz7Zy9d6zsvd3V1eXl4OHwAAAADIi0IdtowxGjlypD755BNt2LBBVatWveE2CQkJkqSAgABJUlhYmL7//nudOnXK3icuLk5eXl6qW7euJXUDAAAAQKG+jTA6OlpLlizRZ599Jk9PT/szVt7e3ipdurSOHj2qJUuWqHPnzqpQoYL27t2rMWPGqHXr1mrYsKEkqUOHDqpbt64effRRzZw5U8nJyXruuecUHR0td3d3Z54eAAAAgDuYzRhjnF3EtdhstlzbFy1apIEDB+qXX35R//79tW/fPl26dElBQUHq3r27nnvuOYdb/37++WeNGDFCmzZtUpkyZRQVFaUZM2bIxeXmsmZqaqq8vb114cIFbikEABQJzCoIwGrHZ0Q6uwSnyEs2KNQjWzfKgUFBQdq8efMN9xMcHKwvv/yyoMoCAAAAgBsq1M9sAQAAAEBRRdgCAAAAAAsQtgAAAADAAoX6mS0AAO4U+Z2worg+gA4AdwLCFgAAhRizCgJA0cVthAAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAAAAAYAHCFgAAAABYgLAFAAAAABYgbAEAAACABXipMQAAecBLhgEAN4uRLQAAAACwAGELAAAAACxA2AIAAAAACxC2AAAAAMACTJABACiWmOgCAGA1RrYAAAAAwAKELQAAAACwAGELAAAAACzAM1sAgEIhv89QHZ8RWcCVAABQMAhbAIAijYkuAACFFbcRAgAAAIAFCFsAAAAAYAHCFgAAAABYgGe2AAAFimeoAAD4A2ELAJADgQkAgFtH2AKAOxihCQAA5yFsAcBtxLukAAAoPghbAFAEMEIFAEDRQ9gCgHwg/AAAijvu1rgxwhaAYo3QBAAArMJ7tgAAAADAAoQtAAAAALAAYQsAAAAALMAzWwAKFR62BQAAdwrCFgBL3O6JJ5joAgAAFDbcRggAAAAAFmBkC8B1MWIEAACQP4xsAQAAAIAFGNkCiglGqAAAAG4vRrYAAAAAwAKELQAAAACwALcRAk7ALX0AAAB3vmIVtt58803NmjVLycnJatSokV5//XXdfffdzi4LRRihCQAAANdSbG4jXLZsmcaOHaspU6Zo9+7datSokSIiInTq1ClnlwYAAADgDmQzxhhnF3E7hIaGqkWLFnrjjTckSVlZWQoKCtKoUaM0YcKE626bmpoqb29vXbhwQV5eXrejXOQTI00AAACF2/EZkc4u4ZbkJRsUi9sIr1y5ol27dmnixIn2thIlSig8PFzx8fE5+qenpys9Pd2+fOHCBUl/fLGFRf0p/3F2CQAAAECeVRmzPF/b7ZsWUcCV5E92JriZMatiEbZ+/fVXZWZmys/Pz6Hdz89PP/zwQ47+MTExmjZtWo72oKAgy2oEAAAAcG3ec5xdgaOLFy/K29v7un2KRdjKq4kTJ2rs2LH25aysLJ09e1YVKlSQzWZzYmXXl5qaqqCgIP3yyy/c7lhIcY0KP65R4cc1Kvy4RoUf16jw4xoVXsYYXbx4UYGBgTfsWyzCVsWKFVWyZEmlpKQ4tKekpMjf3z9Hf3d3d7m7uzu0+fj4WFligfLy8uKXspDjGhV+XKPCj2tU+HGNCj+uUeHHNSqcbjSila1YzEbo5uamZs2aaf369fa2rKwsrV+/XmFhYU6sDAAAAMCdqliMbEnS2LFjFRUVpebNm+vuu+/WnDlzdOnSJQ0aNMjZpQEAAAC4AxWbsNWrVy+dPn1akydPVnJysho3bqw1a9bkmDSjKHN3d9eUKVNy3AKJwoNrVPhxjQo/rlHhxzUq/LhGhR/X6M5QbN6zBQAAAAC3U7F4ZgsAAAAAbjfCFgAAAABYgLAFAAAAABYgbAEAAACABQhbRcjZs2fVr18/eXl5ycfHR0OGDFFaWtp1t3nnnXfUtm1beXl5yWaz6fz58zn6hISEyGazOXxmzJhh0Vnc2ay6RvnZL3KXn+/y8uXLio6OVoUKFVS2bFn17Nkzx0vS//o7ZLPZtHTpUitP5Y7y5ptvKiQkRKVKlVJoaKi++eab6/Zfvny5ateurVKlSqlBgwb68ssvHdYbYzR58mQFBASodOnSCg8P1+HDh608hTteQV+jgQMH5vid6dixo5WncMfLyzXav3+/evbsaf87wJw5c255n7ixgr5GU6dOzfF7VLt2bQvPAHlF2CpC+vXrp/379ysuLk4rV67Uli1bNGzYsOtu89tvv6ljx476xz/+cd1+06dPV1JSkv0zatSogiy92LDqGuVnv8hdfr7LMWPG6IsvvtDy5cu1efNmnTx5Uj169MjRb9GiRQ6/R926dbPoLO4sy5Yt09ixYzVlyhTt3r1bjRo1UkREhE6dOpVr/23btqlPnz4aMmSI9uzZo27duqlbt27at2+fvc/MmTP12muvaf78+dqxY4fKlCmjiIgIXb58+Xad1h3FimskSR07dnT4nfnwww9vx+nckfJ6jX777TdVq1ZNM2bMkL+/f4HsE9dnxTWSpHr16jn8Hn399ddWnQLyw6BIOHDggJFkvv32W3vb6tWrjc1mM//9739vuP3GjRuNJHPu3Lkc64KDg82rr75agNUWT1Zdo1vdL/5Pfr7L8+fPG1dXV7N8+XJ728GDB40kEx8fb2+TZD755BPLar+T3X333SY6Otq+nJmZaQIDA01MTEyu/R955BETGRnp0BYaGmqGDx9ujDEmKyvL+Pv7m1mzZtnXnz9/3ri7u5sPP/zQgjO48xX0NTLGmKioKNO1a1dL6i2O8nqN/uxafw+4lX0iJyuu0ZQpU0yjRo0KsEoUNEa2ioj4+Hj5+PioefPm9rbw8HCVKFFCO3bsuOX9z5gxQxUqVFCTJk00a9YsZWRk3PI+ixurrpHV1744yc93uWvXLl29elXh4eH2ttq1a6tKlSqKj4936BsdHa2KFSvq7rvv1sKFC2V4jeENXblyRbt27XL4fkuUKKHw8PAc32+2+Ph4h/6SFBERYe9/7NgxJScnO/Tx9vZWaGjoNfeJa7PiGmXbtGmTfH19VatWLY0YMUJnzpwp+BMoBvJzjZyxz+LMyu/z8OHDCgwMVLVq1dSvXz8lJibearkoQC7OLgA3Jzk5Wb6+vg5tLi4uKl++vJKTk29p36NHj1bTpk1Vvnx5bdu2TRMnTlRSUpJmz559S/stbqy6RlZe++ImP99lcnKy3Nzc5OPj49Du5+fnsM306dN1//33y8PDQ2vXrtUTTzyhtLQ0jR49usDP407y66+/KjMzU35+fg7tfn5++uGHH3LdJjk5Odf+2dcj+5/X64ObZ8U1kv64hbBHjx6qWrWqjh49qn/84x/q1KmT4uPjVbJkyYI/kTtYfq6RM/ZZnFn1fYaGhmrx4sWqVauWkpKSNG3aNLVq1Ur79u2Tp6fnrZaNAkDYcrIJEybopZdeum6fgwcPWlrD2LFj7T83bNhQbm5uGj58uGJiYuTu7m7psYuCwnCNcH2F4RpNmjTJ/nOTJk106dIlzZo1i7AFXEPv3r3tPzdo0EANGzZU9erVtWnTJrVv396JlQFFR6dOnew/N2zYUKGhoQoODtZHH32kIUOGOLEyZCNsOdnTTz+tgQMHXrdPtWrV5O/vn+MByoyMDJ09e/a6D03mR2hoqDIyMnT8+HHVqlWrQPddFDn7Gt3Oa19UWXmN/P39deXKFZ0/f95hdCslJeW6339oaKief/55paen8z8trqNixYoqWbJkjtkdr/f9+vv7X7d/9j9TUlIUEBDg0Kdx48YFWH3xYMU1yk21atVUsWJFHTlyhLCVR/m5Rs7YZ3F2u75PHx8f1axZU0eOHCmwfeLW8MyWk1WqVEm1a9e+7sfNzU1hYWE6f/68du3aZd92w4YNysrKUmhoaIHWlJCQoBIlSuS43aq4cvY1up3Xvqiy8ho1a9ZMrq6uWr9+vb3t0KFDSkxMVFhY2DVrSkhIULly5QhaN+Dm5qZmzZo5fL9ZWVlav379Nb/fsLAwh/6SFBcXZ+9ftWpV+fv7O/RJTU3Vjh07rnvNkDsrrlFuTpw4oTNnzjgEZNyc/FwjZ+yzOLtd32daWpqOHj3K71Fh4uwZOnDzOnbsaJo0aWJ27Nhhvv76a1OjRg3Tp08f+/oTJ06YWrVqmR07dtjbkpKSzJ49e8y7775rJJktW7aYPXv2mDNnzhhjjNm2bZt59dVXTUJCgjl69Kj54IMPTKVKlcyAAQNu+/ndCay4RjezX9y8/Fyjxx9/3FSpUsVs2LDB7Ny504SFhZmwsDD7+s8//9y8++675vvvvzeHDx828+bNMx4eHmby5Mm39dyKqqVLlxp3d3ezePFic+DAATNs2DDj4+NjkpOTjTHGPProo2bChAn2/lu3bjUuLi7m5ZdfNgcPHjRTpkwxrq6u5vvvv7f3mTFjhvHx8TGfffaZ2bt3r+nataupWrWq+f3332/7+d0JCvoaXbx40YwbN87Ex8ebY8eOmXXr1pmmTZuaGjVqmMuXLzvlHIu6vF6j9PR0s2fPHrNnzx4TEBBgxo0bZ/bs2WMOHz580/tE3lhxjZ5++mmzadMmc+zYMbN161YTHh5uKlasaE6dOnXbzw+5I2wVIWfOnDF9+vQxZcuWNV5eXmbQoEHm4sWL9vXHjh0zkszGjRvtbVOmTDGScnwWLVpkjDFm165dJjQ01Hh7e5tSpUqZOnXqmBdffJH/2OWTFdfoZvaLm5efa/T777+bJ554wpQrV854eHiY7t27m6SkJPv61atXm8aNG5uyZcuaMmXKmEaNGpn58+ebzMzM23lqRdrrr79uqlSpYtzc3Mzdd99ttm/fbl/Xpk0bExUV5dD/o48+MjVr1jRubm6mXr16ZtWqVQ7rs7KyzKRJk4yfn59xd3c37du3N4cOHbodp3LHKshr9Ntvv5kOHTqYSpUqGVdXVxMcHGwee+wx/hJ/i/JyjbL/XffXT5s2bW56n8i7gr5GvXr1MgEBAcbNzc38z//8j+nVq5c5cuTIbTwj3IjNGOYmBgAAAICCxjNbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABgAcIWAAAAAFiAsAUAAAAAFiBsAQAAAIAFCFsAANyEkJAQzZkzx9llAACKEMIWAOC2iY+PV8mSJRUZGensUpzi3XffVaNGjVS2bFn5+PioSZMmiomJcXZZAACLuDi7AABA8bFgwQKNGjVKCxYs0MmTJxUYGOjskm6bhQsX6qmnntJrr72mNm3aKD09XXv37tW+ffssO+aVK1fk5uZm2f4BANfHyBYA4LZIS0vTsmXLNGLECEVGRmrx4sUO6zdt2iSbzab169erefPm8vDw0L333qtDhw459HvrrbdUvXp1ubm5qVatWvr3v//tsN5ms+ntt9/Wgw8+KA8PD9WpU0fx8fE6cuSI2rZtqzJlyujee+/V0aNH7dscPXpUXbt2lZ+fn8qWLasWLVpo3bp11zyXwYMH68EHH3Rou3r1qnx9fbVgwYJct/n888/1yCOPaMiQIbrrrrtUr1499enTR//85z8d+i1cuFD16tWTu7u7AgICNHLkSPu6xMREde3aVWXLlpWXl5ceeeQRpaSk2NdPnTpVjRs31r/+9S9VrVpVpUqVkiSdP39eQ4cOVaVKleTl5aX7779f33333TXPDwBQMAhbAIDb4qOPPlLt2rVVq1Yt9e/fXwsXLpQxJke/Z599Vq+88op27twpFxcXDR482L7uk08+0ZNPPqmnn35a+/bt0/DhwzVo0CBt3LjRYR/PP/+8BgwYoISEBNWuXVt9+/bV8OHDNXHiRO3cuVPGGIcQk5aWps6dO2v9+vXas2ePOnbsqC5duigxMTHXcxk6dKjWrFmjpKQke9vKlSv122+/qVevXrlu4+/vr+3bt+vnn3++5nf01ltvKTo6WsOGDdP333+vzz//XHfddZckKSsrS127dtXZs2e1efNmxcXF6aeffspxvCNHjuh///d/tWLFCiUkJEiSHn74YZ06dUqrV6/Wrl271LRpU7Vv315nz569Zi0AgAJgAAC4De69914zZ84cY4wxV69eNRUrVjQbN260r9+4caORZNatW2dvW7VqlZFkfv/9d/s+HnvsMYf9Pvzww6Zz5872ZUnmueeesy/Hx8cbSWbBggX2tg8//NCUKlXquvXWq1fPvP766/bl4OBg8+qrr9qX69ata1566SX7cpcuXczAgQOvub+TJ0+ae+65x0gyNWvWNFFRUWbZsmUmMzPT3icwMNA8++yzuW6/du1aU7JkSZOYmGhv279/v5FkvvnmG2OMMVOmTDGurq7m1KlT9j5fffWV8fLyMpcvX3bYX/Xq1c3bb7993e8AAHBrGNkCAFju0KFD+uabb9SnTx9JkouLi3r16pXrLXcNGza0/xwQECBJOnXqlCTp4MGDuu+++xz633fffTp48OA19+Hn5ydJatCggUPb5cuXlZqaKumPka1x48apTp068vHxUdmyZXXw4MFrjmxJf4xuLVq0SJKUkpKi1atXO4zC/VVAQIDi4+P1/fff68knn1RGRoaioqLUsWNHZWVl6dSpUzp58qTat2+f6/YHDx5UUFCQgoKC7G1169aVj4+Pw/kHBwerUqVK9uXvvvtOaWlpqlChgsqWLWv/HDt2zOFWSgBAwWOCDACA5RYsWKCMjAyHCTGMMXJ3d9cbb7whb29ve7urq6v9Z5vNJumPW+jyIrd9XG+/48aNU1xcnF5++WXdddddKl26tB566CFduXLlmscYMGCAJkyYoPj4eG3btk1Vq1ZVq1atblhb/fr1Vb9+fT3xxBN6/PHH1apVK23evFnNmzfP0zleS5kyZRyW09LSFBAQoE2bNuXo6+PjUyDHBADkjrAFALBURkaG3n//fb3yyivq0KGDw7pu3brpww8/1OOPP35T+6pTp462bt2qqKgoe9vWrVtVt27dW6px69atGjhwoLp37y7pj4By/Pjx625ToUIFdevWTYsWLVJ8fLwGDRqU5+Nm133p0iV5enoqJCRE69evV7t27XL0rVOnjn755Rf98ssv9tGtAwcO6Pz589c9/6ZNmyo5OVkuLi4KCQnJc40AgPwjbAEALLVy5UqdO3dOQ4YMcRjBkqSePXtqwYIFNx22nnnmGT3yyCNq0qSJwsPD9cUXX2jFihXXnTnwZtSoUUMrVqxQly5dZLPZNGnSpJsaTRs6dKgefPBBZWZmOgTA3IwYMUKBgYG6//77VblyZSUlJemFF15QpUqVFBYWJumP2QQff/xx+fr6qlOnTrp48aK2bt2qUaNGKTw8XA0aNFC/fv00Z84cZWRk6IknnlCbNm2uOyoWHh6usLAwdevWTTNnzlTNmjV18uRJrVq1St27dy+wETUAQE48swUAsNSCBQsUHh6eI2hJf4StnTt3au/evTe1r27dumnu3Ll6+eWXVa9ePb399ttatGiR2rZte0s1zp49W+XKldO9996rLl26KCIiQk2bNr3hduHh4QoICFBERMQN3xkWHh6u7du36+GHH1bNmjXVs2dPlSpVSuvXr1eFChUkSVFRUZozZ47mzZunevXq6cEHH9Thw4cl/XHr42effaZy5cqpdevWCg8PV7Vq1bRs2bLrHtdms+nLL79U69atNWjQINWsWVO9e/fWzz//bH+eDQBgDZsxucy7CwAAbigtLU3/8z//o0WLFqlHjx7OLgcAUMhwGyEAAHmUlZWlX3/9Va+88op8fHz0t7/9zdklAQAKIcIWAAB5lJiYqKpVq6py5cpavHixXFz4zykAICduIwQAAAAACzBBBgAAAABYgLAFAAAAABYgbAEAAACABQhbAAAAAGABwhYAAAAAWICwBQAAAAAWIGwBAAAAgAUIWwAAAABggf8HA5MClbVf2rkAAAAASUVORK5CYII=",
11043
+ "text/plain": [
11044
+ "<Figure size 1000x600 with 1 Axes>"
11045
+ ]
11046
+ },
11047
+ "metadata": {},
11048
+ "output_type": "display_data"
11049
+ }
11050
+ ],
11051
+ "source": [
11052
+ "iso_forest = IsolationForest(contamination=0.05, random_state=42) # Set contamination rate as needed\n",
11053
+ "iso_forest.fit(df[sensor_columns])\n",
11054
+ "\n",
11055
+ "anomaly_scores = iso_forest.decision_function(df[sensor_columns])\n",
11056
+ "df['anomaly_score'] = anomaly_scores\n",
11057
+ "df['anomaly'] = iso_forest.predict(df[sensor_columns])\n",
11058
+ "\n",
11059
+ "# Anomaly flagging: -1 for anomalies, 1 for normal points\n",
11060
+ "df['anomaly'] = df['anomaly'].map({1: 0, -1: 1})\n",
11061
+ "\n",
11062
+ "\n",
11063
+ "# Calculate feature importance based on the change in anomaly scores when each sensor is removed\n",
11064
+ "sensor_importance = {}\n",
11065
+ "for sensor in sensor_columns:\n",
11066
+ " df_temp = df[sensor_columns].copy()\n",
11067
+ " df_temp[sensor] = 0 # Remove the influence of this sensor\n",
11068
+ " temp_scores = iso_forest.decision_function(df_temp)\n",
11069
+ " importance = np.mean(np.abs(anomaly_scores - temp_scores))\n",
11070
+ " sensor_importance[sensor] = importance\n",
11071
+ "\n",
11072
+ "# Sort sensors by importance\n",
11073
+ "sorted_sensors = sorted(sensor_importance.items(), key=lambda x: x[1], reverse=True)\n",
11074
+ "\n",
11075
+ "print(\"Sensors contributing to anomalies in descending order of importance:\")\n",
11076
+ "for sensor, importance in sorted_sensors:\n",
11077
+ " print(f\"{sensor}: {importance:.4f}\")\n",
11078
+ "\n",
11079
+ "# Optional: Visualize anomalies\n",
11080
+ "import matplotlib.pyplot as plt\n",
11081
+ "\n",
11082
+ "plt.figure(figsize=(10, 6))\n",
11083
+ "plt.hist(anomaly_scores, bins=50)\n",
11084
+ "plt.title('Anomaly Scores Distribution')\n",
11085
+ "plt.xlabel('Anomaly Score')\n",
11086
+ "plt.ylabel('Frequency')\n",
11087
+ "plt.show()"
11088
+ ]
11089
+ },
11090
+ {
11091
+ "cell_type": "code",
11092
+ "execution_count": 19,
11093
+ "metadata": {},
11094
+ "outputs": [
11095
+ {
11096
+ "data": {
11097
+ "text/plain": [
11098
+ "['AnomalyDetection.joblib']"
11099
+ ]
11100
+ },
11101
+ "execution_count": 19,
11102
+ "metadata": {},
11103
+ "output_type": "execute_result"
11104
+ }
11105
+ ],
11106
+ "source": [
11107
+ "import joblib \n",
11108
+ "joblib.dump(iso_forest, 'AnomalyDetection.joblib')"
11109
+ ]
11110
+ },
11111
+ {
11112
+ "cell_type": "markdown",
11113
+ "metadata": {},
11114
+ "source": [
11115
+ "# Thank you!"
11116
+ ]
11117
  }
11118
  ],
11119
  "metadata": {