walopy 0.2.6__tar.gz → 0.2.7__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 (40) hide show
  1. {walopy-0.2.6/src/walopy.egg-info → walopy-0.2.7}/PKG-INFO +158 -24
  2. {walopy-0.2.6 → walopy-0.2.7}/README.md +157 -23
  3. {walopy-0.2.6 → walopy-0.2.7}/pyproject.toml +1 -1
  4. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/__init__.py +8 -0
  5. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/inventory.py +384 -0
  6. {walopy-0.2.6 → walopy-0.2.7/src/walopy.egg-info}/PKG-INFO +158 -24
  7. {walopy-0.2.6 → walopy-0.2.7}/src/walopy.egg-info/SOURCES.txt +2 -0
  8. walopy-0.2.7/tests/test_exchange_curve.py +147 -0
  9. walopy-0.2.7/tests/test_safety_stock_curve.py +172 -0
  10. {walopy-0.2.6 → walopy-0.2.7}/LICENSE +0 -0
  11. {walopy-0.2.6 → walopy-0.2.7}/setup.cfg +0 -0
  12. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/__main__.py +0 -0
  13. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/_utils.py +0 -0
  14. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/advanced.py +0 -0
  15. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/bottleneck.py +0 -0
  16. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/fitting.py +0 -0
  17. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/kpi.py +0 -0
  18. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/network.py +0 -0
  19. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/operations.py +0 -0
  20. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/plotting.py +0 -0
  21. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/queuing.py +0 -0
  22. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/reliability.py +0 -0
  23. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/scheduling.py +0 -0
  24. {walopy-0.2.6 → walopy-0.2.7}/src/walopy/solver.py +0 -0
  25. {walopy-0.2.6 → walopy-0.2.7}/src/walopy.egg-info/dependency_links.txt +0 -0
  26. {walopy-0.2.6 → walopy-0.2.7}/src/walopy.egg-info/requires.txt +0 -0
  27. {walopy-0.2.6 → walopy-0.2.7}/src/walopy.egg-info/top_level.txt +0 -0
  28. {walopy-0.2.6 → walopy-0.2.7}/tests/test_advanced.py +0 -0
  29. {walopy-0.2.6 → walopy-0.2.7}/tests/test_bottleneck.py +0 -0
  30. {walopy-0.2.6 → walopy-0.2.7}/tests/test_fitting.py +0 -0
  31. {walopy-0.2.6 → walopy-0.2.7}/tests/test_inventory.py +0 -0
  32. {walopy-0.2.6 → walopy-0.2.7}/tests/test_inventory_new.py +0 -0
  33. {walopy-0.2.6 → walopy-0.2.7}/tests/test_kpi.py +0 -0
  34. {walopy-0.2.6 → walopy-0.2.7}/tests/test_mg1.py +0 -0
  35. {walopy-0.2.6 → walopy-0.2.7}/tests/test_network.py +0 -0
  36. {walopy-0.2.6 → walopy-0.2.7}/tests/test_new_models.py +0 -0
  37. {walopy-0.2.6 → walopy-0.2.7}/tests/test_operations.py +0 -0
  38. {walopy-0.2.6 → walopy-0.2.7}/tests/test_queuing.py +0 -0
  39. {walopy-0.2.6 → walopy-0.2.7}/tests/test_solver.py +0 -0
  40. {walopy-0.2.6 → walopy-0.2.7}/tests/test_v026.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: walopy
3
- Version: 0.2.6
3
+ Version: 0.2.7
4
4
  Summary: Queuing theory, operations analysis, OEE, bottleneck analysis and KPI trees for Python
5
5
  Author-email: LeoSanta15 <angelsanta1@gmail.com>
6
6
  License-Expression: MIT
@@ -63,17 +63,18 @@ pip install walopy
63
63
  9. [Análisis de cuellos de botella](#9-análisis-de-cuellos-de-botella)
64
64
  10. [Balance de línea y tiempo takt](#10-balance-de-línea-y-tiempo-takt)
65
65
  11. [Análisis de punto de equilibrio](#11-análisis-de-punto-de-equilibrio)
66
- 12. [Programación de producción (scheduling)](#12-programación-de-producción-scheduling)
67
- 13. [Confiabilidad](#13-confiabilidad--reliability)
68
- 14. [Árboles de KPI](#14-árboles-de-kpi)
69
- 15. [Solvers y optimización](#15-solvers-y-optimización)
70
- 16. [Análisis de sensibilidad](#16-análisis-de-sensibilidad)
71
- 17. [Escenarios en lote (batch_model)](#17-escenarios-en-lote-batch_model)
72
- 18. [Comparar múltiples resultados](#18-comparar-múltiples-resultados)
73
- 19. [Gráficas](#19-gráficas)
74
- 20. [CLI](#20-cli)
75
- 21. [Referencia de módulos](#21-referencia-de-módulos)
76
- 22. [Requisitos](#22-requisitos)
66
+ 12. [Curvas de intercambio](#12-curvas-de-intercambio)
67
+ 13. [Programación de producción (scheduling)](#13-programación-de-producción-scheduling)
68
+ 14. [Confiabilidad](#14-confiabilidad--reliability)
69
+ 15. [Árboles de KPI](#15-árboles-de-kpi)
70
+ 16. [Solvers y optimización](#16-solvers-y-optimización)
71
+ 17. [Análisis de sensibilidad](#17-análisis-de-sensibilidad)
72
+ 18. [Escenarios en lote (batch_model)](#18-escenarios-en-lote-batch_model)
73
+ 19. [Comparar múltiples resultados](#19-comparar-múltiples-resultados)
74
+ 20. [Gráficas](#20-gráficas)
75
+ 21. [CLI](#21-cli)
76
+ 22. [Referencia de módulos](#22-referencia-de-módulos)
77
+ 23. [Requisitos](#23-requisitos)
77
78
 
78
79
  ---
79
80
 
@@ -1057,7 +1058,7 @@ print(r.margin_of_safety_pct) # 0.375 — porcentaje del margen de seguridad
1057
1058
 
1058
1059
  ---
1059
1060
 
1060
- ## 14. Árboles de KPI
1061
+ ## 15. Árboles de KPI
1061
1062
 
1062
1063
  Los árboles de KPI crean jerarquías interactivas visualizadas como **treemap** o **sunburst** con Plotly.
1063
1064
 
@@ -1110,7 +1111,7 @@ fig.show()
1110
1111
 
1111
1112
  ---
1112
1113
 
1113
- ## 15. Solvers y optimización
1114
+ ## 16. Solvers y optimización
1114
1115
 
1115
1116
  ### `solve_lam(metric, target, *, mu, model)` — Máxima tasa de llegada
1116
1117
 
@@ -1157,7 +1158,7 @@ r.plot() # gráfica de costo vs número de servidores
1157
1158
 
1158
1159
  ---
1159
1160
 
1160
- ## 16. Análisis de sensibilidad
1161
+ ## 17. Análisis de sensibilidad
1161
1162
 
1162
1163
  ### `sensitivity(model_fn, param, values, **fixed_kwargs)` — Barrido paramétrico
1163
1164
 
@@ -1188,7 +1189,7 @@ fig.show()
1188
1189
 
1189
1190
  ---
1190
1191
 
1191
- ## 17. Escenarios en lote (batch_model)
1192
+ ## 18. Escenarios en lote (batch_model)
1192
1193
 
1193
1194
  ### `batch_model(model_fn, df, **fixed_kwargs)` — Aplicar modelo a un DataFrame
1194
1195
 
@@ -1223,7 +1224,7 @@ print(resultado[resultado["_error"].notna()]) # filas fallidas
1223
1224
 
1224
1225
  ---
1225
1226
 
1226
- ## 18. Comparar múltiples resultados
1227
+ ## 19. Comparar múltiples resultados
1227
1228
 
1228
1229
  ### `compare(*results, labels)` — DataFrame comparativo
1229
1230
 
@@ -1248,7 +1249,7 @@ df2 = wl.compare(wl.mm1(1, 5), wl.mm1(2, 5), wl.mm1(3, 5))
1248
1249
 
1249
1250
  ---
1250
1251
 
1251
- ## 19. Gráficas
1252
+ ## 20. Gráficas
1252
1253
 
1253
1254
  Todos los resultados exponen `.plot()` que devuelve una figura de Matplotlib o Plotly. También se pueden llamar directamente desde `walopy.plotting`.
1254
1255
 
@@ -1284,7 +1285,7 @@ fig.show()
1284
1285
 
1285
1286
  ---
1286
1287
 
1287
- ## 20. CLI
1288
+ ## 21. CLI
1288
1289
 
1289
1290
  walopy incluye una interfaz de línea de comandos para uso rápido sin escribir código.
1290
1291
 
@@ -1326,7 +1327,140 @@ Wq : 0.3 (avg wait time in queue)
1326
1327
 
1327
1328
  ---
1328
1329
 
1329
- ## 12. Programación de producción (scheduling)
1330
+ ## 12. Curvas de intercambio
1331
+
1332
+ Herramientas para tomar decisiones de política agregada en familias de artículos.
1333
+
1334
+ ### Tipo 1 — ciclo: `exchange_curve`
1335
+
1336
+ Traza la hipérbola entre número de pedidos por año (N) e inversión promedio en inventario de ciclo (I):
1337
+
1338
+ ```
1339
+ N(k) = N* / k I(k) = k · I* → N · I = N* · I* (constante)
1340
+ ```
1341
+
1342
+ Dado un target de N o de I, resuelve el multiplicador k óptimo y recalcula Q para cada artículo.
1343
+
1344
+ ```python
1345
+ import walopy as wl
1346
+
1347
+ items = [
1348
+ {"name": "A", "demand": 1000, "ordering_cost": 50, "holding_cost": 2, "unit_value": 10},
1349
+ {"name": "B", "demand": 500, "ordering_cost": 30, "holding_cost": 1, "unit_value": 5},
1350
+ {"name": "C", "demand": 2000, "ordering_cost": 100, "holding_cost": 4, "unit_value": 8},
1351
+ ]
1352
+
1353
+ # Sin target → punto EOQ (k = 1)
1354
+ r = wl.exchange_curve(items)
1355
+ print(r)
1356
+ ```
1357
+
1358
+ ```
1359
+ Target : eoq
1360
+ Multiplier k : 1.0000
1361
+ N orders/yr (EOQ) : 87.27
1362
+ N orders/yr (optimal) : 87.27
1363
+ Investment (EOQ) : 5430
1364
+ Investment (optimal) : 5430
1365
+ ```
1366
+
1367
+ ```python
1368
+ # Reducir a 20 pedidos/año (k > 1 → Q más grandes → más inversión)
1369
+ r = wl.exchange_curve(items, target_orders=20)
1370
+
1371
+ # Limitar inversión a 3 000 (k < 1 → Q más pequeños → más pedidos)
1372
+ r = wl.exchange_curve(items, target_investment=3000)
1373
+
1374
+ # Tabla por artículo
1375
+ df = r.to_frame()
1376
+ # columnas: name | Q_eoq | Q_optimal | n_orders | investment
1377
+
1378
+ # Hipérbola para graficar
1379
+ curve = r.curve_to_frame()
1380
+ # columnas: N | I
1381
+ ```
1382
+
1383
+ **Parámetros de cada artículo**
1384
+
1385
+ | Clave | Requerido | Descripción |
1386
+ |-------|-----------|-------------|
1387
+ | `demand` | ✓ | Demanda anual Di |
1388
+ | `ordering_cost` | ✓ | Costo de pedido Ki |
1389
+ | `holding_cost` | ✓ | Costo de mantenimiento hi |
1390
+ | `unit_value` | — | Valor unitario vi (default 1) |
1391
+ | `name` | — | Etiqueta (default I1, I2, …) |
1392
+
1393
+ ---
1394
+
1395
+ ### Tipo 2 — seguridad: `safety_stock_curve`
1396
+
1397
+ Traza la curva entre inversión en stock de seguridad e nivel de servicio por ciclo usando una **política z común** para toda la familia:
1398
+
1399
+ ```
1400
+ SS_i(z) = z · σᵢ_DLT · vᵢ σᵢ_DLT = √(Lᵢ·σ²_Di + D²ᵢ·σ²_Li)
1401
+ SS_total(z) = z · Σ σᵢ_DLT · vᵢ SL(z) = Φ(z)
1402
+ ```
1403
+
1404
+ ```python
1405
+ items = [
1406
+ {"name": "A", "demand_rate": 100, "demand_std": 10, "lead_time": 2, "unit_value": 10},
1407
+ {"name": "B", "demand_rate": 50, "demand_std": 5, "lead_time": 1, "unit_value": 5},
1408
+ {"name": "C", "demand_rate": 200, "demand_std": 20, "lead_time": 3, "unit_value": 8},
1409
+ ]
1410
+
1411
+ # Target: nivel de servicio 95%
1412
+ r = wl.safety_stock_curve(items, target_service_level=0.95)
1413
+ print(r)
1414
+ ```
1415
+
1416
+ ```
1417
+ Target : service_level=95.0000%
1418
+ z : 1.6449
1419
+ Service level : 95.0000%
1420
+ SS investment : 1 847
1421
+ ```
1422
+
1423
+ ```python
1424
+ # Target: presupuesto máximo de SS
1425
+ r = wl.safety_stock_curve(items, target_ss_investment=1000)
1426
+
1427
+ # Tabla por artículo (incluye punto de reorden cuando se da demand_rate)
1428
+ df = r.to_frame()
1429
+ # columnas: name | sigma_dlt | safety_stock | investment | reorder_point
1430
+
1431
+ # Curva completa (z de −2 a 4)
1432
+ curve = r.curve_to_frame()
1433
+ # columnas: z | service_level | ss_investment
1434
+ ```
1435
+
1436
+ **Parámetros de cada artículo**
1437
+
1438
+ | Clave | Requerido | Descripción |
1439
+ |-------|-----------|-------------|
1440
+ | `demand_std` | ✓ | Desviación estándar de la demanda |
1441
+ | `lead_time` | ✓ | Lead time medio |
1442
+ | `demand_rate` | — | Demanda media; activa columna `reorder_point` |
1443
+ | `lead_time_std` | — | Desviación estándar del lead time (default 0) |
1444
+ | `unit_value` | — | Valor unitario (default 1) |
1445
+ | `name` | — | Etiqueta (default I1, I2, …) |
1446
+
1447
+ ---
1448
+
1449
+ ### Relación entre ambas curvas
1450
+
1451
+ | | Tipo 1 (ciclo) | Tipo 2 (seguridad) |
1452
+ |---|---|---|
1453
+ | **Función** | `exchange_curve` | `safety_stock_curve` |
1454
+ | **Decide** | Tamaño de lote Q | Punto de reorden r |
1455
+ | **Tradeoff** | N pedidos/año ↔ Inversión ciclo | Nivel de servicio ↔ SS |
1456
+ | **Resultado** | `ExchangeCurveResult` | `SafetyStockCurveResult` |
1457
+ | **Parámetro** | Multiplicador k | Factor z |
1458
+
1459
+ Usadas juntas cubren la política completa `(Q, r)` para toda la familia.
1460
+
1461
+ ---
1462
+
1463
+ ## 13. Programación de producción (scheduling)
1330
1464
 
1331
1465
  ### Máquina única — `schedule_single`
1332
1466
 
@@ -1409,7 +1543,7 @@ df = r.to_frame()
1409
1543
 
1410
1544
  ---
1411
1545
 
1412
- ## 13. Confiabilidad — `reliability`
1546
+ ## 14. Confiabilidad — `reliability`
1413
1547
 
1414
1548
  Modelos de confiabilidad con distribución exponencial (tasa de falla constante).
1415
1549
 
@@ -1485,14 +1619,14 @@ r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
1485
1619
 
1486
1620
  ---
1487
1621
 
1488
- ## 21. Referencia de módulos
1622
+ ## 22. Referencia de módulos
1489
1623
 
1490
1624
  | Módulo | Funciones y clases principales |
1491
1625
  |---|---|
1492
1626
  | `queuing` | `mm1`, `mmc`, `md1`, `kingman`, `mg1`, `littles_law`, `QueueResult`, `cv2_normal`, `cv2_triangular`, `cv2_uniform`, `cv2_erlang`, `cv2_gamma`, `cv2_lognormal`, `cv2_weibull` |
1493
1627
  | `advanced` | `mm1k`, `mmck`, `erlang_b`, `mm1_priority`, `monte_carlo_gg1`, `takt_time`, `line_balance`, `break_even`, `break_even_multi`, `break_even_sales`, `queue_length_pmf`, `sojourn_cdf`, `PriorityQueueResult`, `SimulationResult`, `LineBalanceResult`, `BreakEvenResult`, `BreakEvenMultiResult` |
1494
1628
  | `fitting` | `fit_from_data`, `FitResult` |
1495
- | `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult` |
1629
+ | `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `exchange_curve`, `safety_stock_curve`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult`, `ExchangeCurveResult`, `SafetyStockCurveResult` |
1496
1630
  | `scheduling` | `schedule_single`, `johnson_flowshop`, `ScheduleResult`, `JobSchedule`, `FlowShopResult` |
1497
1631
  | `reliability` | `mtbf_analysis`, `series_system`, `parallel_system`, `koon_system`, `ReliabilityResult` |
1498
1632
  | `network` | `jackson_network`, `JacksonResult`, `StationMetrics` |
@@ -1504,7 +1638,7 @@ r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
1504
1638
 
1505
1639
  ---
1506
1640
 
1507
- ## 22. Requisitos
1641
+ ## 23. Requisitos
1508
1642
 
1509
1643
  ```
1510
1644
  Python ≥ 3.9
@@ -26,17 +26,18 @@ pip install walopy
26
26
  9. [Análisis de cuellos de botella](#9-análisis-de-cuellos-de-botella)
27
27
  10. [Balance de línea y tiempo takt](#10-balance-de-línea-y-tiempo-takt)
28
28
  11. [Análisis de punto de equilibrio](#11-análisis-de-punto-de-equilibrio)
29
- 12. [Programación de producción (scheduling)](#12-programación-de-producción-scheduling)
30
- 13. [Confiabilidad](#13-confiabilidad--reliability)
31
- 14. [Árboles de KPI](#14-árboles-de-kpi)
32
- 15. [Solvers y optimización](#15-solvers-y-optimización)
33
- 16. [Análisis de sensibilidad](#16-análisis-de-sensibilidad)
34
- 17. [Escenarios en lote (batch_model)](#17-escenarios-en-lote-batch_model)
35
- 18. [Comparar múltiples resultados](#18-comparar-múltiples-resultados)
36
- 19. [Gráficas](#19-gráficas)
37
- 20. [CLI](#20-cli)
38
- 21. [Referencia de módulos](#21-referencia-de-módulos)
39
- 22. [Requisitos](#22-requisitos)
29
+ 12. [Curvas de intercambio](#12-curvas-de-intercambio)
30
+ 13. [Programación de producción (scheduling)](#13-programación-de-producción-scheduling)
31
+ 14. [Confiabilidad](#14-confiabilidad--reliability)
32
+ 15. [Árboles de KPI](#15-árboles-de-kpi)
33
+ 16. [Solvers y optimización](#16-solvers-y-optimización)
34
+ 17. [Análisis de sensibilidad](#17-análisis-de-sensibilidad)
35
+ 18. [Escenarios en lote (batch_model)](#18-escenarios-en-lote-batch_model)
36
+ 19. [Comparar múltiples resultados](#19-comparar-múltiples-resultados)
37
+ 20. [Gráficas](#20-gráficas)
38
+ 21. [CLI](#21-cli)
39
+ 22. [Referencia de módulos](#22-referencia-de-módulos)
40
+ 23. [Requisitos](#23-requisitos)
40
41
 
41
42
  ---
42
43
 
@@ -1020,7 +1021,7 @@ print(r.margin_of_safety_pct) # 0.375 — porcentaje del margen de seguridad
1020
1021
 
1021
1022
  ---
1022
1023
 
1023
- ## 14. Árboles de KPI
1024
+ ## 15. Árboles de KPI
1024
1025
 
1025
1026
  Los árboles de KPI crean jerarquías interactivas visualizadas como **treemap** o **sunburst** con Plotly.
1026
1027
 
@@ -1073,7 +1074,7 @@ fig.show()
1073
1074
 
1074
1075
  ---
1075
1076
 
1076
- ## 15. Solvers y optimización
1077
+ ## 16. Solvers y optimización
1077
1078
 
1078
1079
  ### `solve_lam(metric, target, *, mu, model)` — Máxima tasa de llegada
1079
1080
 
@@ -1120,7 +1121,7 @@ r.plot() # gráfica de costo vs número de servidores
1120
1121
 
1121
1122
  ---
1122
1123
 
1123
- ## 16. Análisis de sensibilidad
1124
+ ## 17. Análisis de sensibilidad
1124
1125
 
1125
1126
  ### `sensitivity(model_fn, param, values, **fixed_kwargs)` — Barrido paramétrico
1126
1127
 
@@ -1151,7 +1152,7 @@ fig.show()
1151
1152
 
1152
1153
  ---
1153
1154
 
1154
- ## 17. Escenarios en lote (batch_model)
1155
+ ## 18. Escenarios en lote (batch_model)
1155
1156
 
1156
1157
  ### `batch_model(model_fn, df, **fixed_kwargs)` — Aplicar modelo a un DataFrame
1157
1158
 
@@ -1186,7 +1187,7 @@ print(resultado[resultado["_error"].notna()]) # filas fallidas
1186
1187
 
1187
1188
  ---
1188
1189
 
1189
- ## 18. Comparar múltiples resultados
1190
+ ## 19. Comparar múltiples resultados
1190
1191
 
1191
1192
  ### `compare(*results, labels)` — DataFrame comparativo
1192
1193
 
@@ -1211,7 +1212,7 @@ df2 = wl.compare(wl.mm1(1, 5), wl.mm1(2, 5), wl.mm1(3, 5))
1211
1212
 
1212
1213
  ---
1213
1214
 
1214
- ## 19. Gráficas
1215
+ ## 20. Gráficas
1215
1216
 
1216
1217
  Todos los resultados exponen `.plot()` que devuelve una figura de Matplotlib o Plotly. También se pueden llamar directamente desde `walopy.plotting`.
1217
1218
 
@@ -1247,7 +1248,7 @@ fig.show()
1247
1248
 
1248
1249
  ---
1249
1250
 
1250
- ## 20. CLI
1251
+ ## 21. CLI
1251
1252
 
1252
1253
  walopy incluye una interfaz de línea de comandos para uso rápido sin escribir código.
1253
1254
 
@@ -1289,7 +1290,140 @@ Wq : 0.3 (avg wait time in queue)
1289
1290
 
1290
1291
  ---
1291
1292
 
1292
- ## 12. Programación de producción (scheduling)
1293
+ ## 12. Curvas de intercambio
1294
+
1295
+ Herramientas para tomar decisiones de política agregada en familias de artículos.
1296
+
1297
+ ### Tipo 1 — ciclo: `exchange_curve`
1298
+
1299
+ Traza la hipérbola entre número de pedidos por año (N) e inversión promedio en inventario de ciclo (I):
1300
+
1301
+ ```
1302
+ N(k) = N* / k I(k) = k · I* → N · I = N* · I* (constante)
1303
+ ```
1304
+
1305
+ Dado un target de N o de I, resuelve el multiplicador k óptimo y recalcula Q para cada artículo.
1306
+
1307
+ ```python
1308
+ import walopy as wl
1309
+
1310
+ items = [
1311
+ {"name": "A", "demand": 1000, "ordering_cost": 50, "holding_cost": 2, "unit_value": 10},
1312
+ {"name": "B", "demand": 500, "ordering_cost": 30, "holding_cost": 1, "unit_value": 5},
1313
+ {"name": "C", "demand": 2000, "ordering_cost": 100, "holding_cost": 4, "unit_value": 8},
1314
+ ]
1315
+
1316
+ # Sin target → punto EOQ (k = 1)
1317
+ r = wl.exchange_curve(items)
1318
+ print(r)
1319
+ ```
1320
+
1321
+ ```
1322
+ Target : eoq
1323
+ Multiplier k : 1.0000
1324
+ N orders/yr (EOQ) : 87.27
1325
+ N orders/yr (optimal) : 87.27
1326
+ Investment (EOQ) : 5430
1327
+ Investment (optimal) : 5430
1328
+ ```
1329
+
1330
+ ```python
1331
+ # Reducir a 20 pedidos/año (k > 1 → Q más grandes → más inversión)
1332
+ r = wl.exchange_curve(items, target_orders=20)
1333
+
1334
+ # Limitar inversión a 3 000 (k < 1 → Q más pequeños → más pedidos)
1335
+ r = wl.exchange_curve(items, target_investment=3000)
1336
+
1337
+ # Tabla por artículo
1338
+ df = r.to_frame()
1339
+ # columnas: name | Q_eoq | Q_optimal | n_orders | investment
1340
+
1341
+ # Hipérbola para graficar
1342
+ curve = r.curve_to_frame()
1343
+ # columnas: N | I
1344
+ ```
1345
+
1346
+ **Parámetros de cada artículo**
1347
+
1348
+ | Clave | Requerido | Descripción |
1349
+ |-------|-----------|-------------|
1350
+ | `demand` | ✓ | Demanda anual Di |
1351
+ | `ordering_cost` | ✓ | Costo de pedido Ki |
1352
+ | `holding_cost` | ✓ | Costo de mantenimiento hi |
1353
+ | `unit_value` | — | Valor unitario vi (default 1) |
1354
+ | `name` | — | Etiqueta (default I1, I2, …) |
1355
+
1356
+ ---
1357
+
1358
+ ### Tipo 2 — seguridad: `safety_stock_curve`
1359
+
1360
+ Traza la curva entre inversión en stock de seguridad e nivel de servicio por ciclo usando una **política z común** para toda la familia:
1361
+
1362
+ ```
1363
+ SS_i(z) = z · σᵢ_DLT · vᵢ σᵢ_DLT = √(Lᵢ·σ²_Di + D²ᵢ·σ²_Li)
1364
+ SS_total(z) = z · Σ σᵢ_DLT · vᵢ SL(z) = Φ(z)
1365
+ ```
1366
+
1367
+ ```python
1368
+ items = [
1369
+ {"name": "A", "demand_rate": 100, "demand_std": 10, "lead_time": 2, "unit_value": 10},
1370
+ {"name": "B", "demand_rate": 50, "demand_std": 5, "lead_time": 1, "unit_value": 5},
1371
+ {"name": "C", "demand_rate": 200, "demand_std": 20, "lead_time": 3, "unit_value": 8},
1372
+ ]
1373
+
1374
+ # Target: nivel de servicio 95%
1375
+ r = wl.safety_stock_curve(items, target_service_level=0.95)
1376
+ print(r)
1377
+ ```
1378
+
1379
+ ```
1380
+ Target : service_level=95.0000%
1381
+ z : 1.6449
1382
+ Service level : 95.0000%
1383
+ SS investment : 1 847
1384
+ ```
1385
+
1386
+ ```python
1387
+ # Target: presupuesto máximo de SS
1388
+ r = wl.safety_stock_curve(items, target_ss_investment=1000)
1389
+
1390
+ # Tabla por artículo (incluye punto de reorden cuando se da demand_rate)
1391
+ df = r.to_frame()
1392
+ # columnas: name | sigma_dlt | safety_stock | investment | reorder_point
1393
+
1394
+ # Curva completa (z de −2 a 4)
1395
+ curve = r.curve_to_frame()
1396
+ # columnas: z | service_level | ss_investment
1397
+ ```
1398
+
1399
+ **Parámetros de cada artículo**
1400
+
1401
+ | Clave | Requerido | Descripción |
1402
+ |-------|-----------|-------------|
1403
+ | `demand_std` | ✓ | Desviación estándar de la demanda |
1404
+ | `lead_time` | ✓ | Lead time medio |
1405
+ | `demand_rate` | — | Demanda media; activa columna `reorder_point` |
1406
+ | `lead_time_std` | — | Desviación estándar del lead time (default 0) |
1407
+ | `unit_value` | — | Valor unitario (default 1) |
1408
+ | `name` | — | Etiqueta (default I1, I2, …) |
1409
+
1410
+ ---
1411
+
1412
+ ### Relación entre ambas curvas
1413
+
1414
+ | | Tipo 1 (ciclo) | Tipo 2 (seguridad) |
1415
+ |---|---|---|
1416
+ | **Función** | `exchange_curve` | `safety_stock_curve` |
1417
+ | **Decide** | Tamaño de lote Q | Punto de reorden r |
1418
+ | **Tradeoff** | N pedidos/año ↔ Inversión ciclo | Nivel de servicio ↔ SS |
1419
+ | **Resultado** | `ExchangeCurveResult` | `SafetyStockCurveResult` |
1420
+ | **Parámetro** | Multiplicador k | Factor z |
1421
+
1422
+ Usadas juntas cubren la política completa `(Q, r)` para toda la familia.
1423
+
1424
+ ---
1425
+
1426
+ ## 13. Programación de producción (scheduling)
1293
1427
 
1294
1428
  ### Máquina única — `schedule_single`
1295
1429
 
@@ -1372,7 +1506,7 @@ df = r.to_frame()
1372
1506
 
1373
1507
  ---
1374
1508
 
1375
- ## 13. Confiabilidad — `reliability`
1509
+ ## 14. Confiabilidad — `reliability`
1376
1510
 
1377
1511
  Modelos de confiabilidad con distribución exponencial (tasa de falla constante).
1378
1512
 
@@ -1448,14 +1582,14 @@ r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
1448
1582
 
1449
1583
  ---
1450
1584
 
1451
- ## 21. Referencia de módulos
1585
+ ## 22. Referencia de módulos
1452
1586
 
1453
1587
  | Módulo | Funciones y clases principales |
1454
1588
  |---|---|
1455
1589
  | `queuing` | `mm1`, `mmc`, `md1`, `kingman`, `mg1`, `littles_law`, `QueueResult`, `cv2_normal`, `cv2_triangular`, `cv2_uniform`, `cv2_erlang`, `cv2_gamma`, `cv2_lognormal`, `cv2_weibull` |
1456
1590
  | `advanced` | `mm1k`, `mmck`, `erlang_b`, `mm1_priority`, `monte_carlo_gg1`, `takt_time`, `line_balance`, `break_even`, `break_even_multi`, `break_even_sales`, `queue_length_pmf`, `sojourn_cdf`, `PriorityQueueResult`, `SimulationResult`, `LineBalanceResult`, `BreakEvenResult`, `BreakEvenMultiResult` |
1457
1591
  | `fitting` | `fit_from_data`, `FitResult` |
1458
- | `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult` |
1592
+ | `inventory` | `eoq`, `ebq`, `eoq_multi`, `ebq_multi`, `eoq_multi_constrained`, `lot_for_lot`, `silver_meal`, `eoq_quantity_discount`, `wagner_whitin`, `rq_policy`, `rs_policy`, `exchange_curve`, `safety_stock_curve`, `reorder_point`, `newsvendor`, `EOQResult`, `EBQResult`, `MultiItemResult`, `ConstrainedMultiEOQResult`, `LotSizingResult`, `QuantityDiscountResult`, `ReorderResult`, `NewsvendorResult`, `RQPolicyResult`, `RSPolicyResult`, `ExchangeCurveResult`, `SafetyStockCurveResult` |
1459
1593
  | `scheduling` | `schedule_single`, `johnson_flowshop`, `ScheduleResult`, `JobSchedule`, `FlowShopResult` |
1460
1594
  | `reliability` | `mtbf_analysis`, `series_system`, `parallel_system`, `koon_system`, `ReliabilityResult` |
1461
1595
  | `network` | `jackson_network`, `JacksonResult`, `StationMetrics` |
@@ -1467,7 +1601,7 @@ r.R(t=200) # evalúa R(t) en cualquier tiempo posterior
1467
1601
 
1468
1602
  ---
1469
1603
 
1470
- ## 22. Requisitos
1604
+ ## 23. Requisitos
1471
1605
 
1472
1606
  ```
1473
1607
  Python ≥ 3.9
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "walopy"
7
- version = "0.2.6"
7
+ version = "0.2.7"
8
8
  description = "Queuing theory, operations analysis, OEE, bottleneck analysis and KPI trees for Python"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
@@ -117,9 +117,13 @@ from .network import (
117
117
  from .inventory import (
118
118
  RQPolicyResult,
119
119
  RSPolicyResult,
120
+ ExchangeCurveResult,
121
+ SafetyStockCurveResult,
120
122
  rq_policy,
121
123
  rs_policy,
122
124
  wagner_whitin,
125
+ exchange_curve,
126
+ safety_stock_curve,
123
127
  )
124
128
 
125
129
  # Scheduling
@@ -235,9 +239,13 @@ __all__ = [
235
239
  # inventory additions
236
240
  "RQPolicyResult",
237
241
  "RSPolicyResult",
242
+ "ExchangeCurveResult",
243
+ "SafetyStockCurveResult",
238
244
  "rq_policy",
239
245
  "rs_policy",
240
246
  "wagner_whitin",
247
+ "exchange_curve",
248
+ "safety_stock_curve",
241
249
  # scheduling
242
250
  "JobSchedule",
243
251
  "ScheduleResult",