sarapy 1.3.1__tar.gz → 2.1.0__tar.gz

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.
Files changed (37) hide show
  1. {sarapy-1.3.1/sarapy.egg-info → sarapy-2.1.0}/PKG-INFO +20 -1
  2. {sarapy-1.3.1 → sarapy-2.1.0}/README.md +19 -0
  3. {sarapy-1.3.1 → sarapy-2.1.0}/pyproject.toml +1 -1
  4. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/dataProcessing/OpsProcessor.py +79 -118
  5. sarapy-2.1.0/sarapy/dataProcessing/TLMSensorDataProcessor.py +90 -0
  6. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/dataProcessing/TimeSeriesProcessor.py +19 -1
  7. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/FertilizerTransformer.py +16 -29
  8. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/PlantinFMCreator.py +39 -59
  9. sarapy-2.1.0/sarapy/preprocessing/TransformInputData.py +149 -0
  10. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/preprocessing/TransformToOutputData.py +16 -33
  11. sarapy-2.1.0/sarapy/stats/__init__.py +1 -0
  12. sarapy-2.1.0/sarapy/stats/stats.py +258 -0
  13. sarapy-2.1.0/sarapy/utils/plotting.py +96 -0
  14. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/version.py +1 -1
  15. {sarapy-1.3.1 → sarapy-2.1.0/sarapy.egg-info}/PKG-INFO +20 -1
  16. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy.egg-info/SOURCES.txt +4 -1
  17. sarapy-1.3.1/sarapy/dataProcessing/TLMSensorDataProcessor.py +0 -781
  18. sarapy-1.3.1/sarapy/preprocessing/TransformInputData.py +0 -110
  19. {sarapy-1.3.1 → sarapy-2.1.0}/LICENCE +0 -0
  20. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/__init__.py +0 -0
  21. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/dataProcessing/GeoProcessor.py +0 -0
  22. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/dataProcessing/__init__.py +0 -0
  23. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/FertilizerFMCreator.py +0 -0
  24. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/PlantinClassifier.py +0 -0
  25. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/__init__.py +0 -0
  26. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/preprocessing/DistancesImputer.py +0 -0
  27. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/preprocessing/FertilizerImputer.py +0 -0
  28. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/preprocessing/__init__.py +0 -0
  29. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/utils/__init__.py +0 -0
  30. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/utils/amg_decoder.py +0 -0
  31. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/utils/amg_ppk.py +0 -0
  32. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/utils/getRawOperations.py +0 -0
  33. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy.egg-info/dependency_links.txt +0 -0
  34. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy.egg-info/requires.txt +0 -0
  35. {sarapy-1.3.1 → sarapy-2.1.0}/sarapy.egg-info/top_level.txt +0 -0
  36. {sarapy-1.3.1 → sarapy-2.1.0}/setup.cfg +0 -0
  37. {sarapy-1.3.1 → sarapy-2.1.0}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: sarapy
3
- Version: 1.3.1
3
+ Version: 2.1.0
4
4
  Home-page: https://github.com/lucasbaldezzari/sarapy
5
5
  Author: Lucas Baldezzari
6
6
  Author-email: Lucas Baldezzari <lmbaldezzari@gmail.com>
@@ -19,6 +19,25 @@ Requires-Dist: geopy
19
19
 
20
20
  Library for processing SARAPICO project metadata of _AMG SA_.
21
21
 
22
+ #### Version 2.1.0
23
+
24
+ - Se modifica TransformInputdata para poder obtener el timestamp en newSample de OpsProcessor. Se modifica transformOutputData para entregar los datos en base a la solicitud de #43. Se modifica OpsProcessor para poder obtener los datos de salida según solicitud en #43. Close #43.
25
+
26
+ #### Version 2.0.0
27
+
28
+ - Se realizan cambios en TransformInputData para que convierta los datos de telemetría a la forma de lista de diccionarios, como un JSON, para que sea más fácil de usar y entender. Además, a futuro, se espera que los datos de la electrónica se puedan enviar en este formato.
29
+ - Se realizan cambios en TLMSensorDataProcessor para que pueda recibir los datos de telemetría como una lista de diccionarios. La clase permite obtener los datos como un numpy array, o indexar por filas y columnas de forma similar a un DataFrame de pandas (de hecho se usa pandas para esto).
30
+ - Se generan cambios en PlantinFMCreator para que pueda recibir los datos de telemetría como una lista de diccionarios, y se usa TLMSensorDataProcessor para procesar los datos y obtener la matriz de características, la cual es la misma que antes.
31
+ - Se generan cambios en FertilizerTransformer para que pueda recibir los datos de telemetría como una lista de diccionarios, y se usa TLMSensorDataProcessor para procesar los datos y obtener la matriz de características, la cual es la misma que antes.
32
+ - Se generan cambios en OpsProcessor para poder trabajar con el formato nuevo de datos.
33
+
34
+ Los detalles de los cambios son comentados en el issue #37.
35
+
36
+ #### Version 1.4.0b1
37
+
38
+ - Se empieza a trabajar sobre librería estadística para poder detectar sensores saturados.
39
+ - Se empieza a trabajar sobre actualización para trabajar con los datos en la nueva modalidad luego de la migración.
40
+
22
41
  #### Version 1.3.1
23
42
 
24
43
  - Se modifica PlantinClassifier para tomar el umbral máximo de dst_pt en 4 y no en 7.
@@ -2,6 +2,25 @@
2
2
 
3
3
  Library for processing SARAPICO project metadata of _AMG SA_.
4
4
 
5
+ #### Version 2.1.0
6
+
7
+ - Se modifica TransformInputdata para poder obtener el timestamp en newSample de OpsProcessor. Se modifica transformOutputData para entregar los datos en base a la solicitud de #43. Se modifica OpsProcessor para poder obtener los datos de salida según solicitud en #43. Close #43.
8
+
9
+ #### Version 2.0.0
10
+
11
+ - Se realizan cambios en TransformInputData para que convierta los datos de telemetría a la forma de lista de diccionarios, como un JSON, para que sea más fácil de usar y entender. Además, a futuro, se espera que los datos de la electrónica se puedan enviar en este formato.
12
+ - Se realizan cambios en TLMSensorDataProcessor para que pueda recibir los datos de telemetría como una lista de diccionarios. La clase permite obtener los datos como un numpy array, o indexar por filas y columnas de forma similar a un DataFrame de pandas (de hecho se usa pandas para esto).
13
+ - Se generan cambios en PlantinFMCreator para que pueda recibir los datos de telemetría como una lista de diccionarios, y se usa TLMSensorDataProcessor para procesar los datos y obtener la matriz de características, la cual es la misma que antes.
14
+ - Se generan cambios en FertilizerTransformer para que pueda recibir los datos de telemetría como una lista de diccionarios, y se usa TLMSensorDataProcessor para procesar los datos y obtener la matriz de características, la cual es la misma que antes.
15
+ - Se generan cambios en OpsProcessor para poder trabajar con el formato nuevo de datos.
16
+
17
+ Los detalles de los cambios son comentados en el issue #37.
18
+
19
+ #### Version 1.4.0b1
20
+
21
+ - Se empieza a trabajar sobre librería estadística para poder detectar sensores saturados.
22
+ - Se empieza a trabajar sobre actualización para trabajar con los datos en la nueva modalidad luego de la migración.
23
+
5
24
  #### Version 1.3.1
6
25
 
7
26
  - Se modifica PlantinClassifier para tomar el umbral máximo de dst_pt en 4 y no en 7.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "sarapy"
3
3
 
4
- version = "1.3.1"
4
+ version = "2.1.0"
5
5
  authors = [
6
6
  {name = "Lucas Baldezzari", email = "lmbaldezzari@gmail.com"},]
7
7
  maintainers = [
@@ -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)
@@ -0,0 +1,90 @@
1
+ ###Documentación en https://github.com/lucasbaldezzari/sarapy/blob/main/docs/Docs.md
2
+ import numpy as np
3
+ import pandas as pd
4
+
5
+ class TLMSensorDataProcessor():
6
+ """- Autor: BALDEZZARI Lucas
7
+
8
+ Estar clase sirve para entegar los datos de telemetría como un array de numpy. La idea es
9
+ poder acceder a los datos a través de indexación, ejemplos:
10
+ tlm_data[["INST_PT","INST_FT"],:] en este caso se obtienen los datos de las columnas INST_PT e INST_FT
11
+
12
+ """
13
+
14
+ def __init__(self, data:list):
15
+ """Constructor de la clase MetadataManager
16
+
17
+ Args:
18
+ - data: Es una lista de diccionarios (como un JSON) con los datos de telemetría.
19
+ """
20
+ #convierto a un DataFrame de pandas
21
+ self.data = pd.DataFrame(data)
22
+ self.keys = self.data.columns.tolist()
23
+
24
+ def getData(self):
25
+ """Devuelve los datos de telemetría como un numpy array.
26
+
27
+ Los datos se retornan como un array de numpy, donde cada fila es una operación y cada columna es un sensor.
28
+ Es decir, los datos son de la forma (n_operaciones, n_columnas).
29
+ """
30
+ return self.data.values
31
+
32
+ def __getitem__(self, key):
33
+ """
34
+ Permite indexar el objeto como un array o DataFrame.
35
+
36
+ Ejemplos:
37
+ obj[["col1", "col2"], :] -> columnas col1 y col2, todas las filas
38
+ obj[:, :100] -> todas las columnas, primeras 100 filas
39
+ obj[:] -> todo
40
+ obj[["col1"], :50] -> columna col1, primeras 50 filas
41
+ """
42
+ if isinstance(key, tuple): ##reviso si es una tupla
43
+ ##se supone que key es una tupla de la forma (cols, rows)
44
+ if len(key) != 2:
45
+ raise ValueError("La clave debe ser una tupla de la forma (cols, rows)")
46
+ cols, rows = key
47
+
48
+ # Columnas
49
+ if isinstance(cols, list) or isinstance(cols, str):
50
+ selected_cols = cols
51
+ elif cols == slice(None): # equivalente a ":"
52
+ selected_cols = self.data.columns
53
+ else:
54
+ raise TypeError(f"Tipo de indexado de columnas no soportado: {type(cols)}")
55
+
56
+ # Filas
57
+ if isinstance(rows, int) or isinstance(rows, slice):
58
+ #retornamos de la forma (n_operaciones, n_columnas)
59
+ return self.data.loc[rows, selected_cols].values
60
+ else:
61
+ raise TypeError(f"Tipo de indexado de filas no soportado: {type(rows)}")
62
+
63
+ elif isinstance(key, slice):
64
+ # Caso para todas las filas y columnas
65
+ # Retornamos de la forma (n_operaciones, n_columnas)
66
+ return self.data.iloc[key].values
67
+
68
+ else:
69
+ raise TypeError("La indexación debe ser una tupla (cols, rows) o un slice ':'")
70
+
71
+ if __name__ == "__main__":
72
+ import pandas as pd
73
+ import json
74
+ from sarapy.preprocessing import TransformInputData
75
+
76
+ historical_data_path = "examples/2025-06-21/UPM000N/historical-data.json"
77
+ with open(historical_data_path, 'r') as file:
78
+ historical_data = json.load(file)
79
+
80
+ inputData_transformer = TransformInputData.TransformInputData()
81
+ data = inputData_transformer.transform(historical_data)
82
+
83
+ tlm_processor = TLMSensorDataProcessor(data=data)
84
+ print(tlm_processor.data.head())
85
+ tlm_processor[:, :10].shape
86
+
87
+ tlm_processor[["id_db_dw", "id_db_h"], :5]#.shape
88
+ tlm_processor.keys
89
+ tlm_processor["longitud",:]
90
+
@@ -54,7 +54,14 @@ class TimeSeriesProcessor(BaseEstimator, TransformerMixin):
54
54
  def transform(self, X: np.array):
55
55
  """Genera un array con los tiempos de operación, caminata, pico abierto y ratio_dCdP.
56
56
  Args:
57
- - X es un array de strings de forma (n, 2) donde la primera columna es el tiempo y la segunda columna es el tiempo de pico abierto (en segundos).
57
+ - X es un array de strings de forma (n, 2) donde la primera columna es el tiempo
58
+ y la segunda columna es el tiempo de pico abierto (en segundos).
59
+
60
+ Returns:
61
+ - Un array de numpy de forma (n, 4) donde la primera columna es
62
+ el tiempo de operación, la segunda columna es el tiempo de caminata,
63
+ la tercera columna es el tiempo de pico abierto y la cuarta columna es
64
+ el ratio entre el tiempo de caminata y el tiempo de pico abierto.
58
65
  """
59
66
 
60
67
  if not self.is_fitted:
@@ -66,6 +73,17 @@ class TimeSeriesProcessor(BaseEstimator, TransformerMixin):
66
73
  self._ratio_dCdP.reshape(-1,1))).round(2)
67
74
 
68
75
  def fit_transform(self, X: np.array, y=None):
76
+ """Genera un array con los tiempos de operación, caminata, pico abierto y ratio_dCdP.
77
+ Args:
78
+ - X es un array de strings de forma (n, 2) donde la primera columna es el tiempo
79
+ y la segunda columna es el tiempo de pico abierto (en segundos).
80
+
81
+ Returns:
82
+ - Un array de numpy de forma (n, 4) donde la primera columna es
83
+ el tiempo de operación, la segunda columna es el tiempo de caminata,
84
+ la tercera columna es el tiempo de pico abierto y la cuarta columna es
85
+ el ratio entre el tiempo de caminata y el tiempo de pico abierto.
86
+ """
69
87
  self.fit(X)
70
88
  return self.transform(X)
71
89
 
@@ -1,4 +1,5 @@
1
1
  import pickle
2
+ from sarapy.dataProcessing import TLMSensorDataProcessor
2
3
 
3
4
  class FertilizerTransformer:
4
5
  """
@@ -31,51 +32,37 @@ class FertilizerTransformer:
31
32
  self.fertilizer_grams = None ##cuando no se ha transformado ningún dato, se inicializa en None
32
33
 
33
34
 
34
- def transform(self, X):
35
+ def transform(self, data):
35
36
  """Transforma los datos de distorsión de fertilizante a gramos.
36
37
 
37
38
  Params:
38
- - X: Es un array con los datos de distorsión de fertilizante. La forma de X es (n,1)
39
-
40
- Ejemplo: [12. 1. 12. 0. 0. 0. 0. 0. 0. 12.]
39
+ - data: Es una lista de diccionarios (como un JSON) con los datos de telemetría.
41
40
 
42
41
  Returns:
43
42
  - 0: Array con los valores de distorsión de fertilizante transformados a gramos.
44
43
  """
45
-
46
- X_poly = self._poly_features.fit_transform(X)
44
+ tlmDataProcessor = TLMSensorDataProcessor.TLMSensorDataProcessor(data)
45
+ X = tlmDataProcessor["SC_FT",:]
46
+ X_poly = self._poly_features.fit_transform(X.reshape(-1, 1))
47
47
  self.fertilizer_grams = self._regresor.predict(X_poly)
48
48
 
49
49
  ##retorno con shape (n,)
50
50
  return self.fertilizer_grams.reshape(-1,)
51
51
 
52
52
  if __name__ == "__main__":
53
- import os
54
53
  import pandas as pd
55
- import numpy as np
54
+ import json
56
55
  from sarapy.preprocessing import TransformInputData
57
- from sarapy.mlProcessors import PlantinFMCreator
58
- import sarapy.utils.getRawOperations as getRawOperations
59
- tindata = TransformInputData.TransformInputData()
60
-
61
- ##cargo los archivos examples\2024-09-04\UPM001N\data.json y examples\2024-09-04\UPM001N\historical-data.json
62
- data_path = os.path.join(os.getcwd(), "examples\\2024-09-04\\UPM001N\\data.json")
63
- historical_data_path = os.path.join(os.getcwd(), "examples\\2024-09-04\\UPM001N\\historical-data.json")
64
- raw_data = pd.read_json(data_path, orient="records").to_dict(orient="records")
65
- raw_data2 = pd.read_json(historical_data_path, orient="records").to_dict(orient="records")
66
-
67
- raw_ops = np.array(getRawOperations.getRawOperations(raw_data, raw_data2))
68
- X = tindata.fit_transform(raw_ops) #transforma los datos de operaciones a un array de numpy
69
-
70
- from sarapy.mlProcessors import FertilizerFMCreator
71
56
 
72
- ftfmcreator = FertilizerFMCreator.FertilizerFMCreator()
73
- dst_ft = ftfmcreator.transform(X[:,2])
74
- ##convierto a int dst_ft
75
- dst_ft = dst_ft.astype(int)
57
+ historical_data_path = "examples/2025-06-21/UPM000N/historical-data.json"
58
+ with open(historical_data_path, 'r') as file:
59
+ historical_data = json.load(file)
76
60
 
77
- from sarapy.mlProcessors import FertilizerTransformer
61
+ ##cargo en un diccionario sarapy\preprocessing\telemetriaDataPosition.json
62
+ data_positions = json.load(open("sarapy/preprocessing/telemetriaDataPosition.json", 'r'))
63
+ transform_input_data = TransformInputData.TransformInputData()
64
+ transformed_data = transform_input_data.transform(historical_data)
78
65
 
79
- fertransformer = FertilizerTransformer.FertilizerTransformer(regresor_file='modelos\\regresor.pkl', poly_features_file='modelos\\poly_features.pkl')
80
- gramos = fertransformer.transform(dst_ft.reshape(-1,1))
66
+ fertransformer = FertilizerTransformer(regresor_file='modelos\\regresor.pkl', poly_features_file='modelos\\poly_features.pkl')
67
+ gramos = fertransformer.transform(transformed_data)
81
68
  print(gramos[:10])