166 lines
7.3 KiB
Python
166 lines
7.3 KiB
Python
"""
|
|
Marketplace-specific configuration: localized transaction-type translations, currencies,
|
|
default FX rates, and storage-fee detection.
|
|
|
|
The type translations were reverse-engineered from the real Jan-2026 files for all 13
|
|
marketplaces and cross-checked against the Finance team's own English helper column
|
|
("type 2") present in the localized EU reports.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import unicodedata
|
|
|
|
|
|
def fold(s: str) -> str:
|
|
"""Lowercase, strip accents, collapse whitespace — matching key for localized text."""
|
|
s = unicodedata.normalize("NFD", str(s).lower().strip())
|
|
s = "".join(ch for ch in s if not unicodedata.combining(ch))
|
|
return " ".join(s.split())
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Canonical transaction types = the USA vocabulary (what the engine/journal understand).
|
|
# localized (folded) -> canonical English
|
|
# ---------------------------------------------------------------------------
|
|
_TYPE_SOURCES: dict[str, list[str]] = {
|
|
"Order": [
|
|
"order", "commande", "bestellung", "ordine", "pedido", "bestelling", "zamowienie",
|
|
"siparis",
|
|
],
|
|
"Refund": [
|
|
"refund", "remboursement", "erstattung", "rimborso", "reembolso", "terugbetaling",
|
|
"zwrot kosztow", "zwrot", "aterbetalning", "iade",
|
|
],
|
|
"Adjustment": [
|
|
"adjustment", "ajustement", "anpassung", "modifica", "ajuste", "aanpassing",
|
|
"korekta", "justering", "duzeltme",
|
|
],
|
|
"Transfer": [
|
|
"transfer", "transfert", "transferer", "ubertrag", "uberweisung", "trasferimento",
|
|
"transferir", "overboeking", "przelew", "overforing", "havale",
|
|
],
|
|
"Service Fee": [
|
|
"service fee", "frais de service", "servicegebuhr", "commissione di servizio",
|
|
"tarifa de prestacion de servicio", "servicekosten", "oplata za usluge",
|
|
"serviceavgift", "hizmet bedeli",
|
|
],
|
|
"FBA Customer Return Fee": [
|
|
"fba customer return fee", "fulfilment by amazon customer return fee",
|
|
"frais de retour client fba", "frais de retour client expedie par amazon",
|
|
"gebuhren fur kundenrucksendungen mit versand durch amazon",
|
|
"tariffa di reso cliente di logistica di amazon",
|
|
"tarifa de devolucion del cliente de logistica de amazon",
|
|
"fba-kosten retourzending klant", "oplata za zwrot towaru od klienta w ramach fba",
|
|
"avgift for fba-kundreturer",
|
|
],
|
|
# Storage fees — Amazon calls these "FBA Inventory Fee" in the US report.
|
|
"FBA Inventory Fee": [
|
|
"fba inventory fee", "fulfilment by amazon inventory fee",
|
|
"frais de stock expedie par amazon", "versand durch amazon lagergebuhr",
|
|
"costo di stoccaggio logistica di amazon",
|
|
"tarifas de inventario de logistica de amazon", "lageravgift for fba",
|
|
],
|
|
"FBA Transaction fees": [
|
|
"fba transaction fees", "fulfilment by amazon (fba) transaction fees",
|
|
"frais de transaction expedie par amazon",
|
|
"transaktionsgebuhren fur versand durch amazon",
|
|
"commissioni per le transazioni di logistica di amazon",
|
|
"tarifas de transaccion de logistica de amazon",
|
|
],
|
|
"Amazon Fees": [
|
|
"amazon fees", "amazon fee", "gebuhren von amazon", "tariffe e commissioni amazon",
|
|
"tarifas de amazon",
|
|
],
|
|
"Shipping Services": ["shipping services", "delivery services"],
|
|
"Order_Retrocharge": ["order_retrocharge", "bestellung_wiedereinzug", "cargo retroactivo"],
|
|
"Refund_Retrocharge": ["refund_retrocharge", "erstattung_wiedereinzug"],
|
|
"Chargeback Refund": ["chargeback refund", "erstattung durch ruckbuchung"],
|
|
"Liquidations": ["liquidations", "liquidationen"],
|
|
"Liquidations Adjustments": ["liquidations adjustments", "liquidationsanpassungen"],
|
|
"Fee Adjustment": ["fee adjustment"],
|
|
"SAFE-T reimbursement": ["safe-t reimbursement"],
|
|
"A-to-z Guarantee Claim": ["a-to-z guarantee claim"],
|
|
"Others": ["others"],
|
|
"Debt": ["debt"],
|
|
}
|
|
|
|
TYPE_TRANSLATIONS: dict[str, str] = {}
|
|
for _canon, _variants in _TYPE_SOURCES.items():
|
|
for _v in _variants:
|
|
TYPE_TRANSLATIONS[fold(_v)] = _canon
|
|
|
|
|
|
def normalize_type(raw: str | None) -> str:
|
|
"""Localized/variant transaction type -> canonical English (falls back to the original)."""
|
|
if not raw:
|
|
return ""
|
|
return TYPE_TRANSLATIONS.get(fold(raw), str(raw).strip())
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Currencies & default FX (seeded from the Jan-2026 AR workbook; always user-editable).
|
|
# The workbook converts PLN/SEK/TRY to EUR first, then EUR->USD — collapsed here into
|
|
# a single to-USD rate.
|
|
# ---------------------------------------------------------------------------
|
|
CURRENCY_BY_REGION: dict[str, str] = {
|
|
"USA": "USD", "Canada": "CAD", "UK": "GBP", "Australia": "AUD",
|
|
"Germany": "EUR", "France": "EUR", "Italy": "EUR", "Spain": "EUR",
|
|
"Netherlands": "EUR", "Belgium": "EUR", "Ireland": "EUR",
|
|
"Poland": "PLN", "Sweden": "SEK", "Turkey": "TRY",
|
|
}
|
|
|
|
_EUR_USD = 1.185665
|
|
DEFAULT_FX_USD: dict[str, float] = {
|
|
"USA": 1.0,
|
|
"Canada": 0.734438,
|
|
"UK": 1.368908,
|
|
"Australia": 0.696189,
|
|
"Germany": _EUR_USD, "France": _EUR_USD, "Italy": _EUR_USD, "Spain": _EUR_USD,
|
|
"Netherlands": _EUR_USD, "Belgium": _EUR_USD, "Ireland": _EUR_USD,
|
|
"Poland": 0.23728 * _EUR_USD, # PLN->EUR->USD (full precision, matches workbook)
|
|
"Sweden": 0.094559 * _EUR_USD, # SEK->EUR->USD
|
|
"Turkey": 0.019409 * _EUR_USD, # TRY->EUR->USD
|
|
}
|
|
|
|
|
|
def currency_for_region(region: str) -> str:
|
|
return CURRENCY_BY_REGION.get(region, "USD")
|
|
|
|
|
|
def default_fx_for_region(region: str) -> float:
|
|
return DEFAULT_FX_USD.get(region, 1.0)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Storage-fee detection (req #8). Primary signal: canonical type == FBA Inventory Fee.
|
|
# Secondary: storage keywords in the type/description — used to flag "potential storage
|
|
# fees" recorded under other categories (never silently reclassified).
|
|
# ---------------------------------------------------------------------------
|
|
STORAGE_KEYWORDS = [
|
|
"storage", "lagergebuhr", "lagerung", "langzeitlager", # EN / DE
|
|
"stockage", "frais de stock", # FR
|
|
"stoccaggio", # IT
|
|
"almacenamiento", "almacenaje", # ES
|
|
"opslag", # NL
|
|
"magazynowanie", "magazyn", # PL
|
|
"lagring", "lageravgift", # SV
|
|
"depolama", # TR
|
|
"inventory storage", "monthly storage", "long-term storage",
|
|
]
|
|
_STORAGE_KEYS_FOLDED = [fold(k) for k in STORAGE_KEYWORDS]
|
|
|
|
# Markets where Amazon bills monthly FBA storage — used for the "expected storage fees
|
|
# missing" warning.
|
|
STORAGE_EXPECTED_REGIONS = {
|
|
"USA", "Canada", "UK", "Germany", "France", "Italy", "Spain",
|
|
"Netherlands", "Belgium", "Poland", "Sweden", "Australia", "Ireland",
|
|
}
|
|
|
|
|
|
def is_storage_like(type_en: str | None, description: str | None) -> bool:
|
|
"""True when the type/description reads as a storage fee."""
|
|
if type_en == "FBA Inventory Fee":
|
|
return True
|
|
hay = fold(f"{type_en or ''} {description or ''}")
|
|
return any(k in hay for k in _STORAGE_KEYS_FOLDED)
|