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.
- {sarapy-1.3.1/sarapy.egg-info → sarapy-2.1.0}/PKG-INFO +20 -1
- {sarapy-1.3.1 → sarapy-2.1.0}/README.md +19 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/pyproject.toml +1 -1
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/dataProcessing/OpsProcessor.py +79 -118
- sarapy-2.1.0/sarapy/dataProcessing/TLMSensorDataProcessor.py +90 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/dataProcessing/TimeSeriesProcessor.py +19 -1
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/FertilizerTransformer.py +16 -29
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/PlantinFMCreator.py +39 -59
- sarapy-2.1.0/sarapy/preprocessing/TransformInputData.py +149 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/preprocessing/TransformToOutputData.py +16 -33
- sarapy-2.1.0/sarapy/stats/__init__.py +1 -0
- sarapy-2.1.0/sarapy/stats/stats.py +258 -0
- sarapy-2.1.0/sarapy/utils/plotting.py +96 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/version.py +1 -1
- {sarapy-1.3.1 → sarapy-2.1.0/sarapy.egg-info}/PKG-INFO +20 -1
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy.egg-info/SOURCES.txt +4 -1
- sarapy-1.3.1/sarapy/dataProcessing/TLMSensorDataProcessor.py +0 -781
- sarapy-1.3.1/sarapy/preprocessing/TransformInputData.py +0 -110
- {sarapy-1.3.1 → sarapy-2.1.0}/LICENCE +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/__init__.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/dataProcessing/GeoProcessor.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/dataProcessing/__init__.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/FertilizerFMCreator.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/PlantinClassifier.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/mlProcessors/__init__.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/preprocessing/DistancesImputer.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/preprocessing/FertilizerImputer.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/preprocessing/__init__.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/utils/__init__.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/utils/amg_decoder.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/utils/amg_ppk.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy/utils/getRawOperations.py +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy.egg-info/dependency_links.txt +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy.egg-info/requires.txt +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/sarapy.egg-info/top_level.txt +0 -0
- {sarapy-1.3.1 → sarapy-2.1.0}/setup.cfg +0 -0
- {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
|
+
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.
|
|
@@ -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)
|
|
@@ -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
|
|
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,
|
|
35
|
+
def transform(self, data):
|
|
35
36
|
"""Transforma los datos de distorsión de fertilizante a gramos.
|
|
36
37
|
|
|
37
38
|
Params:
|
|
38
|
-
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
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
|
|
80
|
-
gramos = fertransformer.transform(
|
|
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])
|