IGJSP 1.0.0__py3-none-any.whl → 1.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.
IGJSP/generador.py CHANGED
@@ -3,16 +3,18 @@ import datetime
3
3
  import json
4
4
  import os
5
5
  import pickle
6
+ import re
6
7
  from itertools import combinations
8
+
7
9
  import networkx as nx
8
10
  import numpy as np
9
- import re
10
11
 
11
12
  np.set_printoptions(linewidth=np.inf, threshold=np.inf, formatter={"int": lambda x: f"{x}"})
12
13
 
13
- from scipy.stats import expon, norm, uniform
14
14
  from pprint import pprint
15
15
 
16
+ from scipy.stats import expon, norm, uniform
17
+
16
18
 
17
19
  def f(x):
18
20
  return int(np.exp(-int(x)/100)*100)
@@ -23,6 +25,13 @@ def g(x):
23
25
  def t(c):
24
26
  return 4.0704 * np.log(2) / np.log(1 + (c* 2.5093)**3)
25
27
 
28
+
29
+ #################################################################################
30
+ # #
31
+ # JSP #
32
+ # #
33
+ #################################################################################
34
+
26
35
  class JSP:
27
36
  def __init__(self, jobs, machines, ProcessingTime=np.array([]), EnergyConsumption=np.array([]), ReleaseDateDueDate=np.array([]), Orden=np.array([])) -> None:
28
37
  self.numJobs = jobs
@@ -115,10 +124,325 @@ class JSP:
115
124
  else:
116
125
  return expon(loc=duration, scale=duration/2).rvs()
117
126
 
118
- def savePythonFile(self, path):
119
- os.makedirs(os.path.dirname(path), exist_ok=True)
120
- with open(path, 'wb') as f:
121
- pickle.dump(self, f)
127
+ def loadPythonFile(path):
128
+ """
129
+ Carga un fichero .pkl generado por savePythonFile y devuelve un JSP.
130
+ """
131
+ with open(path, 'rb') as f:
132
+ obj = pickle.load(f)
133
+
134
+ # Si ya es un JSP, lo devolvemos tal cual
135
+ if isinstance(obj, JSP):
136
+ return obj
137
+
138
+ # Si es un dict con la misma estructura que usamos en loadJsonFile, construimos el JSP
139
+ if isinstance(obj, dict) and all(
140
+ k in obj for k in ['jobs', 'machines', 'ProcessingTime', 'EnergyConsumption', 'ReleaseDateDueDate', 'Orden']
141
+ ):
142
+ return JSP(**obj)
143
+
144
+ raise TypeError(
145
+ f"El objeto cargado desde {path} no es un JSP ni un diccionario compatible para construir uno."
146
+ )
147
+
148
+ def loadDznFile(path):
149
+ """
150
+ Carga un .dzn generado por saveDznFile y devuelve un JSP.
151
+ Soporta rddd = 0, 1, 2 (sin fechas, fechas por job, fechas por operación).
152
+ """
153
+ with open(path, 'r', encoding='utf-8') as f:
154
+ text = f.read()
155
+
156
+ numJobs = _parse_int_var(text, 'jobs')
157
+ numMchs = _parse_int_var(text, 'machines')
158
+ speed = _parse_int_var(text, 'Speed', default=1)
159
+
160
+ time_flat = _parse_array_var(text, 'time')
161
+ energy_flat = _parse_array_var(text, 'energy')
162
+ prec_flat = _parse_array_var(text, 'precedence')
163
+
164
+ if time_flat is None or energy_flat is None or prec_flat is None:
165
+ raise ValueError("Faltan variables obligatorias (time, energy o precedence) en el fichero DZN.")
166
+
167
+ # Comprobar tamaños
168
+ expected_te = numJobs * numMchs * speed
169
+ if time_flat.size != expected_te or energy_flat.size != expected_te:
170
+ raise ValueError(
171
+ f"Tamaños incompatibles en time/energy: esperado {expected_te}, "
172
+ f"time={time_flat.size}, energy={energy_flat.size}"
173
+ )
174
+
175
+ expected_prec = numJobs * numMchs
176
+ if prec_flat.size != expected_prec:
177
+ raise ValueError(
178
+ f"Tamaño incompatible en precedence: esperado {expected_prec}, precedence={prec_flat.size}"
179
+ )
180
+
181
+ ProcessingTime = time_flat.reshape((numJobs, numMchs, speed))
182
+ EnergyConsumption = energy_flat.reshape((numJobs, numMchs, speed))
183
+ precedence = prec_flat.reshape((numJobs, numMchs))
184
+
185
+ # Reconstruir Orden a partir de la precedencia (cada fila es posiciones 0..numMchs-1 por máquina)
186
+ Orden = np.zeros((numJobs, numMchs), dtype=int)
187
+ for j in range(numJobs):
188
+ # precedence[j, m] = posición de la máquina m en la secuencia
189
+ # argsort da el índice de la máquina por orden de prioridad
190
+ Orden[j, :] = np.argsort(precedence[j, :])
191
+
192
+ # Release / Due dates (pueden no existir)
193
+ release_flat = _parse_array_var(text, 'releaseDate')
194
+ due_flat = _parse_array_var(text, 'dueDate')
195
+
196
+ if release_flat is None or due_flat is None:
197
+ # rddd = 0
198
+ ReleaseDueDate = np.array([])
199
+ else:
200
+ # O bien vector por job, o matriz jobs x machines
201
+ if release_flat.size == numJobs and due_flat.size == numJobs:
202
+ # rddd = 1 → (numJobs, 2)
203
+ ReleaseDueDate = np.zeros((numJobs, 2), dtype=int)
204
+ ReleaseDueDate[:, 0] = release_flat
205
+ ReleaseDueDate[:, 1] = due_flat
206
+ elif release_flat.size == numJobs * numMchs and due_flat.size == numJobs * numMchs:
207
+ # rddd = 2 → (numJobs, numMchs, 2)
208
+ ReleaseDueDate = np.zeros((numJobs, numMchs, 2), dtype=int)
209
+ ReleaseDueDate[:, :, 0] = release_flat.reshape((numJobs, numMchs))
210
+ ReleaseDueDate[:, :, 1] = due_flat.reshape((numJobs, numMchs))
211
+ else:
212
+ raise ValueError(
213
+ "Los tamaños de releaseDate/dueDate no cuadran ni con rddd=1 ni con rddd=2."
214
+ )
215
+
216
+ sol = {
217
+ 'jobs': numJobs,
218
+ 'machines': numMchs,
219
+ 'ProcessingTime': ProcessingTime,
220
+ 'EnergyConsumption': EnergyConsumption,
221
+ 'ReleaseDateDueDate': ReleaseDueDate,
222
+ 'Orden': Orden
223
+ }
224
+ return JSP(**sol)
225
+
226
+ def loadTaillardFile(path):
227
+ """
228
+ Carga un fichero de texto generado por saveTaillardStandardFile y devuelve un JSP.
229
+ Formato esperado:
230
+
231
+ Number of jobs: J
232
+ Number of machines: M
233
+
234
+ Processing times:
235
+ ... J filas, cada una con M enteros ...
236
+
237
+ Energy consumption:
238
+ ... J filas, cada una con M enteros ...
239
+
240
+ Machine order:
241
+ ... J filas, cada una con M enteros ...
242
+ """
243
+ with open(path, 'r') as f:
244
+ lines = [line.strip() for line in f]
245
+
246
+ # Leer encabezado
247
+ # Number of jobs: X
248
+ # Number of machines: Y
249
+ numJobs = None
250
+ numMchs = None
251
+
252
+ i = 0
253
+ while i < len(lines):
254
+ line = lines[i]
255
+ if line.startswith("Number of jobs"):
256
+ numJobs = int(line.split(":")[1].strip())
257
+ elif line.startswith("Number of machines"):
258
+ numMchs = int(line.split(":")[1].strip())
259
+ if numJobs is not None and numMchs is not None:
260
+ i += 1
261
+ break
262
+ i += 1
263
+
264
+ if numJobs is None or numMchs is None:
265
+ raise ValueError("No se pudieron leer numJobs / numMchs del fichero Taillard.")
266
+
267
+ # Saltar líneas vacías hasta "Processing times:"
268
+ while i < len(lines) and lines[i] == "":
269
+ i += 1
270
+ if i >= len(lines) or not lines[i].startswith("Processing times"):
271
+ raise ValueError("No se encontró la sección 'Processing times:' en el fichero Taillard.")
272
+ i += 1 # pasar la línea de cabecera
273
+
274
+ # Leer matriz de tiempos de procesamiento (J filas)
275
+ proc_by_order = np.zeros((numJobs, numMchs), dtype=int)
276
+ for j in range(numJobs):
277
+ while i < len(lines) and lines[i] == "":
278
+ i += 1
279
+ parts = lines[i].split()
280
+ if len(parts) != numMchs:
281
+ raise ValueError(
282
+ f"Línea de tiempos de procesamiento para job {j} tiene {len(parts)} elementos, "
283
+ f"pero se esperaban {numMchs}."
284
+ )
285
+ proc_by_order[j, :] = [int(x) for x in parts]
286
+ i += 1
287
+
288
+ # Saltar hasta "Energy consumption:"
289
+ while i < len(lines) and lines[i] == "":
290
+ i += 1
291
+ if i >= len(lines) or not lines[i].startswith("Energy consumption"):
292
+ raise ValueError("No se encontró la sección 'Energy consumption:' en el fichero Taillard.")
293
+ i += 1 # cabecera
294
+
295
+ energy_by_order = np.zeros((numJobs, numMchs), dtype=int)
296
+ for j in range(numJobs):
297
+ while i < len(lines) and lines[i] == "":
298
+ i += 1
299
+ parts = lines[i].split()
300
+ if len(parts) != numMchs:
301
+ raise ValueError(
302
+ f"Línea de consumo de energía para job {j} tiene {len(parts)} elementos, "
303
+ f"pero se esperaban {numMchs}."
304
+ )
305
+ energy_by_order[j, :] = [int(x) for x in parts]
306
+ i += 1
307
+
308
+ # Saltar hasta "Machine order:"
309
+ while i < len(lines) and lines[i] == "":
310
+ i += 1
311
+ if i >= len(lines) or not lines[i].startswith("Machine order"):
312
+ raise ValueError("No se encontró la sección 'Machine order:' en el fichero Taillard.")
313
+ i += 1 # cabecera
314
+
315
+ Orden = np.zeros((numJobs, numMchs), dtype=int)
316
+ for j in range(numJobs):
317
+ while i < len(lines) and lines[i] == "":
318
+ i += 1
319
+ parts = lines[i].split()
320
+ if len(parts) != numMchs:
321
+ raise ValueError(
322
+ f"Línea de orden de máquinas para job {j} tiene {len(parts)} elementos, "
323
+ f"pero se esperaban {numMchs}."
324
+ )
325
+ Orden[j, :] = [int(x) for x in parts]
326
+ i += 1
327
+
328
+ # Reconstruir ProcessingTime y EnergyConsumption con speed=1
329
+ speed = 1
330
+ ProcessingTime = np.zeros((numJobs, numMchs, speed), dtype=int)
331
+ EnergyConsumption = np.zeros((numJobs, numMchs, speed), dtype=int)
332
+
333
+ for j in range(numJobs):
334
+ for pos in range(numMchs):
335
+ machine = Orden[j, pos]
336
+ ProcessingTime[j, machine, 0] = proc_by_order[j, pos]
337
+ EnergyConsumption[j, machine, 0] = energy_by_order[j, pos]
338
+
339
+ # Taillard estándar: sin release/due dates → rddd=0
340
+ ReleaseDueDate = np.array([])
341
+
342
+ sol = {
343
+ 'jobs': numJobs,
344
+ 'machines': numMchs,
345
+ 'ProcessingTime': ProcessingTime,
346
+ 'EnergyConsumption': EnergyConsumption,
347
+ 'ReleaseDateDueDate': ReleaseDueDate,
348
+ 'Orden': Orden
349
+ }
350
+ return JSP(**sol)
351
+
352
+ def loadJsonFile(path):
353
+ with open(path, "r") as f:
354
+ data = json.load(f)
355
+ numJobs = len(data["nbJobs"])
356
+ numMchs = len(data["nbMchs"])
357
+ speed = data["speed"]
358
+
359
+ # # Load KPIs (opcional)
360
+ # min_makespan = data.get("minMakespan", None)
361
+ # min_energy = data.get("minEnergy", None)
362
+ # max_min_makespan = data.get("maxMinMakespan", None)
363
+ # max_min_energy = data.get("maxMinEnergy", None)
364
+
365
+ # Prepare empty structures
366
+ ProcessingTime = np.zeros((numJobs, numMchs, speed), dtype=int)
367
+ EnergyConsumption = np.zeros((numJobs, numMchs, speed), dtype=int)
368
+ Orden_list = [[] for _ in range(numJobs)]
369
+
370
+ # Detect rddd mode
371
+ # rddd = 0 → no release/due dates
372
+ # rddd = 1 → job-level RDF
373
+ # rddd = 2 → operation-level RDF
374
+ rddd = 0
375
+ if data["timeEnergy"]:
376
+ if "release-date" in data["timeEnergy"][0]:
377
+ rddd = 1
378
+ for m in data["timeEnergy"][0]["operations"]:
379
+ if "release-date" in data["timeEnergy"][0]["operations"][m]:
380
+ rddd = 2
381
+ break
382
+
383
+ # Initialize ReleaseDueDate array according to rddd
384
+ if rddd == 1:
385
+ ReleaseDueDate = np.zeros((numJobs, 2), dtype=int)
386
+ elif rddd == 2:
387
+ ReleaseDueDate = np.zeros((numJobs, numMchs, 2), dtype=int)
388
+ else:
389
+ # No dates: devolver array vacío para que __init__ detecte rddd=0
390
+ ReleaseDueDate = np.array([])
391
+
392
+ # -------------------------
393
+ # Load jobs & operations
394
+ # -------------------------
395
+ for job_data in data["timeEnergy"]:
396
+ job = int(job_data["jobId"])
397
+
398
+ # Optional job-level release/due dates
399
+ if rddd == 1:
400
+ ReleaseDueDate[job, 0] = int(job_data["release-date"])
401
+ ReleaseDueDate[job, 1] = int(job_data["due-date"])
402
+
403
+ for machine_str, op_data in job_data["operations"].items():
404
+ machine = int(machine_str)
405
+ Orden_list[job].append(machine)
406
+
407
+ # Load speed-scaling arrays
408
+ proc_times = [int(entry["procTime"]) for entry in op_data["speed-scaling"]]
409
+ energies = [int(entry["energyCons"]) for entry in op_data["speed-scaling"]]
410
+
411
+ # Aseguramos longitud speed
412
+ # Si speed > len(proc_times) -> rellenamos con ceros (o ajustar según tu política)
413
+ proc_arr = np.zeros((speed,), dtype=int)
414
+ en_arr = np.zeros((speed,), dtype=int)
415
+ L = min(len(proc_times), speed)
416
+ proc_arr[:L] = proc_times[:L]
417
+ en_arr[:L] = energies[:L]
418
+
419
+ ProcessingTime[job, machine, :] = proc_arr
420
+ EnergyConsumption[job, machine, :] = en_arr
421
+
422
+ if rddd == 2:
423
+ ReleaseDueDate[job, machine, 0] = int(op_data["release-date"])
424
+ ReleaseDueDate[job, machine, 1] = int(op_data["due-date"])
425
+
426
+ # Convertir Orden a ndarray (shape = numJobs x numMchs)
427
+ Orden = np.zeros((numJobs, numMchs), dtype=int)
428
+ for j in range(numJobs):
429
+ if len(Orden_list[j]) != numMchs:
430
+ # Si por algun motivo no tiene todas las máquinas,
431
+ # rellenamos con -1 o lanzamos error; aquí uso -1.
432
+ row = Orden_list[j] + [-1] * (numMchs - len(Orden_list[j]))
433
+ else:
434
+ row = Orden_list[j]
435
+ Orden[j, :] = np.array(row, dtype=int)
436
+
437
+ sol = {
438
+ 'jobs': numJobs,
439
+ 'machines': numMchs,
440
+ 'ProcessingTime': ProcessingTime,
441
+ 'EnergyConsumption': EnergyConsumption,
442
+ 'ReleaseDateDueDate': ReleaseDueDate, # <-- ahora es array, no int
443
+ 'Orden': Orden
444
+ }
445
+ return JSP(**sol)
122
446
 
123
447
  def saveJsonFile(self, path):
124
448
  self.JSP = {
@@ -141,13 +465,13 @@ class JSP:
141
465
  for machine in self.Orden[job]:
142
466
  machine = int(machine)
143
467
  new["operations"][machine] = {"speed-scaling" :
144
- [
468
+ [
145
469
  {"procTime" : int(proc),
146
- "energyCons" : int(energy)
470
+ "energyCons" : int(energy)
147
471
  }
148
472
  for proc, energy in zip(self.ProcessingTime[job, machine],self.EnergyConsumption[job, machine])
149
- ]
150
- }
473
+ ]
474
+ }
151
475
  if self.rddd == 2:
152
476
  new["operations"][machine]["release-date"] = int(self.ReleaseDueDate[job][machine][0])
153
477
  new["operations"][machine]["due-date"] = int(self.ReleaseDueDate[job][machine][1])
@@ -183,6 +507,11 @@ class JSP:
183
507
 
184
508
  f.write(json_str)
185
509
 
510
+ def savePythonFile(self, path):
511
+ os.makedirs(os.path.dirname(path), exist_ok=True)
512
+ with open(path, 'wb') as f:
513
+ pickle.dump(self, f)
514
+
186
515
  def saveDznFile(self, InputDir, OutputDir):
187
516
  indexProblema = OutputDir.split("/")[-2]
188
517
  OutputDir = "/".join(OutputDir.split("/")[:-2])
@@ -202,35 +531,30 @@ class JSP:
202
531
  "machines": data.numMchs,
203
532
  "jobs": data.numJobs,
204
533
  "Speed": s,
205
- "time": time.flatten(),
206
- "energy": energy.flatten(),
534
+ "time": str(time.flatten()).replace(" ", ", "),
535
+ "energy": str(energy.flatten()).replace(" ", ", ")
207
536
  }
208
537
 
209
538
  if t == 1:
210
539
  replace_data["releaseDate"] = str([int(data.ReleaseDueDate[job, 0]) for job in range(data.numJobs)]).replace(", ", " ")
211
540
  replace_data["dueDate"] = str([int(data.ReleaseDueDate[job, 1]) for job in range(data.numJobs)]).replace(", ", " ")
212
541
  elif t == 2:
213
- replace_data["releaseDate"] = data.ReleaseDueDate[:, :, 0].flatten()
214
- replace_data["dueDate"] = data.ReleaseDueDate[:, :, 1].flatten()
542
+ replace_data["releaseDate"] = str(data.ReleaseDueDate[:, :, 0].flatten()).replace(", ", " ")
543
+ replace_data["dueDate"] = str(data.ReleaseDueDate[:, :, 1].flatten()).replace(", ", " ")
215
544
 
216
545
  for job in range(data.numJobs):
217
546
  for i, prioridad in enumerate(range(data.numMchs)):
218
547
  precedence[job, data.Orden[job, prioridad]] = i
219
548
 
220
- replace_data["precedence"] = precedence.flatten()
549
+ replace_data["precedence"] = str(precedence.flatten()).replace(" ", ",")
221
550
 
222
551
  with open(f"./Minizinc/Types/RD/JSP/type{t}.dzn", "r", encoding="utf-8") as file:
223
552
  filedata = file.read()
224
553
  for k, v in replace_data.items():
225
554
  filedata = filedata.replace("{" + k + "}", str(v))
226
-
227
555
  os.makedirs(f"{OutputDir}/", exist_ok=True)
228
-
229
556
  with open(f"{OutputDir}/{indexProblema}-{t}-{s}.dzn", "w+", encoding="utf-8") as new:
230
557
  new.write(filedata)
231
- # print(f"{OutputDir}/{indexProblema}")
232
- # with open(f"{OutputDir}/{indexProblema}", "wb") as new:
233
- # pickle.dump(new_object, new)
234
558
 
235
559
  def saveTaillardStandardFile(self, path):
236
560
  os.makedirs("/".join(path.split("/")[:-1]),exist_ok=True)
@@ -460,11 +784,6 @@ class JSP:
460
784
  if operation > 0 and operation < self.numMchs - 1:
461
785
  graph.add_edge((job * self.numMchs + operation - 1, job * self.numMchs + operation))
462
786
 
463
- # if __name__ == "__main__":
464
- # jsp = JSP(jobs=5, machines=5)
465
- # jsp.fill_random_values(speed=3, rddd=2, distribution="uniform", seed=1234)
466
- # jsp.saveTaillardStandardFile("./output_taillard.txt")
467
-
468
787
 
469
788
 
470
789
  #################################################################################
@@ -585,16 +904,16 @@ class FJSP(JSP):
585
904
  "energy": str(energy.flatten()).replace(" ", ", ")
586
905
  }
587
906
  if t == 1:
588
- replace_data["releaseDate"] = str([int(data.ReleaseDueDate[job, 0]) for job in range(data.numJobs)])
589
- replace_data["dueDate"] = str([int(data.ReleaseDueDate[job, 1]) for job in range(data.numJobs)])
907
+ replace_data["releaseDate"] = str([int(data.ReleaseDueDate[job, 0]) for job in range(data.numJobs)]).replace(" ", ",")
908
+ replace_data["dueDate"] = str([int(data.ReleaseDueDate[job, 1]) for job in range(data.numJobs)]).replace(" ", ",")
590
909
  elif t == 2:
591
- replace_data["releaseDate"] = data.ReleaseDueDate[:, :, 0].flatten()
592
- replace_data["dueDate"] = data.ReleaseDueDate[:, :, 1].flatten()
910
+ replace_data["releaseDate"] = str(data.ReleaseDueDate[:, :, 0].flatten()).replace(" ", ",")
911
+ replace_data["dueDate"] = str(data.ReleaseDueDate[:, :, 1].flatten()).replace(" ", ",")
593
912
 
594
913
  for job in range(data.numJobs):
595
914
  for i, prioridad in enumerate(range(data.numMchs)):
596
915
  precedence[job, data.Orden[job, prioridad]] = i
597
- replace_data["precedence"] = str(precedence.flatten()).replace(" ", ", ")
916
+ replace_data["precedence"] = str(precedence.flatten()).replace(" ", ",")
598
917
 
599
918
  replace_data["available_machines"] = str(data.available_machines.flatten()).replace(" ", ", ")
600
919
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: IGJSP
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: Instance generator for JSP
5
5
  Project-URL: Homepage, https://gps.blogs.upv.es/
6
6
  Author-email: GPS-UPV <gps@dsic.upv.es>
@@ -1,4 +1,4 @@
1
- IGJSP/generador.py,sha256=AuiRTZ-tfRVt8U0-EECznq85NtDZsLyZLCAIjM1U8-8,34124
1
+ IGJSP/generador.py,sha256=nSPgsVbpR5YydbrkIOhkGmXbFCnkaIAkRu-ok-_BJpY,47603
2
2
  IGJSP/main.py,sha256=Sia5Ss8O3HWBdshvPLJKUMaZIoQPHy6x8yzvojojPFo,2838
3
3
  IGJSP/Minizinc/Models/RD/JSP0.mzn,sha256=cfN_E3RQ6nBulGfaOOYTd-zAgA5SI6E2saDlYtKCflg,2282
4
4
  IGJSP/Minizinc/Models/RD/JSP1.mzn,sha256=5B8cyw2WyKR8yEL1fFd0TaCAVhjPoxEJRJDPPEjJGEk,2840
@@ -12,7 +12,7 @@ IGJSP/Minizinc/Types/RD/FJSP/type2.dzn,sha256=Wz1MnkSL5GUPsbh1eq0leoaQRImkNqQqkX
12
12
  IGJSP/Minizinc/Types/RD/JSP/type0.dzn,sha256=wNuPQkXBXPSpPaPz2WFhp4pGDgfSimtg4I93UfwC01Q,263
13
13
  IGJSP/Minizinc/Types/RD/JSP/type1.dzn,sha256=Xbt9StzCgEqqh_HS9tWGrTVtu-OEnf5Yq5Ty91AkzoM,333
14
14
  IGJSP/Minizinc/Types/RD/JSP/type2.dzn,sha256=L2nc7bPJEhyuaEwgw0ZCpC52CpVJILQU_WQdKn8GUZs,379
15
- igjsp-1.0.0.dist-info/METADATA,sha256=zU57DaxL2XUGadX8d5Bxlyfo8uUxgD1X_f43gKDMSns,10609
16
- igjsp-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
17
- igjsp-1.0.0.dist-info/licenses/LICENSE,sha256=f7RDRO-z_nMoooAya7NAb8sXtrHR6WnttYtyUc9fB-c,1116
18
- igjsp-1.0.0.dist-info/RECORD,,
15
+ igjsp-1.1.0.dist-info/METADATA,sha256=q-mdPgQhQCvXKTtYbNMsDzgvR-oov4pe27Js_9Oeazo,10609
16
+ igjsp-1.1.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
+ igjsp-1.1.0.dist-info/licenses/LICENSE,sha256=f7RDRO-z_nMoooAya7NAb8sXtrHR6WnttYtyUc9fB-c,1116
18
+ igjsp-1.1.0.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any