simera 0.3.6__tar.gz → 0.3.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.
- simera-0.3.7/PKG-INFO +12 -0
- simera-0.3.7/pyproject.toml +18 -0
- simera-0.3.7/pyproject.toml.orig +17 -0
- simera-0.3.7/src/simera/__init__.py +11 -0
- simera-0.3.6/PKG-INFO +0 -18
- simera-0.3.6/pyproject.toml +0 -19
- simera-0.3.6/setup.cfg +0 -4
- simera-0.3.6/simera/__init__.py +0 -9
- simera-0.3.6/simera.egg-info/PKG-INFO +0 -18
- simera-0.3.6/simera.egg-info/SOURCES.txt +0 -25
- simera-0.3.6/simera.egg-info/dependency_links.txt +0 -1
- simera-0.3.6/simera.egg-info/requires.txt +0 -6
- simera-0.3.6/simera.egg-info/top_level.txt +0 -1
- simera-0.3.6/simera_inputs/config/generate_configs.py +0 -81
- simera-0.3.6/simera_resources/ratesheets/10_ratesheet_creation_support/dhl_exp_leadtimes.py +0 -67
- simera-0.3.6/simera_resources/ratesheets/10_ratesheet_creation_support/dhl_exp_zones.py +0 -228
- simera-0.3.6/simera_resources/ratesheets/10_ratesheet_creation_support/dhl_express_rates.py +0 -98
- simera-0.3.6/tests/test_units.py +0 -560
- simera-0.3.6/tests/test_zipcode.py +0 -123
- {simera-0.3.6 → simera-0.3.7}/README.md +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/activity.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/calculator.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/config.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/cosmicforg.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/presets.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/ratesheet.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/scenario.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/scenario_item.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/units.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/utils.py +0 -0
- {simera-0.3.6 → simera-0.3.7/src}/simera/zipcode.py +0 -0
simera-0.3.7/PKG-INFO
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: simera
|
|
3
|
+
Version: 0.3.7
|
|
4
|
+
Summary: Add your description here
|
|
5
|
+
Author: Artur Blachnio
|
|
6
|
+
Author-email: Artur Blachnio <blachnio.artur@gmail.com>
|
|
7
|
+
Requires-Python: >=3.14
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
Simera
|
|
11
|
+
|
|
12
|
+
Simera is a Python package designed for running and managing supply chain simulation scenarios.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "simera"
|
|
3
|
+
version = "0.3.7"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.14"
|
|
7
|
+
dependencies = []
|
|
8
|
+
|
|
9
|
+
[[project.authors]]
|
|
10
|
+
name = "Artur Blachnio"
|
|
11
|
+
email = "blachnio.artur@gmail.com"
|
|
12
|
+
|
|
13
|
+
[project.scripts]
|
|
14
|
+
simera = "simera:main"
|
|
15
|
+
|
|
16
|
+
[build-system]
|
|
17
|
+
requires = ["uv_build>=0.12.19,<0.13.0"]
|
|
18
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "simera"
|
|
3
|
+
version = "0.3.7"
|
|
4
|
+
description = "Add your description here"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Artur Blachnio", email = "blachnio.artur@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.14"
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
[project.scripts]
|
|
13
|
+
simera = "simera:main"
|
|
14
|
+
|
|
15
|
+
[build-system]
|
|
16
|
+
requires = ["uv_build>=0.12.19,<0.13.0"]
|
|
17
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# __version__ = '0.3.6'
|
|
2
|
+
|
|
3
|
+
print('Hello from Simera')
|
|
4
|
+
|
|
5
|
+
# from simera.config import Config
|
|
6
|
+
# from simera.zipcode import ZipcodeManager
|
|
7
|
+
# from simera.ratesheet import Ratesheet, RatesheetManager
|
|
8
|
+
# from simera.activity import Activity
|
|
9
|
+
# from simera.calculator import CostCalculator
|
|
10
|
+
# from simera.units import UnitGenerator
|
|
11
|
+
# from simera.scenario import Scenario
|
simera-0.3.6/PKG-INFO
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: simera
|
|
3
|
-
Version: 0.3.6
|
|
4
|
-
Summary: Supply Chain Simulation Tool
|
|
5
|
-
Author-email: "Artur. Blachnio" <blachnio.artur@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Requires-Python: >=3.6
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: pandas<=2.3.3
|
|
10
|
-
Requires-Dist: openpyxl
|
|
11
|
-
Requires-Dist: pyxlsb
|
|
12
|
-
Requires-Dist: python-calamine
|
|
13
|
-
Requires-Dist: pyyaml
|
|
14
|
-
Requires-Dist: tqdm
|
|
15
|
-
|
|
16
|
-
Simera
|
|
17
|
-
|
|
18
|
-
Simera is a Python package designed for running and managing supply chain simulation scenarios.
|
simera-0.3.6/pyproject.toml
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
[build-system]
|
|
3
|
-
requires = ["setuptools", "wheel"]
|
|
4
|
-
build-backend = "setuptools.build_meta"
|
|
5
|
-
|
|
6
|
-
[tool.setuptools.packages.find]
|
|
7
|
-
include = ["simera"]
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
[project]
|
|
11
|
-
name = "simera"
|
|
12
|
-
version = "0.3.6"
|
|
13
|
-
description = "Supply Chain Simulation Tool"
|
|
14
|
-
authors = [{ name = "Artur. Blachnio", email = "blachnio.artur@gmail.com" }]
|
|
15
|
-
license = {text = "MIT"}
|
|
16
|
-
readme = "README.md"
|
|
17
|
-
requires-python = ">=3.6"
|
|
18
|
-
dependencies = ['pandas<=2.3.3', 'openpyxl', 'pyxlsb', 'python-calamine', 'pyyaml', 'tqdm']
|
|
19
|
-
|
simera-0.3.6/setup.cfg
DELETED
simera-0.3.6/simera/__init__.py
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
__version__ = '0.3.6'
|
|
2
|
-
|
|
3
|
-
from simera.config import Config
|
|
4
|
-
from simera.zipcode import ZipcodeManager
|
|
5
|
-
from simera.ratesheet import Ratesheet, RatesheetManager
|
|
6
|
-
from simera.activity import Activity
|
|
7
|
-
from simera.calculator import CostCalculator
|
|
8
|
-
from simera.units import UnitGenerator
|
|
9
|
-
from simera.scenario import Scenario
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: simera
|
|
3
|
-
Version: 0.3.6
|
|
4
|
-
Summary: Supply Chain Simulation Tool
|
|
5
|
-
Author-email: "Artur. Blachnio" <blachnio.artur@gmail.com>
|
|
6
|
-
License: MIT
|
|
7
|
-
Requires-Python: >=3.6
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Requires-Dist: pandas<=2.3.3
|
|
10
|
-
Requires-Dist: openpyxl
|
|
11
|
-
Requires-Dist: pyxlsb
|
|
12
|
-
Requires-Dist: python-calamine
|
|
13
|
-
Requires-Dist: pyyaml
|
|
14
|
-
Requires-Dist: tqdm
|
|
15
|
-
|
|
16
|
-
Simera
|
|
17
|
-
|
|
18
|
-
Simera is a Python package designed for running and managing supply chain simulation scenarios.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
README.md
|
|
2
|
-
pyproject.toml
|
|
3
|
-
simera/__init__.py
|
|
4
|
-
simera/activity.py
|
|
5
|
-
simera/calculator.py
|
|
6
|
-
simera/config.py
|
|
7
|
-
simera/cosmicforg.py
|
|
8
|
-
simera/presets.py
|
|
9
|
-
simera/ratesheet.py
|
|
10
|
-
simera/scenario.py
|
|
11
|
-
simera/scenario_item.py
|
|
12
|
-
simera/units.py
|
|
13
|
-
simera/utils.py
|
|
14
|
-
simera/zipcode.py
|
|
15
|
-
simera.egg-info/PKG-INFO
|
|
16
|
-
simera.egg-info/SOURCES.txt
|
|
17
|
-
simera.egg-info/dependency_links.txt
|
|
18
|
-
simera.egg-info/requires.txt
|
|
19
|
-
simera.egg-info/top_level.txt
|
|
20
|
-
simera_inputs/config/generate_configs.py
|
|
21
|
-
simera_resources/ratesheets/10_ratesheet_creation_support/dhl_exp_leadtimes.py
|
|
22
|
-
simera_resources/ratesheets/10_ratesheet_creation_support/dhl_exp_zones.py
|
|
23
|
-
simera_resources/ratesheets/10_ratesheet_creation_support/dhl_express_rates.py
|
|
24
|
-
tests/test_units.py
|
|
25
|
-
tests/test_zipcode.py
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
simera
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Generate initial inputs to configs. Mostly from Excel files
|
|
3
|
-
"""
|
|
4
|
-
import numpy as np
|
|
5
|
-
import pandas as pd
|
|
6
|
-
import yaml
|
|
7
|
-
|
|
8
|
-
from simera.config import Config
|
|
9
|
-
|
|
10
|
-
sc = Config()
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
def country_excel_to_yaml(excel_path, yaml_path):
|
|
14
|
-
"""
|
|
15
|
-
Converts an Excel file to a YAML file.
|
|
16
|
-
"""
|
|
17
|
-
# Read the Excel file
|
|
18
|
-
df = pd.read_excel(excel_path, dtype=str) # Keep everything as string for safety
|
|
19
|
-
df['latitude'] = pd.to_numeric(df['latitude'])
|
|
20
|
-
df['longitude'] = pd.to_numeric(df['longitude'])
|
|
21
|
-
|
|
22
|
-
# Replace NaN with None
|
|
23
|
-
df = df.replace({np.nan: None})
|
|
24
|
-
|
|
25
|
-
# Convert DataFrame into a dictionary with 'ctry' as the key
|
|
26
|
-
data_dict = {row["ctry"]: row.to_dict() for _, row in df.iterrows()}
|
|
27
|
-
|
|
28
|
-
# Remove 'ctry' from values since it's already the key
|
|
29
|
-
for key in data_dict:
|
|
30
|
-
data_dict[key].pop("ctry", None)
|
|
31
|
-
|
|
32
|
-
# Save as YAML
|
|
33
|
-
with open(yaml_path, "w", encoding="utf-8") as file:
|
|
34
|
-
yaml.dump(data_dict, file, default_flow_style=False, allow_unicode=True)
|
|
35
|
-
|
|
36
|
-
print(f"YAML file saved successfully at: {yaml_path}")
|
|
37
|
-
print(f"Remember to copy-paste that to simera/config.country.yaml (!)")
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
def currency_excel_to_yaml(excel_path, yaml_path):
|
|
41
|
-
"""
|
|
42
|
-
Converts an Excel file to a YAML file.
|
|
43
|
-
"""
|
|
44
|
-
# Read the Excel file
|
|
45
|
-
df = pd.read_excel(excel_path)
|
|
46
|
-
|
|
47
|
-
yaml_dict = {}
|
|
48
|
-
|
|
49
|
-
for _, row in df.iterrows():
|
|
50
|
-
base_currency = row['from']
|
|
51
|
-
target_currency = row['currency']
|
|
52
|
-
rate = row['rate']
|
|
53
|
-
|
|
54
|
-
if base_currency not in yaml_dict:
|
|
55
|
-
yaml_dict[base_currency] = {}
|
|
56
|
-
|
|
57
|
-
yaml_dict[base_currency][target_currency] = rate
|
|
58
|
-
|
|
59
|
-
# Save as YAML
|
|
60
|
-
with open(yaml_path, "w", encoding="utf-8") as file:
|
|
61
|
-
yaml.dump(yaml_dict, file, default_flow_style=False, allow_unicode=True)
|
|
62
|
-
|
|
63
|
-
print(f"YAML file saved successfully at: {yaml_path}")
|
|
64
|
-
print(f"Remember to copy-paste that to simera/config.country.yaml (!)")
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
def generate_config_country():
|
|
68
|
-
excel_path = sc.path.base_dir / 'simera_inputs/config/country.xlsx'
|
|
69
|
-
yaml_path = sc.path.base_dir / 'simera_inputs/config/country.yaml'
|
|
70
|
-
country_excel_to_yaml(excel_path, yaml_path)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def generate_config_currency():
|
|
74
|
-
excel_path = sc.path.base_dir / 'simera_inputs/config/currency.xlsx'
|
|
75
|
-
yaml_path = sc.path.base_dir / 'simera_inputs/config/currency.yaml'
|
|
76
|
-
currency_excel_to_yaml(excel_path, yaml_path)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if __name__ == '__main__':
|
|
80
|
-
generate_config_country()
|
|
81
|
-
generate_config_currency()
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import pandas as pd
|
|
2
|
-
|
|
3
|
-
from simera.zipcode import ZipcodeManager
|
|
4
|
-
zm = ZipcodeManager()
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
# df = pd.read_excel(r'.\simera_inputs\transport\input_2026\02_success\Signify PL_to_EU_non-IE_STD_TT_20251205_extract.xlsb')
|
|
8
|
-
# # rename all columns to lowercase with _ as separator
|
|
9
|
-
# df.columns = df.columns.str.lower().str.replace(' ', '_')
|
|
10
|
-
#
|
|
11
|
-
# df.rename(columns={'destination_country_code': 'ctry', 'destination_postcode': 'zipcode'}, inplace=True)
|
|
12
|
-
# df.rename(columns={'dd_final_tt': 'dd', 'td_final_tt_excl_customs': 'td_none', 'td_tt_incl_low_value_clearance': 'td_low', 'td_tt_incl_high_value_clearance': 'td_high'}, inplace=True)
|
|
13
|
-
#
|
|
14
|
-
# df.dd.value_counts(dropna=False)
|
|
15
|
-
# df.loc[df.dd == 'No DDI Lane', 'dd'] = '99'
|
|
16
|
-
# df.dd = pd.to_numeric(df.dd, errors='coerce')
|
|
17
|
-
# df.to_feather(r'.\simera_inputs\transport\input_2026\02_success\Signify PL_to_EU_non-IE_STD_TT_20251205_extract.feather')
|
|
18
|
-
|
|
19
|
-
# mask_1 = (df.td_none != df.td_low)
|
|
20
|
-
# mask_2 = (df.td_none != df.td_high)
|
|
21
|
-
# df[mask_1].shape
|
|
22
|
-
# df[mask_2].shape # 47k td_high is bigger than none or low
|
|
23
|
-
# df[mask_2].ctry.value_counts()
|
|
24
|
-
# ctry
|
|
25
|
-
# GB 43886
|
|
26
|
-
# NO 3383
|
|
27
|
-
|
|
28
|
-
df = pd.read_feather(r'.\simera_inputs\transport\input_2026\02_success\Signify PL_to_EU_non-IE_STD_TT_20251205_extract.feather')
|
|
29
|
-
|
|
30
|
-
# TD with low value clearance will be used (ask Georgios what the threshold is)
|
|
31
|
-
df['td'] = df.td_low
|
|
32
|
-
df.td = pd.to_numeric(df.td, errors='coerce')
|
|
33
|
-
df = df[['ctry', 'zipcode', 'dd', 'td']].copy()
|
|
34
|
-
|
|
35
|
-
df.sort_values(['ctry', 'zipcode', 'td', 'dd'], inplace=True)
|
|
36
|
-
df.drop_duplicates(['ctry', 'zipcode'], keep='first', inplace=True)
|
|
37
|
-
|
|
38
|
-
df.reset_index(drop=True, inplace=True)
|
|
39
|
-
|
|
40
|
-
td = df[['ctry', 'zipcode', 'td']].copy()
|
|
41
|
-
dd = df[['ctry', 'zipcode', 'dd']].copy()
|
|
42
|
-
|
|
43
|
-
td.rename(columns={'td': 'leadtime'}, inplace=True)
|
|
44
|
-
dd.rename(columns={'dd': 'leadtime'}, inplace=True)
|
|
45
|
-
|
|
46
|
-
# Select all rows where ctry or leadtime is different versus the preceding row
|
|
47
|
-
td['same_as_previous'] = ((td['leadtime'].ne(td['leadtime'].shift(1))) | (td['ctry'].ne(td['ctry'].shift(1))))
|
|
48
|
-
dd['same_as_previous'] = ((dd['leadtime'].ne(dd['leadtime'].shift(1))) | (dd['ctry'].ne(dd['ctry'].shift(1))))
|
|
49
|
-
|
|
50
|
-
td = td.loc[td.same_as_previous].copy()
|
|
51
|
-
dd = dd.loc[dd.same_as_previous].copy()
|
|
52
|
-
|
|
53
|
-
dd['zipcode_clean'] = dd.apply(lambda row: zm.clean_zipcode(row['ctry'], row['zipcode']), axis=1)
|
|
54
|
-
td['zipcode_clean'] = td.apply(lambda row: zm.clean_zipcode(row['ctry'], row['zipcode']), axis=1)
|
|
55
|
-
|
|
56
|
-
dd = dd[['ctry', 'zipcode_clean', 'same_as_previous', 'leadtime']].copy()
|
|
57
|
-
td = td[['ctry', 'zipcode_clean', 'same_as_previous', 'leadtime']].copy()
|
|
58
|
-
|
|
59
|
-
dd['same_as_previous'] = ''
|
|
60
|
-
td['same_as_previous'] = ''
|
|
61
|
-
|
|
62
|
-
dd.to_csv(r'.\simera_inputs\transport\input_2026\02_success\dhl_exp_leadtimes_dd.csv', index=False)
|
|
63
|
-
td.to_csv(r'.\simera_inputs\transport\input_2026\02_success\dhl_exp_leadtimes_td.csv', index=False)
|
|
64
|
-
|
|
65
|
-
dd.to_clipboard(index=False)
|
|
66
|
-
|
|
67
|
-
td.to_clipboard(index=False)
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
import re
|
|
2
|
-
|
|
3
|
-
import pandas as pd
|
|
4
|
-
import pyperclip
|
|
5
|
-
|
|
6
|
-
from simera.zipcode import ZipcodeManager
|
|
7
|
-
|
|
8
|
-
zones_input = """
|
|
9
|
-
Afghanistan (AF) 11 Ecuador (EC) 10 Liechtenstein (LI) 6 Sao Tome And Principe (ST) 11
|
|
10
|
-
Albania (AL) 9 Egypt (EG) 9 Lithuania (LT) 4 Saudi Arabia (SA) 9
|
|
11
|
-
Algeria (DZ) 11 El Salvador (SV) 10 Luxembourg (LU) 2 Senegal (SN) 11
|
|
12
|
-
American Samoa (AS) 11 Eritrea (ER) 11 Macau SAR China (MO) 8 Serbia, Rep. Of (RS) 9
|
|
13
|
-
Andorra (AD) 6 Estonia (EE) 4 Madagascar (MG) 11 Seychelles (SC) 11
|
|
14
|
-
Angola (AO) 11 Eswatini (SZ) 11 Malawi (MW) 11 Sierra Leone (SL) 11
|
|
15
|
-
Anguilla (AI) 10 Ethiopia (ET) 11 Malaysia (MY) 8 Singapore (SG) 8
|
|
16
|
-
Antigua (AG) 10 Falkland Islands (FK) 11 Maldives (MV) 10 Slovakia (SK) 4
|
|
17
|
-
Argentina (AR) 10 Faroe Islands (FO) 9 Mali (ML) 11 Slovenia (SI) 4
|
|
18
|
-
Armenia (AM) 9 Fiji (FJ) 10 Malta (MT) 4 Solomon Islands (SB) 10
|
|
19
|
-
Aruba (AW) 10 Finland (FI) 3 Marshall Islands (MH) 11 Somalia (SO) 11
|
|
20
|
-
Australia (AU) 8 France (FR) 2 Martinique (MQ) 10 Somaliland, Rep Of (XS) 11
|
|
21
|
-
Austria (AT) 3 French Guyana (GF) 10 Mauritania (MR) 11 South Africa (ZA) 9
|
|
22
|
-
Azerbaijan (AZ) 9 Gabon (GA) 11 Mauritius (MU) 11 South Sudan (SS) 11
|
|
23
|
-
Bahamas (BS) 10 Gambia (GM) 11 Mayotte (YT) 11 Spain (ES) 3
|
|
24
|
-
Bahrain (BH) 9 Georgia (GE) 9 Mexico (MX) 10 Sri Lanka (LK) 10
|
|
25
|
-
Bangladesh (BD) 10 Germany (DE) 2 Micronesia (FM) 11 St. Barthelemy (XY) 10
|
|
26
|
-
Barbados (BB) 10 Ghana (GH) 11 Moldova, Rep. Of (MD) 9 St. Eustatius (XE) 10
|
|
27
|
-
Belarus (BY) 9 Gibraltar (GI) 6 Monaco (MC) 2 St. Kitts (KN) 10
|
|
28
|
-
Belgium (BE) 2 Greece (GR) 3 Mongolia (MN) 10 St. Lucia (LC) 10
|
|
29
|
-
Belize (BZ) 10 Greenland (GL) 11 Montenegro, Rep Of (ME) 9 St. Maarten (XM) 10
|
|
30
|
-
Benin (BJ) 11 Grenada (GD) 10 Montserrat (MS) 10 St. Vincent (VC) 10
|
|
31
|
-
Bermuda (BM) 10 Guadeloupe (GP) 10 Morocco (MA) 11 Sudan (SD) 11
|
|
32
|
-
Bhutan (BT) 10 Guam (GU) 11 Mozambique (MZ) 11 Suriname (SR) 10
|
|
33
|
-
Bolivia (BO) 10 Guatemala (GT) 10 Myanmar (MM) 10 Sweden (SE) 3
|
|
34
|
-
Bonaire (XB) 10 Guernsey (GG) 6 Namibia (NA) 11 Switzerland (CH) 6
|
|
35
|
-
Bosnia & Herzegovina(BA) 9 Guinea Rep. (GN) 11 Nauru, Rep. Of (NR) 10 Syria (SY) 9
|
|
36
|
-
Botswana (BW) 11 Guinea-Bissau (GW) 11 Nepal (NP) 10 Tahiti (PF) 10
|
|
37
|
-
Brazil (BR) 10 Guinea-Equatorial (GQ) 11 Netherlands, The (NL) 2 Taiwan (TW) 8
|
|
38
|
-
Brunei (BN) 10 Guyana (British) (GY) 10 Nevis (XN) 10 Tajikistan (TJ) 10
|
|
39
|
-
Bulgaria (BG) 4 Haiti (HT) 10 New Caledonia (NC) 10 Tanzania (TZ) 11
|
|
40
|
-
Burkina Faso (BF) 11 Honduras (HN) 10 New Zealand (NZ) 8 Thailand (TH) 8
|
|
41
|
-
Burundi (BI) 11 Hong Kong SAR China (HK) 8 Nicaragua (NI) 10 Timor-Leste (TL) 10
|
|
42
|
-
Cambodia (KH) 10 Hungary (HU) 4 Niger (NE) 11 Togo (TG) 11
|
|
43
|
-
Cameroon (CM) 11 Iceland (IS) 6 Nigeria (NG) 11 Tonga (TO) 10
|
|
44
|
-
Canada (CA) 7 India (IN) 8 Niue (NU) 10 Trinidad And Tobago (TT) 10
|
|
45
|
-
Canary Islands, The (IC) 6 Indonesia (ID) 8 North Macedonia (MK) 9 Tunisia (TN) 11
|
|
46
|
-
Cape Verde (CV) 11 Iran (IR) 11 Northern Mariana Islands (MP) 11 Turkey (TR) 9
|
|
47
|
-
Cayman Islands (KY) 10 Iraq (IQ) 11 Norway (NO) 6 Turkmenistan (TM) 9
|
|
48
|
-
Central African Rep(CF) 11 Israel (IL) 9 Oman (OM) 9 Turks & Caicos (TC) 10
|
|
49
|
-
Chad (TD) 11 Italy (IT) 3 Pakistan (PK) 10 Tuvalu (TV) 10
|
|
50
|
-
Chile (CL) 10 Jamaica (JM) 10 Palau (PW) 11 USA (US) 7
|
|
51
|
-
China (CN) 8 Japan (JP) 8 Panama (PA) 10 Uganda (UG) 11
|
|
52
|
-
Colombia (CO) 10 Jersey (JE) 6 Papua New Guinea (PG) 10 Ukraine (UA) 9
|
|
53
|
-
Comoros (KM) 11 Jordan (JO) 9 Paraguay (PY) 10 United Arab Emirates (AE) 9
|
|
54
|
-
Congo (CG) 11 Kazakhstan (KZ) 10 Peru (PE) 10 United Kingdom (GB) *1 1
|
|
55
|
-
Congo, DPR (CD) 11 Kenya (KE) 11 Philippines, The (PH) 8 United Kingdom (GB) *2 5
|
|
56
|
-
Cook Islands (CK) 10 Kiribati (KI) 10 Poland (PL) 4 Uruguay (UY) 10
|
|
57
|
-
Costa Rica (CR) 10 Korea, Rep. Of (KR) 8 Portugal (PT) 3 Uzbekistan (UZ) 10
|
|
58
|
-
Cote D Ivoire (CI) 11 Korea, D.P.R Of (KP) 10 Puerto Rico (PR) 10 Vanuatu (VU) 10
|
|
59
|
-
Croatia (HR) 4 Kosovo (KV) 9 Qatar (QA) 9 Vatican City (VA) 3
|
|
60
|
-
Cuba (CU) 10 Kuwait (KW) 9 Reunion, Island Of (RE) 11 Venezuela (VE) 10
|
|
61
|
-
Curacao (XC) 10 Kyrgyzstan (KG) 10 Romania (RO) 4 Vietnam (VN) 10
|
|
62
|
-
Cyprus (CY) 4 Laos (LA) 10 Russian Federation (RU) 9 Virgin Islands-British (VG) 10
|
|
63
|
-
Czech Rep., The (CZ) 4 Latvia (LV) 4 Rwanda (RW) 11 Virgin Islands-US (VI) 10
|
|
64
|
-
Denmark (DK) 3 Lebanon (LB) 9 Saint Helena (SH) 11 Yemen, Rep. Of (YE) 11
|
|
65
|
-
Djibouti (DJ) 11 Lesotho (LS) 11 Samoa (WS) 10 Zambia (ZM) 11
|
|
66
|
-
Dominica (DM) 10 Liberia (LR) 11 San Marino (SM) 6 Zimbabwe (ZW) 11
|
|
67
|
-
Dominican Rep. (DO) 10 Libya (LY) 11
|
|
68
|
-
|
|
69
|
-
"""
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
def _get_country_iso2(country_name):
|
|
73
|
-
"""
|
|
74
|
-
Get ISO2 code for a country name using pycountry.
|
|
75
|
-
|
|
76
|
-
Args:
|
|
77
|
-
country_name: Country name to lookup
|
|
78
|
-
|
|
79
|
-
Returns:
|
|
80
|
-
str: ISO2 country code or None if not found
|
|
81
|
-
"""
|
|
82
|
-
import pycountry
|
|
83
|
-
|
|
84
|
-
# Manual mappings for common variations not in pycountry
|
|
85
|
-
manual_map = {
|
|
86
|
-
'United Kingdom': 'GB',
|
|
87
|
-
'Czech Rep., The': 'CZ',
|
|
88
|
-
'Korea, Republic Of (South K.)': 'KR',
|
|
89
|
-
'Korea, The D.P.R Of (North K.)': 'KP',
|
|
90
|
-
'Russian Federation, The': 'RU',
|
|
91
|
-
'Iran (Islamic Republic Of)': 'IR',
|
|
92
|
-
'Lao Peoples Democratic Republic': 'LA',
|
|
93
|
-
'China, Peoples Republic': 'CN',
|
|
94
|
-
'United States Of America': 'US',
|
|
95
|
-
'Virgin Islands (British)': 'VG',
|
|
96
|
-
'Virgin Islands (Us)': 'VI',
|
|
97
|
-
'Cote D Ivoire': 'CI',
|
|
98
|
-
'Republic Of Moldova': 'MD',
|
|
99
|
-
'Moldova, Republic Of': 'MD',
|
|
100
|
-
'Ireland, Republic Of': 'IE',
|
|
101
|
-
'Micronesia, Federated States Of': 'FM',
|
|
102
|
-
'Congo, The Democratic Republic Of': 'CD',
|
|
103
|
-
'Nauru, Republic Of': 'NR',
|
|
104
|
-
'Serbia, Republic Of': 'RS',
|
|
105
|
-
'Montenegro, Republic Of': 'ME',
|
|
106
|
-
'Yemen, Republic Of': 'YE',
|
|
107
|
-
'Guyana (British)': 'GY',
|
|
108
|
-
'Philippines, The': 'PH',
|
|
109
|
-
'Netherlands, The': 'NL',
|
|
110
|
-
'Canary Islands, The': 'IC',
|
|
111
|
-
'Commonwealth No. Mariana Islands': 'MP',
|
|
112
|
-
'Vatican City State': 'VA',
|
|
113
|
-
'Turks And Caicos Islands': 'TC',
|
|
114
|
-
'Guinea Republic': 'GN',
|
|
115
|
-
'Reunion, Island Of': 'RE',
|
|
116
|
-
'Swaziland': 'SZ',
|
|
117
|
-
'Timor Leste': 'TL',
|
|
118
|
-
'Somaliland, Rep Of (North Somalia)': 'XS',
|
|
119
|
-
'Central African Republic': 'CF',
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
# Clean up country name
|
|
123
|
-
name_clean = country_name.strip()
|
|
124
|
-
|
|
125
|
-
# Check manual mappings first
|
|
126
|
-
if name_clean in manual_map:
|
|
127
|
-
return manual_map[name_clean]
|
|
128
|
-
|
|
129
|
-
# Try exact match
|
|
130
|
-
try:
|
|
131
|
-
country = pycountry.countries.get(name=name_clean)
|
|
132
|
-
if country:
|
|
133
|
-
return country.alpha_2
|
|
134
|
-
except:
|
|
135
|
-
pass
|
|
136
|
-
|
|
137
|
-
# Try fuzzy search
|
|
138
|
-
try:
|
|
139
|
-
results = pycountry.countries.search_fuzzy(name_clean)
|
|
140
|
-
if results:
|
|
141
|
-
return results[0].alpha_2
|
|
142
|
-
except:
|
|
143
|
-
pass
|
|
144
|
-
|
|
145
|
-
return None
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
def parse_country_zones(input_text):
|
|
149
|
-
"""
|
|
150
|
-
Parse Excel copy-paste data with country-zone mappings.
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
input_text: Multi-column text with format "Country Name (ISO2) Zone" or "Country Name Zone"
|
|
154
|
-
|
|
155
|
-
Returns:
|
|
156
|
-
dict: Mapping of ISO2 country codes to zones (e.g., {'AF': 8, 'DE': 1, ...})
|
|
157
|
-
|
|
158
|
-
Note:
|
|
159
|
-
- Handles both formats: with ISO2 in brackets "Country (ISO2)" or without "Country"
|
|
160
|
-
- For duplicate countries (e.g., "United Kingdom *1", "United Kingdom *2"),
|
|
161
|
-
the last occurrence is used.
|
|
162
|
-
"""
|
|
163
|
-
zone_mapping = {}
|
|
164
|
-
|
|
165
|
-
# Pattern to match entries with ISO2 in brackets: Country Name (ISO2) [optional suffix] followed by zone number
|
|
166
|
-
pattern_with_iso2 = r'([^\(\)]+)\(([A-Z]{2})\)[^\t]*\t(\d+)'
|
|
167
|
-
# Pattern to match entries without ISO2: Country Name followed by zone number
|
|
168
|
-
pattern_without_iso2 = r'([^\t]+)\t(\d+)'
|
|
169
|
-
|
|
170
|
-
for line in input_text.strip().split('\n'):
|
|
171
|
-
# Try matching with ISO2 first
|
|
172
|
-
matches_with_iso2 = re.findall(pattern_with_iso2, line)
|
|
173
|
-
if matches_with_iso2:
|
|
174
|
-
for match in matches_with_iso2:
|
|
175
|
-
country_name, iso2, zone = match
|
|
176
|
-
zone_mapping[iso2] = int(zone)
|
|
177
|
-
else:
|
|
178
|
-
# Try matching without ISO2
|
|
179
|
-
matches_without_iso2 = re.findall(pattern_without_iso2, line)
|
|
180
|
-
for match in matches_without_iso2:
|
|
181
|
-
country_name, zone = match
|
|
182
|
-
country_name = country_name.strip()
|
|
183
|
-
|
|
184
|
-
# Extract base country name (remove suffixes like *1, *2)
|
|
185
|
-
base_name = re.sub(r'\s*\*\d+$', '', country_name)
|
|
186
|
-
|
|
187
|
-
# Get ISO2 code
|
|
188
|
-
iso2 = _get_country_iso2(base_name)
|
|
189
|
-
if iso2:
|
|
190
|
-
# Convert zone to int and store (will overwrite duplicates, keeping last)
|
|
191
|
-
zone_mapping[iso2] = int(zone)
|
|
192
|
-
|
|
193
|
-
return zone_mapping
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def copy_zones_to_clipboard(zone_mapping):
|
|
198
|
-
"""
|
|
199
|
-
Copy zone mapping to clipboard in tab-separated format for Excel.
|
|
200
|
-
|
|
201
|
-
Args:
|
|
202
|
-
zone_mapping: dict mapping ISO2 codes to zones (e.g., {'AF': 8, 'DE': 1})
|
|
203
|
-
|
|
204
|
-
Returns:
|
|
205
|
-
str: Tab-separated text copied to clipboard
|
|
206
|
-
"""
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
# Create tab-separated rows with 3 columns (ISO2, empty, zone)
|
|
210
|
-
lines = [f"{iso2}\t\t{zone}" for iso2, zone in sorted(zone_mapping.items())]
|
|
211
|
-
text = "\n".join(lines)
|
|
212
|
-
|
|
213
|
-
# Copy to clipboard
|
|
214
|
-
pyperclip.copy(text)
|
|
215
|
-
|
|
216
|
-
return text
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
# Example usage
|
|
220
|
-
if __name__ == "__main__":
|
|
221
|
-
zm = ZipcodeManager()
|
|
222
|
-
result = parse_country_zones(zones_input)
|
|
223
|
-
result_in_scope = {k: v for k, v in result.items() if k in zm.country.keys()}
|
|
224
|
-
|
|
225
|
-
# Copy to clipboard
|
|
226
|
-
copy_zones_to_clipboard(result_in_scope)
|
|
227
|
-
print(f"\n✓ Copied {len(result_in_scope)} countries to clipboard (paste into Excel)")
|
|
228
|
-
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
from pathlib import Path
|
|
2
|
-
import pandas as pd
|
|
3
|
-
import numpy as np
|
|
4
|
-
|
|
5
|
-
PATH_DIR = Path(r'simera_resources\ratesheets\10_ratesheet_creation_support')
|
|
6
|
-
|
|
7
|
-
df = pd.read_excel(PATH_DIR / 'DHL Express ratesheet converter.xlsb', sheet_name='python_script')
|
|
8
|
-
extras = df.loc[:, '<cost_type>':'value'].copy()
|
|
9
|
-
extras.dropna(axis=0, how='all', inplace=True)
|
|
10
|
-
|
|
11
|
-
df = df.loc[:, 'category':'<cost_type>'].iloc[:, :-1]
|
|
12
|
-
df = df.dropna(axis=1, how='all')
|
|
13
|
-
df = df.dropna(axis=0, how='all', subset=df.columns[1:])
|
|
14
|
-
|
|
15
|
-
adder = df.loc[df['category'] == 'adder'].copy()
|
|
16
|
-
adder = adder.dropna(axis=0, how='all', subset=adder.columns[1:])
|
|
17
|
-
adder.drop(columns=['category'], inplace=True)
|
|
18
|
-
|
|
19
|
-
rate = df.loc[df['category'] == 'rate'].copy()
|
|
20
|
-
rate = rate.dropna(axis=0, how='all', subset=rate.columns[1:])
|
|
21
|
-
rate = rate.dropna(axis=1, how='all')
|
|
22
|
-
rate.drop(columns=['category'], inplace=True)
|
|
23
|
-
rate.set_index('from', inplace=True)
|
|
24
|
-
|
|
25
|
-
use_adder_from = adder['from'].min()
|
|
26
|
-
# Drop rates with an index bigger than use_adder_from
|
|
27
|
-
rate = rate.loc[rate.index <= use_adder_from].copy()
|
|
28
|
-
|
|
29
|
-
# Get zone columns (all columns after 'to')
|
|
30
|
-
zone_cols = [col for col in adder.columns if col not in ['from', 'to', 'per_kg']]
|
|
31
|
-
|
|
32
|
-
# Extend rate dataframe using adder
|
|
33
|
-
for idx, adder_row in adder.iterrows():
|
|
34
|
-
from_weight = adder_row['from']
|
|
35
|
-
to_weight = adder_row['to']
|
|
36
|
-
per_kg = adder_row['per_kg']
|
|
37
|
-
|
|
38
|
-
# Get the last rate values (will be updated cumulatively)
|
|
39
|
-
last_rates = rate.iloc[-1][zone_cols]
|
|
40
|
-
|
|
41
|
-
# Determine weight range
|
|
42
|
-
current_weight = rate.index[-1] + per_kg
|
|
43
|
-
|
|
44
|
-
while current_weight <= to_weight and current_weight <= 3000:
|
|
45
|
-
# Check if next weight would be above 300kg
|
|
46
|
-
if current_weight > 300:
|
|
47
|
-
# Switch to 10kg steps - round up to next 10kg boundary
|
|
48
|
-
next_weight = ((int(rate.index[-1]) // 10) + 1) * 10
|
|
49
|
-
|
|
50
|
-
# Calculate incremental cost per kg (adder value is for per_kg amount)
|
|
51
|
-
incremental_cost_per_kg = adder_row[zone_cols] / per_kg
|
|
52
|
-
|
|
53
|
-
# Calculate how many kg to jump
|
|
54
|
-
kg_jump = next_weight - rate.index[-1]
|
|
55
|
-
|
|
56
|
-
# Add the cost for the jump
|
|
57
|
-
new_rates = last_rates + (incremental_cost_per_kg * kg_jump)
|
|
58
|
-
|
|
59
|
-
# Add the new row
|
|
60
|
-
rate.loc[next_weight] = new_rates
|
|
61
|
-
last_rates = new_rates
|
|
62
|
-
|
|
63
|
-
# Continue with 10kg steps
|
|
64
|
-
current_weight = next_weight + 10
|
|
65
|
-
else:
|
|
66
|
-
# Below or at 300kg: use per_kg steps
|
|
67
|
-
# adder value is already for per_kg amount, so just add it
|
|
68
|
-
new_rates = last_rates + adder_row[zone_cols]
|
|
69
|
-
|
|
70
|
-
# Add the new row
|
|
71
|
-
rate.loc[current_weight] = new_rates
|
|
72
|
-
last_rates = new_rates
|
|
73
|
-
|
|
74
|
-
# Move to next step
|
|
75
|
-
current_weight = round(current_weight + per_kg, 2)
|
|
76
|
-
|
|
77
|
-
# Sort index to ensure proper order
|
|
78
|
-
rate = rate.sort_index()
|
|
79
|
-
rate = np.round(rate, 2)
|
|
80
|
-
|
|
81
|
-
# Preparing simera template
|
|
82
|
-
rate.reset_index(inplace=True)
|
|
83
|
-
rate.insert(0, '<cost_type>', 'trp_var')
|
|
84
|
-
rate.insert(1, '<cost_unit>', 'shipment')
|
|
85
|
-
rate.insert(3, '<range_unit>', 'kg')
|
|
86
|
-
rate.rename(columns={'from': '<range_value>'}, inplace=True)
|
|
87
|
-
rate.loc[rate['<range_value>'] >= 3000, '<range_value>'] = pd.NA
|
|
88
|
-
|
|
89
|
-
# Add one fully empty row at the end
|
|
90
|
-
rate.loc[len(rate)] = np.nan
|
|
91
|
-
|
|
92
|
-
# Add additional cost types
|
|
93
|
-
output = pd.concat([rate, extras], ignore_index=True)
|
|
94
|
-
for zone in zone_cols:
|
|
95
|
-
output.loc[output[zone].isna(), zone] = output['value']
|
|
96
|
-
output.drop(columns=['value'], inplace=True)
|
|
97
|
-
|
|
98
|
-
output.to_clipboard(index=False)
|