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.
Files changed (31) hide show
  1. simera-0.3.7/PKG-INFO +12 -0
  2. simera-0.3.7/pyproject.toml +18 -0
  3. simera-0.3.7/pyproject.toml.orig +17 -0
  4. simera-0.3.7/src/simera/__init__.py +11 -0
  5. simera-0.3.6/PKG-INFO +0 -18
  6. simera-0.3.6/pyproject.toml +0 -19
  7. simera-0.3.6/setup.cfg +0 -4
  8. simera-0.3.6/simera/__init__.py +0 -9
  9. simera-0.3.6/simera.egg-info/PKG-INFO +0 -18
  10. simera-0.3.6/simera.egg-info/SOURCES.txt +0 -25
  11. simera-0.3.6/simera.egg-info/dependency_links.txt +0 -1
  12. simera-0.3.6/simera.egg-info/requires.txt +0 -6
  13. simera-0.3.6/simera.egg-info/top_level.txt +0 -1
  14. simera-0.3.6/simera_inputs/config/generate_configs.py +0 -81
  15. simera-0.3.6/simera_resources/ratesheets/10_ratesheet_creation_support/dhl_exp_leadtimes.py +0 -67
  16. simera-0.3.6/simera_resources/ratesheets/10_ratesheet_creation_support/dhl_exp_zones.py +0 -228
  17. simera-0.3.6/simera_resources/ratesheets/10_ratesheet_creation_support/dhl_express_rates.py +0 -98
  18. simera-0.3.6/tests/test_units.py +0 -560
  19. simera-0.3.6/tests/test_zipcode.py +0 -123
  20. {simera-0.3.6 → simera-0.3.7}/README.md +0 -0
  21. {simera-0.3.6 → simera-0.3.7/src}/simera/activity.py +0 -0
  22. {simera-0.3.6 → simera-0.3.7/src}/simera/calculator.py +0 -0
  23. {simera-0.3.6 → simera-0.3.7/src}/simera/config.py +0 -0
  24. {simera-0.3.6 → simera-0.3.7/src}/simera/cosmicforg.py +0 -0
  25. {simera-0.3.6 → simera-0.3.7/src}/simera/presets.py +0 -0
  26. {simera-0.3.6 → simera-0.3.7/src}/simera/ratesheet.py +0 -0
  27. {simera-0.3.6 → simera-0.3.7/src}/simera/scenario.py +0 -0
  28. {simera-0.3.6 → simera-0.3.7/src}/simera/scenario_item.py +0 -0
  29. {simera-0.3.6 → simera-0.3.7/src}/simera/units.py +0 -0
  30. {simera-0.3.6 → simera-0.3.7/src}/simera/utils.py +0 -0
  31. {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.
@@ -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
@@ -1,4 +0,0 @@
1
- [egg_info]
2
- tag_build =
3
- tag_date = 0
4
-
@@ -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,6 +0,0 @@
1
- pandas<=2.3.3
2
- openpyxl
3
- pyxlsb
4
- python-calamine
5
- pyyaml
6
- tqdm
@@ -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)