sarapy 1.3.1__py3-none-any.whl → 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sarapy/dataProcessing/OpsProcessor.py +79 -118
- sarapy/dataProcessing/TLMSensorDataProcessor.py +73 -764
- sarapy/dataProcessing/TimeSeriesProcessor.py +19 -1
- sarapy/mlProcessors/FertilizerTransformer.py +16 -29
- sarapy/mlProcessors/PlantinFMCreator.py +39 -59
- sarapy/preprocessing/TransformInputData.py +123 -84
- sarapy/preprocessing/TransformToOutputData.py +16 -33
- sarapy/stats/__init__.py +1 -0
- sarapy/stats/stats.py +258 -0
- sarapy/utils/plotting.py +96 -0
- sarapy/version.py +1 -1
- {sarapy-1.3.1.dist-info → sarapy-2.1.0.dist-info}/METADATA +20 -1
- sarapy-2.1.0.dist-info/RECORD +29 -0
- sarapy-1.3.1.dist-info/RECORD +0 -26
- {sarapy-1.3.1.dist-info → sarapy-2.1.0.dist-info}/LICENCE +0 -0
- {sarapy-1.3.1.dist-info → sarapy-2.1.0.dist-info}/WHEEL +0 -0
- {sarapy-1.3.1.dist-info → sarapy-2.1.0.dist-info}/top_level.txt +0 -0
|
@@ -5,6 +5,12 @@ from sarapy.mlProcessors import PlantinFMCreator
|
|
|
5
5
|
from sarapy.mlProcessors import PlantinClassifier
|
|
6
6
|
from sarapy.preprocessing import TransformInputData, TransformToOutputData
|
|
7
7
|
from sarapy.mlProcessors import FertilizerFMCreator, FertilizerTransformer
|
|
8
|
+
import logging
|
|
9
|
+
|
|
10
|
+
##nivel de logging en warning para evitar mensajes de advertencia de sklearn
|
|
11
|
+
logging.basicConfig(level=logging.WARNING,
|
|
12
|
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
|
|
13
|
+
datefmt='%Y-%m-%d %H:%M:%S')
|
|
8
14
|
|
|
9
15
|
class OpsProcessor():
|
|
10
16
|
"""Clase para procesar las operaciones de los operarios. La información se toma de la base de datos
|
|
@@ -66,48 +72,41 @@ class OpsProcessor():
|
|
|
66
72
|
Se toma una nueva muestra y se procesa la información para clasificar las operaciones considerando el
|
|
67
73
|
plantín y por otro lado el fertilizante.
|
|
68
74
|
Se retorna un array con las clasificaciones concatenadas, manteniendo el orden de las operaciones por operario.
|
|
69
|
-
|
|
70
|
-
Args:
|
|
71
|
-
data: Es una lista de diccionario. Cada diccionario tiene los siguientes keys.
|
|
72
|
-
|
|
73
|
-
Ejemplo:
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
"id_db_h":1, #int
|
|
77
|
-
"ID_NPDP":"XXAA123", #string
|
|
78
|
-
"FR": 1, #int
|
|
79
|
-
"TLM_NPDP": b'\xfc\x01\t\t\x00\x00\x00\x98', #bytes
|
|
80
|
-
"date_oprc":datetime.datetime(2024, 2, 16, 21, 2, 2, tzinfo=tzutc()),#datetime
|
|
81
|
-
"Latitud":-32.145564789, #float
|
|
82
|
-
"Longitud":-55.145564789, #float
|
|
83
|
-
"Precision": 1000,
|
|
84
|
-
"id_db_dw": 1 #int
|
|
85
|
-
}
|
|
86
75
|
|
|
87
|
-
|
|
76
|
+
Args:
|
|
77
|
+
- data: Lista de diccionario. Cada diccionario tiene la forma.
|
|
78
|
+
Ejemplo (NOTA: El salto de línea es agregado para mejorar la legibilidad):
|
|
79
|
+
[
|
|
80
|
+
{"id": 6, "receiver_timestamp": "2025-06-21T15:51:36.527825+00:00", "timestamp": "2025-06-21T15:51:01.000002+00:00", "datum": null,
|
|
81
|
+
"csv_datum": "2025-06-21T15:51:01.000002+00:00,2,0,0,1,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,-58.0321,-33.2471,
|
|
82
|
+
1,0,0,0,0,0,0,0,0,0,1,1,1,0,1,1,0,0,0,0,1,0,0,1,1,0,3,0,0,0,0,3,0,0,0,0,0,1,0,0,0,0,0.0000,0.0000,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0",
|
|
83
|
+
"longitude": null, "latitude": null, "accuracy": null, "tag_seedling": null, "tag_fertilizer": null}
|
|
84
|
+
]
|
|
88
85
|
|
|
89
86
|
Returns:
|
|
90
87
|
Lista de diccionarios con las clasificaciones. Cada diccionario tiene la forma
|
|
91
|
-
{"
|
|
88
|
+
[{"timestamp": "2025-05-27T21:42:38.000002+00:00", "tag_seedling": 1, "tag_fertilizer": gramos (float)},...]
|
|
92
89
|
"""
|
|
93
90
|
|
|
94
91
|
##chqueo que newSample no esté vacío
|
|
95
92
|
if len(data) != 0:
|
|
96
|
-
newSample = self.transformInputData.
|
|
93
|
+
newSample = self.transformInputData.transform(data)
|
|
94
|
+
|
|
97
95
|
#Si tenemos nuevas operaciones, actualizamos el diccionario de operaciones
|
|
98
96
|
self.updateOperationsDict(newSample) #actualizamos diccionario interno de la clase
|
|
99
97
|
pl_clas = self.classifyForPlantin(**kwargs) #clasificamos las operaciones para plantín
|
|
100
98
|
|
|
101
99
|
#estimamos los gramos de fertilizante
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
ft_grams = self._fertilizer_transformer.transform(newSample)
|
|
101
|
+
logging.debug(f"Fertilizer grams shape: {ft_grams.shape}")
|
|
104
102
|
id_db_h_nums, id_db_dw_nums = self.getActualOperationsNumbers() #obtenemos los números de operaciones desde el diccionario de operaciones
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
103
|
+
logging.debug(f"ID_DB_H shape: {id_db_h_nums.shape}, ID_DB_DW shape: {id_db_dw_nums.shape}")
|
|
104
|
+
date_oprc = pd.DataFrame(newSample)["date_oprc"].values.reshape(-1, 1) ##extraigo las fechas de operación de la muestra
|
|
105
|
+
timestamps = pd.DataFrame(newSample)["timestamp"].values.reshape(-1, 1) ##extraigo los timestamps de la muestra
|
|
106
|
+
|
|
107
|
+
return self.transformToOutputData.fit_transform(np.column_stack((timestamps,
|
|
108
108
|
pl_clas,
|
|
109
|
-
ft_grams
|
|
110
|
-
date_oprc)))
|
|
109
|
+
ft_grams)))
|
|
111
110
|
else:
|
|
112
111
|
self.resetAllNewSamplesValues()
|
|
113
112
|
return None
|
|
@@ -116,33 +115,20 @@ class OpsProcessor():
|
|
|
116
115
|
"""Actualiza el diccionario de operaciones.
|
|
117
116
|
|
|
118
117
|
Args:
|
|
119
|
-
- newSample: lista con los datos
|
|
120
|
-
|
|
121
|
-
- 0: id_db_h
|
|
122
|
-
- 1: ID_NPDP
|
|
123
|
-
- 2: TLM_NPDP
|
|
124
|
-
- 3: date_oprc
|
|
125
|
-
- 4: latitud
|
|
126
|
-
- 5: longitud
|
|
127
|
-
- 6: Precision
|
|
128
|
-
- 7: FR
|
|
129
|
-
- 8: id_db_dw
|
|
130
|
-
|
|
131
|
-
Returns:
|
|
132
|
-
- None
|
|
133
|
-
NOTA: PENSAR SI SE DEVUELVE ALGO COMO UN TRUE O FALSE PARA SABER SI SE ACTUALIZÓ O NO EL DICCIONARIO
|
|
134
|
-
DE MANERA CORRECTA O HUBO ALGÚN PROBLEMA Y ASÍ VER QUÉ HACER EN EL MAIN
|
|
118
|
+
- newSample: lista de diccionarios con los datos de las operaciones.
|
|
119
|
+
|
|
135
120
|
"""
|
|
136
|
-
|
|
137
|
-
ID_NPDPs_newOperations = np.unique(
|
|
138
|
-
|
|
121
|
+
nodos_recibidos = np.array([row["ID_NPDP"] for row in newSample]) ##nodos recibidos en la muestra
|
|
122
|
+
ID_NPDPs_newOperations = np.unique(nodos_recibidos) ##identificadores de operarios con nuevas operaciones en la muestra
|
|
123
|
+
logging.debug(f"Received nodes: {ID_NPDPs_newOperations}")
|
|
124
|
+
|
|
139
125
|
##chqueo si estos ID_NPDPs ya están en el diccionario, sino los agrego
|
|
140
126
|
for ID_NPDP in ID_NPDPs_newOperations:
|
|
141
127
|
if ID_NPDP not in self._operationsDict:
|
|
142
128
|
#El diccionario contiene la siguiente información:
|
|
143
|
-
#sample_ops:
|
|
144
|
-
#last_oprc:
|
|
145
|
-
#first_day_op_classified: booleano para saber si es la primera operación del día fue clasificada
|
|
129
|
+
#sample_ops: lista de diccionarios con los datos de las operaciones.
|
|
130
|
+
#last_oprc: diccionario con la última operación registrada.
|
|
131
|
+
#first_day_op_classified: booleano para saber si es la primera operación del día que fue clasificada
|
|
146
132
|
self._operationsDict[ID_NPDP] = {"sample_ops": None,
|
|
147
133
|
"last_oprc": None,
|
|
148
134
|
"first_day_op_classified": False,
|
|
@@ -152,18 +138,18 @@ class OpsProcessor():
|
|
|
152
138
|
|
|
153
139
|
##actualizo el diccionario con las operaciones nuevas para aquellos operarios que correspondan
|
|
154
140
|
for ID_NPDP in ID_NPDPs_newOperations:
|
|
155
|
-
sample_ops = newSample
|
|
156
|
-
id_db_h =
|
|
157
|
-
id_db_dw =
|
|
141
|
+
sample_ops = newSample
|
|
142
|
+
id_db_h = np.array([row["id_db_h"] for row in newSample]) ##extraigo los id_db_h de la muestra
|
|
143
|
+
id_db_dw = np.array([row["id_db_dw"] for row in newSample])
|
|
158
144
|
##actualizo el diccionario
|
|
159
145
|
self._operationsDict[ID_NPDP]["sample_ops"] = sample_ops
|
|
160
|
-
self._operationsDict[ID_NPDP]["id_db_h"] = id_db_h
|
|
161
|
-
self._operationsDict[ID_NPDP]["id_db_dw"] = id_db_dw
|
|
146
|
+
self._operationsDict[ID_NPDP]["id_db_h"] = np.astype(id_db_h, str) ##convierto a int
|
|
147
|
+
self._operationsDict[ID_NPDP]["id_db_dw"] = np.astype(id_db_dw, str) ##convierto a int
|
|
162
148
|
##chequeo si tenemos última operación, si es así, asignamos dicha operación en la primera fila de sample_ops
|
|
163
149
|
last_op = self._operationsDict[ID_NPDP]["last_oprc"]
|
|
164
150
|
###si last_op es not None y last_op no está vacía, entonces concatenamos last_op con sample_ops
|
|
165
|
-
if last_op is not None and last_op
|
|
166
|
-
self._operationsDict[ID_NPDP]["sample_ops"]
|
|
151
|
+
if last_op is not None and len(last_op) != 0:
|
|
152
|
+
self._operationsDict[ID_NPDP]["sample_ops"] += last_op ##concatenamos la última operación con las nuevas operaciones
|
|
167
153
|
|
|
168
154
|
self.updateNewSamplesValues(ID_NPDPs_newOperations) #actualizo el estado de 'new_sample' en el diccionario de operaciones
|
|
169
155
|
self.updateLastOperations(ID_NPDPs_newOperations) #actualizo la última operación de una muestra de operaciones en el diccionario de operaciones
|
|
@@ -193,25 +179,27 @@ class OpsProcessor():
|
|
|
193
179
|
plantinClassifications = None
|
|
194
180
|
|
|
195
181
|
##me quedo con los ID_NPDPs que tengan _operationsDict[ID_NPDP]["new_sample"] iguales a True
|
|
196
|
-
ops_with_new_sample = [ID_NPDP for ID_NPDP in self.
|
|
182
|
+
ops_with_new_sample = [ID_NPDP for ID_NPDP in self._operationsDict.keys() if self.operationsDict[ID_NPDP]["new_sample"]]
|
|
197
183
|
|
|
198
184
|
for ID_NPDP in ops_with_new_sample:#self.operationsDict.keys():
|
|
199
185
|
##clasificamos las operaciones para plantín
|
|
200
|
-
operations = self.
|
|
186
|
+
operations = self._operationsDict[ID_NPDP]["sample_ops"]
|
|
187
|
+
logging.debug(f"Número de operaciones para el nodo {ID_NPDP}: {len(operations)}")
|
|
201
188
|
features, dst_pt, inest_pt = self.plantinFMCreator.fit_transform(operations)
|
|
189
|
+
logging.debug(f"Features shape for {ID_NPDP}: {features.shape}")
|
|
202
190
|
classified_ops = self._plantin_classifier.classify(features, dst_pt, inest_pt, **classify_kwargs)
|
|
191
|
+
logging.debug(f"Classified operations shape for {ID_NPDP}: {classified_ops.shape}")
|
|
203
192
|
|
|
204
193
|
##chequeo si first_day_op_classified es True, si es así, no se considera la primera fila de las classified_ops
|
|
205
|
-
if self.
|
|
194
|
+
if self._operationsDict[ID_NPDP]["first_day_op_classified"]:
|
|
206
195
|
classified_ops = classified_ops[1:]
|
|
207
196
|
|
|
208
197
|
##actualizo las operaciones que hayan sido hardcodeadas luego de despertar y/o reiniciar la electrónica
|
|
209
|
-
classified_ops = self.
|
|
198
|
+
classified_ops = self.updateAfterAwake(classified_ops)
|
|
210
199
|
|
|
211
|
-
# plantinClassifications = np.vstack((plantinClassifications, classified_ops)) if plantinClassifications is not None else classified_ops
|
|
212
200
|
plantinClassifications = np.concatenate((plantinClassifications, classified_ops)) if plantinClassifications is not None else classified_ops
|
|
213
201
|
|
|
214
|
-
self.
|
|
202
|
+
self._operationsDict[ID_NPDP]["first_day_op_classified"] = True
|
|
215
203
|
|
|
216
204
|
return plantinClassifications
|
|
217
205
|
|
|
@@ -219,18 +207,7 @@ class OpsProcessor():
|
|
|
219
207
|
"""Método para actualizar la última operación de una muestra de operaciones en el diccionario de operaciones
|
|
220
208
|
|
|
221
209
|
Args:
|
|
222
|
-
- newSample: lista con los datos
|
|
223
|
-
La forma de cada dato dentro de la lista newSample es (n,6). Las columnas de newSample son,
|
|
224
|
-
|
|
225
|
-
- 0: id_db_h
|
|
226
|
-
- 1: ID_NPDP
|
|
227
|
-
- 2: TLM_NPDP
|
|
228
|
-
- 3: date_oprc
|
|
229
|
-
- 4: latitud
|
|
230
|
-
- 5: longitud
|
|
231
|
-
- 6: Precision
|
|
232
|
-
- 7: FR
|
|
233
|
-
- 8: id_db_dw
|
|
210
|
+
- newSample: lista de diccionarios con los datos de las operaciones.
|
|
234
211
|
"""
|
|
235
212
|
|
|
236
213
|
for ID_NPDP in ID_NPDPs_newOperations:
|
|
@@ -245,8 +222,9 @@ class OpsProcessor():
|
|
|
245
222
|
|
|
246
223
|
##recorro el diccionario de operaciones y actualizo el estado de 'new_sample' a
|
|
247
224
|
##True para los ID_NPDPs que tienen nuevas operaciones y a False para los que no tienen nuevas operaciones
|
|
248
|
-
for ID_NPDP in self.
|
|
225
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
249
226
|
if ID_NPDP in ID_NPDPs_newOperations:
|
|
227
|
+
logging.debug(f"Actualizando 'new_sample' para nodo: {ID_NPDP}")
|
|
250
228
|
self._operationsDict[ID_NPDP]["new_sample"] = True
|
|
251
229
|
else:
|
|
252
230
|
self._operationsDict[ID_NPDP]["new_sample"] = False
|
|
@@ -255,19 +233,20 @@ class OpsProcessor():
|
|
|
255
233
|
"""Método para resetar todos los valores de new_sample en el diccionario de operaciones.
|
|
256
234
|
"""
|
|
257
235
|
|
|
258
|
-
for ID_NPDP in self.
|
|
236
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
259
237
|
self._operationsDict[ID_NPDP]["new_sample"] = False
|
|
260
238
|
|
|
261
239
|
def getActualOperationsNumbers(self):
|
|
262
240
|
"""Método para obtener los números de operaciones desde el diccionario de operaciones para aquellos operarios que
|
|
263
241
|
tienen nuevas operaciones en la muestra."""
|
|
264
|
-
|
|
242
|
+
|
|
265
243
|
id_db_h_list = np.array([])
|
|
266
244
|
id_db_dw_list = np.array([])
|
|
267
|
-
for ID_NPDP in self.
|
|
268
|
-
if self.
|
|
269
|
-
|
|
270
|
-
|
|
245
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
246
|
+
if self._operationsDict[ID_NPDP]["new_sample"]:
|
|
247
|
+
logging.debug(f"Obteniendo números de operaciones para el ID_NPDP: {ID_NPDP}")
|
|
248
|
+
id_db_h_list = np.append(id_db_h_list, self._operationsDict[ID_NPDP]["id_db_h"].flatten())
|
|
249
|
+
id_db_dw_list = np.append(id_db_dw_list, self._operationsDict[ID_NPDP]["id_db_dw"].flatten())
|
|
271
250
|
|
|
272
251
|
return id_db_h_list.astype(int), id_db_dw_list.astype(int)
|
|
273
252
|
|
|
@@ -275,7 +254,7 @@ class OpsProcessor():
|
|
|
275
254
|
"""Método para actualizar el indicador de si es la primera operación del día para cada operario en el diccionario de operaciones.
|
|
276
255
|
"""
|
|
277
256
|
|
|
278
|
-
for ID_NPDP in self.
|
|
257
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
279
258
|
self._operationsDict[ID_NPDP]["first_day_op_classified"] = False
|
|
280
259
|
|
|
281
260
|
def cleanSamplesOperations(self):
|
|
@@ -294,10 +273,10 @@ class OpsProcessor():
|
|
|
294
273
|
- 6: Precision
|
|
295
274
|
"""
|
|
296
275
|
|
|
297
|
-
for ID_NPDP in self.
|
|
276
|
+
for ID_NPDP in self._operationsDict.keys():
|
|
298
277
|
self._operationsDict[ID_NPDP]["sample_ops"] = None
|
|
299
278
|
|
|
300
|
-
def
|
|
279
|
+
def updateAfterAwake(self, classified_ops):
|
|
301
280
|
"""
|
|
302
281
|
Función para actualizar las operaciones que hayan sido hardcodeadas luego de despertar y/o reiniciar la electrónica.
|
|
303
282
|
|
|
@@ -310,8 +289,8 @@ class OpsProcessor():
|
|
|
310
289
|
- classified_ops: np.array con las operaciones clasificadas.
|
|
311
290
|
"""
|
|
312
291
|
|
|
313
|
-
##me quedo con los índices donde
|
|
314
|
-
mask = self.plantinFMCreator.
|
|
292
|
+
##me quedo con los índices donde N_MODE es igual a 1
|
|
293
|
+
mask = self.plantinFMCreator.tlmDataProcessor["N_MODE",:]==1
|
|
315
294
|
classified_ops[mask] = 0 ##hardcodeo las operaciones que hayan sido clasificadas como 1
|
|
316
295
|
return classified_ops
|
|
317
296
|
|
|
@@ -321,37 +300,19 @@ class OpsProcessor():
|
|
|
321
300
|
|
|
322
301
|
|
|
323
302
|
if __name__ == "__main__":
|
|
324
|
-
#cargo archivo examples\volcado_17112023_NODE_processed.csv
|
|
325
303
|
import pandas as pd
|
|
326
|
-
import
|
|
327
|
-
import
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
import time
|
|
340
|
-
start_time = time.time()
|
|
341
|
-
op = OpsProcessor.OpsProcessor(classifier_file='modelos\\pipeline_rf.pkl', imputeDistances = False,
|
|
342
|
-
regresor_file='modelos\\regresor.pkl', poly_features_file='modelos\\poly_features.pkl')
|
|
343
|
-
classifications = op.processOperations(raw_ops, update_samePlace=True, useRatioStats=True)
|
|
344
|
-
end_time = time.time()
|
|
345
|
-
execution_time = end_time - start_time
|
|
346
|
-
print("Execution time:", execution_time, "seconds")
|
|
304
|
+
import json
|
|
305
|
+
import logging
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
historical_data_path = "examples/2025-06-21/UPM000N/historical-data.json"
|
|
309
|
+
with open(historical_data_path, 'r') as file:
|
|
310
|
+
samples = json.load(file)
|
|
311
|
+
|
|
312
|
+
samples1 = samples
|
|
313
|
+
|
|
314
|
+
op = OpsProcessor(classifier_file='modelos\\pipeline_rf.pkl', imputeDistances = False,
|
|
315
|
+
regresor_file='modelos\\regresor.pkl', poly_features_file='modelos\\poly_features.pkl')
|
|
347
316
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
tag_seedling = df["tag_seedling"].values
|
|
351
|
-
print(tag_seedling.mean())
|
|
352
|
-
print(df["tag_seedling"].shape)
|
|
353
|
-
|
|
354
|
-
##datos de fertilizante
|
|
355
|
-
tag_fertilizer = df["tag_fertilizer"].values
|
|
356
|
-
print(tag_fertilizer[1500:1560])
|
|
357
|
-
print(tag_fertilizer.mean())
|
|
317
|
+
print(op.processOperations(samples[:20]))
|
|
318
|
+
# op.processOperations(samples2)
|