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.
@@ -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
- - kwargs: Diccionario con los argumentos necesarios para la clasificación. Se utiliza para pasar argumentos a los métodos de clasificación.
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
- {"id_db_h": 10, "id_db_dw": 10, "tag_seedling": 1, "tag_fertilizer": gramos (float)}
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.fit_transform(data)
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
- dst_ft = self._ftfmcreator.transform(newSample[:,2]).astype(int)
103
- ft_grams = self._fertilizer_transformer.transform(dst_ft.reshape(-1,1))
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
- date_oprc = newSample[:,3]
106
- return self.transformToOutputData.fit_transform(np.column_stack((id_db_h_nums,
107
- id_db_dw_nums,
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 (numpy.array de strings) de las operaciones.
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(newSample[:,1]) ##identificadores de operarios con nuevas operaciones en la muestra
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: np.array con las columnas de TLM_NPDP, date_oprc, lat, lon, precision
144
- #last_oprc: np.array de la última operación con las columnas de TLM_NPDP, date_oprc, lat, lon, precision
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[newSample[:,1] == ID_NPDP][:,2:] #me quedo con las columnas de TLM_NPDP, date_oprc, lat, lon, precision
156
- id_db_h = newSample[newSample[:,1] == ID_NPDP][:,0]
157
- id_db_dw = newSample[newSample[:,1] == ID_NPDP][:,8]
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.size != 0:
166
- self._operationsDict[ID_NPDP]["sample_ops"] = np.vstack((last_op, 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.operationsDict.keys() if self.operationsDict[ID_NPDP]["new_sample"]]
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.operationsDict[ID_NPDP]["sample_ops"]
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.operationsDict[ID_NPDP]["first_day_op_classified"]:
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.updateBedoreAwake(classified_ops)
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.operationsDict[ID_NPDP]["first_day_op_classified"] = True
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 (numpy.array de strings) de las operaciones.
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.operationsDict.keys():
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.operationsDict.keys():
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.operationsDict.keys():
268
- if self.operationsDict[ID_NPDP]["new_sample"]:
269
- id_db_h_list = np.append(id_db_h_list, self.operationsDict[ID_NPDP]["id_db_h"].flatten())
270
- id_db_dw_list = np.append(id_db_dw_list, self.operationsDict[ID_NPDP]["id_db_dw"].flatten())
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.operationsDict.keys():
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.operationsDict.keys():
276
+ for ID_NPDP in self._operationsDict.keys():
298
277
  self._operationsDict[ID_NPDP]["sample_ops"] = None
299
278
 
300
- def updateBedoreAwake(self, classified_ops):
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 MODEFlag es igual a 1
314
- mask = self.plantinFMCreator.tlmExtracted[:,self.plantinFMCreator.tlmdeDP["MODEFlag"]]==1
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 numpy as np
327
- import os
328
- import sarapy.utils.getRawOperations as getRawOperations
329
- from sarapy.dataProcessing import OpsProcessor
330
-
331
- data_path = os.path.join(os.getcwd(), "examples\\2024-09-16\\UPM001N\\data.json")
332
- historical_data_path = os.path.join(os.getcwd(), "examples\\2024-09-16\\UPM001N\\historical-data.json")
333
-
334
- raw_data = pd.read_json(data_path, orient="records").to_dict(orient="records")
335
- raw_data2 = pd.read_json(historical_data_path, orient="records").to_dict(orient="records")
336
-
337
- raw_ops = getRawOperations.getRawOperations(raw_data, raw_data2)
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
- df = pd.DataFrame(classifications)
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)