leadingbridge commited on
Commit
0d71cda
·
verified ·
1 Parent(s): 0d5cde4

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +199 -0
app.py ADDED
@@ -0,0 +1,199 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ from datetime import datetime
4
+ import pytz
5
+ import re
6
+
7
+ # Updated Gradio app for YunExpress:
8
+ # - New output headers (as provided)
9
+ # - Email -> Email mapping
10
+ # - FOBPrice1 = 2 (replaces UnitPrice1)
11
+ # - ZIP padding fix for US 3- and 4-digit ZIPs (no leading apostrophe; sanitize non [A-Za-z0-9 ] chars)
12
+ # - RoutingCode logic: US/NO/FR -> HK-ASS-PF; others -> HKTHZXR
13
+ # - Save Excel with ZipCode column set to Text (@) to preserve leading zeros without apostrophes
14
+
15
+ def process_file(file):
16
+ file_name = file.name.lower()
17
+ try:
18
+ if file_name.endswith(('.xls', '.xlsx', '.xlsm')):
19
+ # Read data from the "YunExpress" sheet
20
+ df = pd.read_excel(file, sheet_name="YunExpress")
21
+ else:
22
+ return f"Unsupported file format: {file_name}", None
23
+ except Exception as e:
24
+ return f"Error reading file: {e}", None
25
+
26
+ # === New output headers (exact order) ===
27
+ output_headers = [
28
+ "CustomerOrderNo.", "RoutingCode", "Trackingnumber", "AdditionalServices",
29
+ "ShipmentProtectionPlusService", "CustomDeclaredValue", "SignatureService",
30
+ "VatNumber", "EoriNumber", "IossCode", "ManufactureSalesName",
31
+ "UnifiedSocialCreditCode", "CountryCode", "Name", "CertificateCode", "Company",
32
+ "Street", "City", "Province/State", "ZipCode", "phone", "HouseNumber", "Email",
33
+ "PackageNumber", "PackageWeight", "SenderFiastName", "SenderCompany",
34
+ "SenderStreet", "SenderCity", "SenderProvince", "SenderPostalCode",
35
+ "SenderCountry", "SenderTelephone", "SenderEmail", "SenderUSCI", "PlatformName",
36
+ "PlatformProvince", "PlatformAddress", "PlatformPostalCode", "PlatformPhoneNumber",
37
+ "PlatformEmail", "EcommercePlatformCode", "SalesPlatformLink", "CurrencyCode",
38
+ "SKU1", "ItemDescription1", "ForeignItemDescription1", "DeclaredQuantity1",
39
+ "FOBPrice1", "SellingPrice1", "UnitWeight1", "HsCode1", "Remarks1", "SalesLink1",
40
+ "Materials1", "Use1", "Brand1", "ModelType1", "Specs1", "FabricCreationMethod1",
41
+ "ManufacturerID1", "ManufacturerName1", "ManufacturerCountry1",
42
+ "ManufacturerState1", "ManufacturerCity1", "ManufacturerPostalCode1",
43
+ "ManufacturerAddress1", "CargoCategory", "PaymentPlatform", "PaymentAccount",
44
+ "PaymentTransactionNumber"
45
+ ]
46
+
47
+ # Initialize empty output DataFrame
48
+ output_df = pd.DataFrame("", index=range(len(df)), columns=output_headers)
49
+
50
+ # 1) Order Number -> CustomerOrderNo. (prefix "LB")
51
+ if "Order Number" in df.columns:
52
+ output_df["CustomerOrderNo."] = "LB" + df["Order Number"].astype(str)
53
+
54
+ # 2) Name: First + Last + Company -> Name
55
+ if {"Shipping First Name", "Shipping Last Name", "Shipping Company"}.issubset(df.columns):
56
+ first = df["Shipping First Name"].fillna("").astype(str).str.strip()
57
+ last = df["Shipping Last Name"].fillna("").astype(str).str.strip()
58
+ comp = df["Shipping Company"].fillna("").astype(str).str.strip()
59
+ output_df["Name"] = (first + " " + last + " " + comp).str.replace(r"\s+", " ", regex=True).str.strip()
60
+
61
+ # 3) Street: Address 1 + Address 2 -> Street
62
+ addr1 = df.get("Shipping Address 1", pd.Series([""] * len(df), index=df.index)).fillna("").astype(str).str.strip()
63
+ addr2 = df.get("Shipping Address 2", pd.Series([""] * len(df), index=df.index)).fillna("").astype(str).str.strip()
64
+ output_df["Street"] = (addr1 + " " + addr2).str.replace(r"\s+", " ", regex=True).str.strip()
65
+
66
+ # 4) City
67
+ if "Shipping City" in df.columns:
68
+ output_df["City"] = df["Shipping City"]
69
+
70
+ # 5) Province/State
71
+ if "Shipping Province" in df.columns:
72
+ output_df["Province/State"] = df["Shipping Province"]
73
+
74
+ # 6) CountryCode (before ZIP handling)
75
+ if "Shipping Country Code" in df.columns:
76
+ output_df["CountryCode"] = df["Shipping Country Code"]
77
+
78
+ # 7) ZipCode (sanitize; US 3/4-digit -> pad to 5; do NOT add apostrophe)
79
+ if "Shipping ZIP" in df.columns:
80
+ zip_raw = (
81
+ df["Shipping ZIP"]
82
+ .astype(str)
83
+ .str.strip()
84
+ .str.replace(r"\.0$", "", regex=True) # clean "1234.0"
85
+ )
86
+ # Keep only letters, digits, spaces (avoid "only letters, numbers and spaces" API error)
87
+ zip_clean = zip_raw.str.replace(r"[^A-Za-z0-9 ]+", "", regex=True)
88
+
89
+ mask_us = output_df["CountryCode"].eq("US")
90
+ # For US: if 3 or 4 numeric digits -> zero-fill to 5
91
+ mask_3_4_digits = zip_clean.str.fullmatch(r"\d{3,4}")
92
+ zip_padded = zip_clean.where(~(mask_us & mask_3_4_digits), zip_clean.str.zfill(5))
93
+
94
+ # Do NOT add any leading apostrophe
95
+ output_df["ZipCode"] = zip_padded
96
+
97
+ # 8) phone
98
+ if "Shipping Address Phone" in df.columns:
99
+ output_df["phone"] = df["Shipping Address Phone"]
100
+
101
+ # 9) NEW: Email -> Email (direct mapping)
102
+ if "Email" in df.columns:
103
+ output_df["Email"] = df["Email"]
104
+
105
+ # 10) PackageWeight: Total Weight (g) -> kg
106
+ if "Total Weight" in df.columns:
107
+ output_df["PackageWeight"] = df["Total Weight"] / 1000
108
+
109
+ # 11) DeclaredQuantity1: sum Quantity per Order Number
110
+ if "Order Number" in df.columns and "Quantity" in df.columns:
111
+ output_df["DeclaredQuantity1"] = df.groupby("Order Number")["Quantity"].transform("sum")
112
+
113
+ # Fixed defaults & RoutingCode
114
+ mask = output_df["CustomerOrderNo."].astype(str).str.len() > 0
115
+
116
+ # RoutingCode: HK-ASS-PF if US/NO/FR else HKTHZXR
117
+ mask_us_no_fr = mask & output_df["CountryCode"].isin(["US", "NO", "FR"])
118
+ mask_other = mask & ~output_df["CountryCode"].isin(["US", "NO", "FR"])
119
+ output_df.loc[mask_us_no_fr, "RoutingCode"] = "HK-ASS-PF"
120
+ output_df.loc[mask_other, "RoutingCode"] = "HKTHZXR"
121
+
122
+ # Pricing / descriptions / weights
123
+ output_df.loc[mask, "FOBPrice1"] = 2 # CHANGED from UnitPrice1=2
124
+ output_df.loc[mask, "CurrencyCode"] = "USD"
125
+ output_df.loc[mask, "ItemDescription1"] = "Eye Cosmetic Accessories"
126
+ output_df.loc[mask, "ForeignItemDescription1"] = "Eye Cosmetic Accessories"
127
+ output_df.loc[mask, "UnitWeight1"] = 0.02
128
+
129
+ # EU AdditionalServices = v1
130
+ EU_COUNTRIES = {"AT","BE","BG","CY","CZ","DE","DK","EE","ES","FI",
131
+ "FR","HR","HU","IE","IT","LT","LU","LV","MT","NL",
132
+ "PL","PT","RO","SE","SI","SK","GR"}
133
+ mask_eu = mask & output_df["CountryCode"].isin(EU_COUNTRIES)
134
+ output_df.loc[mask_eu, "AdditionalServices"] = "v1"
135
+
136
+ # Remove duplicates by CustomerOrderNo.
137
+ output_df = output_df.drop_duplicates(subset=["CustomerOrderNo."], keep="first")
138
+
139
+ # Output filename (HK date)
140
+ hk_tz = pytz.timezone("Asia/Hong_Kong")
141
+ today_hk = datetime.now(hk_tz).strftime("%y%m%d")
142
+ output_file_name = f"yunexpress {today_hk}.xlsx"
143
+
144
+ # Save to Excel with ZipCode column forced to Text (@) using xlsxwriter
145
+ try:
146
+ import xlsxwriter
147
+ from xlsxwriter.utility import xl_col_to_name
148
+
149
+ with pd.ExcelWriter(output_file_name, engine="xlsxwriter") as writer:
150
+ output_df.to_excel(writer, index=False, sheet_name="Sheet1")
151
+ workbook = writer.book
152
+ worksheet = writer.sheets["Sheet1"]
153
+
154
+ # Text format to preserve leading zeros without adding apostrophes
155
+ text_fmt = workbook.add_format({"num_format": "@"})
156
+
157
+ # Locate ZipCode column and set entire column to text
158
+ zip_col_idx = output_df.columns.get_loc("ZipCode") # 0-based index
159
+ col_letter = xl_col_to_name(zip_col_idx)
160
+ worksheet.set_column(f"{col_letter}:{col_letter}", None, text_fmt)
161
+ except Exception:
162
+ # Fallback to default writer if xlsxwriter not available
163
+ output_df.to_excel(output_file_name, index=False)
164
+
165
+ return output_df, output_file_name
166
+
167
+
168
+ # Gradio interface
169
+ with gr.Blocks(title="Shipping - YunExpress") as demo:
170
+ gr.Markdown("# Shipping - YunExpress")
171
+ with gr.Row():
172
+ file_input = gr.File(label="Upload Excel File with a YunExpress sheet")
173
+ process_button = gr.Button("Process Data")
174
+ with gr.Row():
175
+ output_data = gr.DataFrame()
176
+ output_file_component = gr.File(label="Download Processed File")
177
+ process_button.click(fn=process_file, inputs=[file_input], outputs=[output_data, output_file_component])
178
+
179
+ # Links to other tools
180
+ gr.HTML(
181
+ """
182
+ <div style="text-align: center; font-size: 16px; margin-top: 20px;">
183
+ <h3>Shipping Tools</h3>
184
+ <a href="https://huggingface.co/spaces/leadingbridge/shipping-dhl-e-commerce">DHL</a> |
185
+ <a href="https://huggingface.co/spaces/leadingbridge/shipping-ec-ship">EC-Ship</a> |
186
+ <a href="https://huggingface.co/spaces/leadingbridge/shipping-fedex">Fedex</a> |
187
+ <a href="https://huggingface.co/spaces/leadingbridge/shipping-UPS">UPS</a><br> |
188
+ <a href="https://huggingface.co/spaces/leadingbridge/shipping-yunexpress">Yunexpress</a>
189
+ </div>
190
+ <div style="text-align: center; font-size: 16px; margin-top: 20px;">
191
+ <h3>Administration Tools</h3>
192
+ <a href="https://huggingface.co/spaces/leadingbridge/email-template">Email Template</a> |
193
+ <a href="https://huggingface.co/spaces/leadingbridge/product-feeding">Google Merchant</a> |
194
+ <a href="https://huggingface.co/spaces/leadingbridge/tss">Order Processing</a>
195
+ </div>
196
+ """
197
+ )
198
+
199
+ demo.launch()