transdesk-importer-python-sdk 1.0.0__tar.gz → 1.1.0__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 (38) hide show
  1. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/PKG-INFO +16 -1
  2. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/README.md +15 -0
  3. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/pyproject.toml +1 -1
  4. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/models/canonical.py +18 -0
  5. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/models/internal.py +18 -4
  6. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/shared/data_normalizer.py +21 -1
  7. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/canonical/apolice_builder.py +32 -0
  8. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/internal/coverage_mapper.py +3 -3
  9. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/internal/internal_mapper.py +78 -5
  10. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/internal/rcf_parser.py +1 -1
  11. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk_importer_python_sdk.egg-info/PKG-INFO +16 -1
  12. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk_importer_python_sdk.egg-info/SOURCES.txt +3 -1
  13. transdesk_importer_python_sdk-1.1.0/tests/test_data_normalizer.py +26 -0
  14. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/tests/test_importer.py +1 -0
  15. transdesk_importer_python_sdk-1.1.0/tests/test_vehicle_details.py +87 -0
  16. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/LICENSE +0 -0
  17. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/setup.cfg +0 -0
  18. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/__init__.py +0 -0
  19. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/client.py +0 -0
  20. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/exporters/__init__.py +0 -0
  21. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/exporters/json_exporter.py +0 -0
  22. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/models/__init__.py +0 -0
  23. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/readers/__init__.py +0 -0
  24. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/readers/excel_reader.py +0 -0
  25. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/shared/__init__.py +0 -0
  26. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/__init__.py +0 -0
  27. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/canonical/__init__.py +0 -0
  28. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/canonical/cobertura_builder.py +0 -0
  29. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/canonical/item_classifier.py +0 -0
  30. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/internal/__init__.py +0 -0
  31. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/transformers/internal/towing_parser.py +0 -0
  32. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/validators/__init__.py +0 -0
  33. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk/importer/validators/internal_policy_validator.py +0 -0
  34. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk_importer_python_sdk.egg-info/dependency_links.txt +0 -0
  35. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk_importer_python_sdk.egg-info/requires.txt +0 -0
  36. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/src/transdesk_importer_python_sdk.egg-info/top_level.txt +0 -0
  37. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/tests/test_excel_reader.py +0 -0
  38. {transdesk_importer_python_sdk-1.0.0 → transdesk_importer_python_sdk-1.1.0}/tests/test_validator.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transdesk-importer-python-sdk
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: SDK de leitura/transformacao de planilhas de apolices Transdesk para o formato interno 77seg
5
5
  Author-email: FMConsult <contato@fmconsult.com.br>
6
6
  License: MIT License
@@ -112,6 +112,21 @@ Helpers: `result.to_dict()` e `result.to_json(**kwargs)`.
112
112
  cobertura e de risco estao sempre presentes; os que nao se aplicam ao tipo do
113
113
  item (ex.: campos de vida num veiculo) vem como `null`.
114
114
 
115
+ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
116
+
117
+ | Coluna planilha | Campo `InternalItem` | Observacao |
118
+ | --------------- | -------------------- | ---------- |
119
+ | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
120
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
121
+ | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
122
+ | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
123
+ | `RENAVAM` | `renavam` | |
124
+ | `COR` | `color` | |
125
+ | `ANO` | `model_year`, `manufacture_year` | Mesmo valor nos dois campos |
126
+ | `EIXOS` | `axles` | |
127
+ | `CAMBIO` | `transmission` | |
128
+ | `ALIENADO` | `is_alienated` | `N/A` ou vazio = `false` |
129
+
115
130
  ### Validacao
116
131
 
117
132
  O `ValidationError` cobre tres tipos:
@@ -69,6 +69,21 @@ Helpers: `result.to_dict()` e `result.to_json(**kwargs)`.
69
69
  cobertura e de risco estao sempre presentes; os que nao se aplicam ao tipo do
70
70
  item (ex.: campos de vida num veiculo) vem como `null`.
71
71
 
72
+ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
73
+
74
+ | Coluna planilha | Campo `InternalItem` | Observacao |
75
+ | --------------- | -------------------- | ---------- |
76
+ | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
77
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
78
+ | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
79
+ | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
80
+ | `RENAVAM` | `renavam` | |
81
+ | `COR` | `color` | |
82
+ | `ANO` | `model_year`, `manufacture_year` | Mesmo valor nos dois campos |
83
+ | `EIXOS` | `axles` | |
84
+ | `CAMBIO` | `transmission` | |
85
+ | `ALIENADO` | `is_alienated` | `N/A` ou vazio = `false` |
86
+
72
87
  ### Validacao
73
88
 
74
89
  O `ValidationError` cobre tres tipos:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "transdesk-importer-python-sdk"
7
- version = "1.0.0"
7
+ version = "1.1.0"
8
8
  description = "SDK de leitura/transformacao de planilhas de apolices Transdesk para o formato interno 77seg"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -36,6 +36,16 @@ class VeiculoRisco:
36
36
  placa: Optional[str] = None
37
37
  chassi: Optional[str] = None
38
38
  codigo_fipe: Optional[str] = None
39
+ empresa_rastreamento: Optional[str] = None
40
+ categoria: Optional[str] = None
41
+ modelo: Optional[str] = None
42
+ renavam: Optional[str] = None
43
+ marca: Optional[str] = None
44
+ cor: Optional[str] = None
45
+ ano: Optional[str] = None
46
+ eixos: Optional[str] = None
47
+ cambio: Optional[str] = None
48
+ alienado: Optional[str] = None
39
49
 
40
50
 
41
51
  @dataclass
@@ -44,6 +54,14 @@ class ReboqueRisco:
44
54
  chassi: Optional[str] = None
45
55
  marca: Optional[str] = None
46
56
  modelo: Optional[str] = None
57
+ empresa_rastreamento: Optional[str] = None
58
+ categoria: Optional[str] = None
59
+ renavam: Optional[str] = None
60
+ cor: Optional[str] = None
61
+ ano: Optional[str] = None
62
+ eixos: Optional[str] = None
63
+ cambio: Optional[str] = None
64
+ alienado: Optional[str] = None
47
65
 
48
66
 
49
67
  @dataclass
@@ -23,22 +23,25 @@ class Broker:
23
23
 
24
24
  @dataclass
25
25
  class CustomerInfo:
26
+ cpf: Optional[str] = None
26
27
  cnpj: Optional[str] = None
27
28
  principal_name: Optional[str] = None
28
29
  secondary_name: Optional[str] = None
29
30
  email_address: Optional[str] = None
31
+ enrollment_number: Optional[str] = None
32
+ custom_fields: Optional[Dict[str, Any]] = None
30
33
 
31
34
 
32
35
  @dataclass
33
36
  class Telephone:
34
- telphone_type_code: str
37
+ type_code: str
35
38
  ddd: str
36
39
  number: str
37
40
 
38
41
 
39
42
  @dataclass
40
43
  class Mobile:
41
- telphone_type_code: str
44
+ type_code: str
42
45
  ddd: str
43
46
  number: str
44
47
  allow_sms: bool = True
@@ -85,7 +88,7 @@ class InternalItem:
85
88
 
86
89
  # RCF
87
90
  has_rcf: bool = False
88
- property_damage_value: float = 0
91
+ material_damage_value: float = 0
89
92
  property_damage_premium: float = 0
90
93
  bodily_injury_value: float = 0
91
94
  bodily_injury_premium: float = 0
@@ -102,12 +105,13 @@ class InternalItem:
102
105
  towing_premium: float = 0
103
106
 
104
107
  # Vidros
105
- glass_coverage_type: Optional[str] = None
108
+ glass_coverage_level: Optional[str] = None
106
109
  glass_premium: float = 0
107
110
 
108
111
  # Rastreador
109
112
  has_vehicle_tracker: bool = False
110
113
  vehicle_tracker_premium: float = 0
114
+ vehicle_tracker_company: Optional[str] = None
111
115
 
112
116
  # Juridica
113
117
  has_legal_assistance: bool = False
@@ -124,6 +128,16 @@ class InternalItem:
124
128
  body_description: Optional[str] = None
125
129
  brand: Optional[str] = None
126
130
  model: Optional[str] = None
131
+ brand_name: Optional[str] = None
132
+ model_description: Optional[str] = None
133
+ vehicle_classification: Optional[int] = None
134
+ renavam: Optional[str] = None
135
+ color: Optional[str] = None
136
+ model_year: Optional[int] = None
137
+ manufacture_year: Optional[int] = None
138
+ axles: Optional[int] = None
139
+ transmission: Optional[str] = None
140
+ is_alienated: bool = False
127
141
 
128
142
  # Vida
129
143
  name: Optional[str] = None
@@ -41,7 +41,27 @@ class DataNormalizer:
41
41
 
42
42
  @staticmethod
43
43
  def to_int(value):
44
+ if value in (None, ""):
45
+ return None
46
+
44
47
  try:
45
- return int(value)
48
+ return int(str(value).strip())
46
49
  except Exception:
47
50
  return None
51
+
52
+ @staticmethod
53
+ def to_bool(value, na_values=("N/A", "n/a", "")):
54
+ if value is None:
55
+ return False
56
+
57
+ normalized = str(value).strip()
58
+ if normalized in na_values:
59
+ return False
60
+
61
+ upper = normalized.upper()
62
+ if upper in ("SIM", "S", "TRUE", "1", "YES", "Y"):
63
+ return True
64
+ if upper in ("NÃO", "NAO", "N", "FALSE", "0", "NO"):
65
+ return False
66
+
67
+ return False
@@ -188,6 +188,7 @@ class ApoliceBuilder:
188
188
  chassi=row.get("CHASSI"),
189
189
  marca=row.get("MARCA_REBOQUE"),
190
190
  modelo=row.get("MODELO_REBOQUE"),
191
+ **self._read_vehicle_detail_fields(rows, include_brand_model=False),
191
192
  ),
192
193
  coberturas=CoberturaBuilder(rows).build(),
193
194
  )
@@ -205,6 +206,7 @@ class ApoliceBuilder:
205
206
  placa=row.get("PLACA_VEICULO"),
206
207
  chassi=row.get("CHASSI"),
207
208
  codigo_fipe=row.get("CODE_FIPE"),
209
+ **self._read_vehicle_detail_fields(rows, include_brand_model=True),
208
210
  ),
209
211
  complementos=self.build_complementos(rows),
210
212
  coberturas=CoberturaBuilder(rows).build(),
@@ -214,6 +216,36 @@ class ApoliceBuilder:
214
216
 
215
217
  return item
216
218
 
219
+ def _read_vehicle_detail_fields(self, rows, include_brand_model=False):
220
+ column_map = {
221
+ "empresa_rastreamento": "EMPRESA_RASTREAMENTO",
222
+ "categoria": "CATEGORIA",
223
+ "renavam": "RENAVAM",
224
+ "cor": "COR",
225
+ "ano": "ANO",
226
+ "eixos": "EIXOS",
227
+ "cambio": "CAMBIO",
228
+ "alienado": "ALIENADO",
229
+ }
230
+
231
+ if include_brand_model:
232
+ column_map["marca"] = "MARCA"
233
+ column_map["modelo"] = "MODELO"
234
+
235
+ fields = {}
236
+ for field_name, column_name in column_map.items():
237
+ fields[field_name] = self._first_non_empty(rows, column_name)
238
+
239
+ return fields
240
+
241
+ @staticmethod
242
+ def _first_non_empty(rows, column_name):
243
+ for row in rows:
244
+ value = row.get(column_name)
245
+ if value is not None and str(value).strip() != "":
246
+ return value
247
+ return None
248
+
217
249
  # ---------------------------------------------------------
218
250
  # COMPLEMENTOS
219
251
  # ---------------------------------------------------------
@@ -93,7 +93,7 @@ class CoverageMapper:
93
93
  item.vehicle_tracker_premium += (coverage.valor_mensalidade or 0)
94
94
 
95
95
  def _apply_glass(self, item, coverage):
96
- item.glass_coverage_type = "completo"
96
+ item.glass_coverage_level = "completo"
97
97
  item.glass_premium += (coverage.valor_mensalidade or 0)
98
98
 
99
99
  def _apply_rcf(self, item, coverage):
@@ -104,13 +104,13 @@ class CoverageMapper:
104
104
  premiums = self._allocate_premium(
105
105
  coverage.valor_mensalidade or 0,
106
106
  {
107
- "property_damage": parsed["property_damage_value"],
107
+ "property_damage": parsed["material_damage_value"],
108
108
  "bodily_injury": parsed["bodily_injury_value"],
109
109
  "moral_damage": parsed["moral_damage_value"],
110
110
  },
111
111
  )
112
112
 
113
- item.property_damage_value += (parsed["property_damage_value"])
113
+ item.material_damage_value += (parsed["material_damage_value"])
114
114
  item.property_damage_premium += (premiums["property_damage"])
115
115
 
116
116
  item.bodily_injury_value += (parsed["bodily_injury_value"])
@@ -10,6 +10,7 @@ from transdesk.importer.models.internal import (
10
10
  ResellerData,
11
11
  Telephone,
12
12
  )
13
+ from transdesk.importer.shared.data_normalizer import DataNormalizer
13
14
  from transdesk.importer.transformers.internal.coverage_mapper import CoverageMapper
14
15
 
15
16
 
@@ -44,19 +45,61 @@ class InternalMapper:
44
45
  customer = policy.cliente
45
46
 
46
47
  email = next((e for e in customer.emails if e), None)
48
+ cpf, cnpj = self._route_document(customer.documento, customer.tipo_pessoa)
47
49
 
48
50
  return Lead(
49
51
  customer_info=CustomerInfo(
50
- cnpj=customer.documento,
51
- principal_name=(customer.razao_social or customer.nome_fantasia),
52
- secondary_name="",
52
+ cpf=cpf,
53
+ cnpj=cnpj,
54
+ principal_name=customer.razao_social or customer.nome_fantasia,
55
+ secondary_name=customer.nome_fantasia or "",
53
56
  email_address=email,
57
+ enrollment_number=customer.matricula,
58
+ custom_fields=self._build_bank_custom_fields(customer),
54
59
  ),
55
60
  telphone_info=self._build_phone(customer),
56
61
  celphone_info=self._build_mobile(customer),
57
62
  address_info=self._build_address(customer),
58
63
  )
59
64
 
65
+ def _build_bank_custom_fields(self, customer):
66
+ bank = customer.dados_bancarios
67
+
68
+ if bank is None:
69
+ return None
70
+
71
+ bank_info = {
72
+ 'bank_code': bank.banco,
73
+ 'agency': bank.agencia,
74
+ 'account': bank.conta,
75
+ 'pix_key': bank.chave_pix,
76
+ 'holder_name': bank.titular,
77
+ 'holder_document': bank.documento_titular,
78
+ }
79
+
80
+ if not any(bank_info.values()):
81
+ return None
82
+
83
+ return {'bank_info': bank_info}
84
+
85
+ def _route_document(self, documento, tipo_pessoa=None):
86
+ digits = "".join(filter(str.isdigit, str(documento or "")))
87
+
88
+ if not digits:
89
+ return None, None
90
+
91
+ tipo = str(tipo_pessoa or "").strip().upper()
92
+
93
+ if tipo in ("F", "PF", "FISICA", "FÍSICA"):
94
+ return digits, None
95
+ if tipo in ("J", "PJ", "JURIDICA", "JURÍDICA"):
96
+ return None, digits
97
+
98
+ if len(digits) <= 11:
99
+ return digits.zfill(11), None
100
+
101
+ return None, digits.zfill(14)
102
+
60
103
  def build_items(self, policy):
61
104
  return [
62
105
  self.map_item(item, index)
@@ -77,7 +120,7 @@ class InternalMapper:
77
120
 
78
121
  if not numero.startswith("9"):
79
122
  return Telephone(
80
- telphone_type_code="03",
123
+ type_code="03",
81
124
  ddd=digits[:2],
82
125
  number=numero,
83
126
  )
@@ -98,7 +141,7 @@ class InternalMapper:
98
141
 
99
142
  if numero.startswith("9"):
100
143
  return Mobile(
101
- telphone_type_code="02",
144
+ type_code="02",
102
145
  ddd=digits[:2],
103
146
  number=numero,
104
147
  allow_sms=True,
@@ -154,6 +197,36 @@ class InternalMapper:
154
197
  if item.tipo_item == "reboque":
155
198
  internal_item.brand = getattr(risco, "marca", None)
156
199
  internal_item.model = getattr(risco, "modelo", None)
200
+ internal_item.brand_name = internal_item.brand
201
+ internal_item.model_description = internal_item.model
202
+ elif item.tipo_item == "veiculo":
203
+ marca = getattr(risco, "marca", None)
204
+ modelo = getattr(risco, "modelo", None)
205
+ internal_item.brand = marca
206
+ internal_item.model = modelo
207
+ internal_item.brand_name = marca
208
+ internal_item.model_description = modelo
209
+
210
+ internal_item.vehicle_classification = DataNormalizer.to_int(
211
+ getattr(risco, "categoria", None)
212
+ )
213
+ internal_item.renavam = DataNormalizer.clean_doc(
214
+ getattr(risco, "renavam", None)
215
+ )
216
+ internal_item.color = getattr(risco, "cor", None) or None
217
+ year = DataNormalizer.to_int(getattr(risco, "ano", None))
218
+ internal_item.model_year = year
219
+ internal_item.manufacture_year = year
220
+ internal_item.axles = DataNormalizer.to_int(getattr(risco, "eixos", None))
221
+ internal_item.transmission = getattr(risco, "cambio", None) or None
222
+ internal_item.is_alienated = DataNormalizer.to_bool(
223
+ getattr(risco, "alienado", None)
224
+ )
225
+
226
+ if internal_item.has_vehicle_tracker:
227
+ internal_item.vehicle_tracker_company = (
228
+ getattr(risco, "empresa_rastreamento", None) or None
229
+ )
157
230
 
158
231
  def _build_body_description(self, item):
159
232
  parts = []
@@ -10,7 +10,7 @@ class RCFParser:
10
10
  dmo = self.extract_dmo(produto)
11
11
 
12
12
  return {
13
- "property_damage_value": dma_dc,
13
+ "material_damage_value": dma_dc,
14
14
  "bodily_injury_value": dma_dc,
15
15
  "moral_damage_value": dmo,
16
16
  }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transdesk-importer-python-sdk
3
- Version: 1.0.0
3
+ Version: 1.1.0
4
4
  Summary: SDK de leitura/transformacao de planilhas de apolices Transdesk para o formato interno 77seg
5
5
  Author-email: FMConsult <contato@fmconsult.com.br>
6
6
  License: MIT License
@@ -112,6 +112,21 @@ Helpers: `result.to_dict()` e `result.to_json(**kwargs)`.
112
112
  cobertura e de risco estao sempre presentes; os que nao se aplicam ao tipo do
113
113
  item (ex.: campos de vida num veiculo) vem como `null`.
114
114
 
115
+ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
116
+
117
+ | Coluna planilha | Campo `InternalItem` | Observacao |
118
+ | --------------- | -------------------- | ---------- |
119
+ | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
120
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
121
+ | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
122
+ | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
123
+ | `RENAVAM` | `renavam` | |
124
+ | `COR` | `color` | |
125
+ | `ANO` | `model_year`, `manufacture_year` | Mesmo valor nos dois campos |
126
+ | `EIXOS` | `axles` | |
127
+ | `CAMBIO` | `transmission` | |
128
+ | `ALIENADO` | `is_alienated` | `N/A` ou vazio = `false` |
129
+
115
130
  ### Validacao
116
131
 
117
132
  O `ValidationError` cobre tres tipos:
@@ -29,6 +29,8 @@ src/transdesk_importer_python_sdk.egg-info/SOURCES.txt
29
29
  src/transdesk_importer_python_sdk.egg-info/dependency_links.txt
30
30
  src/transdesk_importer_python_sdk.egg-info/requires.txt
31
31
  src/transdesk_importer_python_sdk.egg-info/top_level.txt
32
+ tests/test_data_normalizer.py
32
33
  tests/test_excel_reader.py
33
34
  tests/test_importer.py
34
- tests/test_validator.py
35
+ tests/test_validator.py
36
+ tests/test_vehicle_details.py
@@ -0,0 +1,26 @@
1
+ import pytest
2
+
3
+ from transdesk.importer.shared.data_normalizer import DataNormalizer
4
+
5
+
6
+ @pytest.mark.parametrize(
7
+ "value,expected",
8
+ [
9
+ ("N/A", False),
10
+ ("n/a", False),
11
+ ("", False),
12
+ (None, False),
13
+ ("SIM", True),
14
+ ("sim", True),
15
+ ("S", True),
16
+ ("1", True),
17
+ ("true", True),
18
+ ("NÃO", False),
19
+ ("NAO", False),
20
+ ("N", False),
21
+ ("0", False),
22
+ ("false", False),
23
+ ],
24
+ )
25
+ def test_to_bool(value, expected):
26
+ assert DataNormalizer.to_bool(value) is expected
@@ -35,6 +35,7 @@ def test_policy_structure(apolices_bytes):
35
35
  assert broker.principal_name == "TRANSDESK UNIDADE CURITIBA"
36
36
 
37
37
  assert policy.data.lead.customer_info.principal_name == "ALM TRANSPORTES E LOGISTICA LTDA"
38
+ assert policy.data.lead.customer_info.cnpj
38
39
 
39
40
  item = policy.data.items[0]
40
41
  assert item.item_number == 1
@@ -0,0 +1,87 @@
1
+ import pandas as pd
2
+
3
+ from transdesk.importer.transformers.canonical.apolice_builder import ApoliceBuilder
4
+ from transdesk.importer.transformers.internal.internal_mapper import InternalMapper
5
+
6
+
7
+ def _base_row(**overrides):
8
+ row = {
9
+ "COD_AGRUPADOR_APOLICE": "99",
10
+ "DIA_VCTO": "10",
11
+ "ID_UNITY": "1",
12
+ "SUB_ESTIPULANTE_CNPJ": "12345678000199",
13
+ "SUB_ESTIPULANTE_FANTASIA": "CORRETORA TESTE",
14
+ "SUB_ESTIPULANTE_RAZAO_SOCIAL": "CORRETORA TESTE LTDA",
15
+ "PESSOA": "J",
16
+ "CNPJ_CPF_CLIENTE": "98765432000188",
17
+ "NOME_FANTASIA_CLIENTE": "CLIENTE TESTE",
18
+ "RAZAO_SOCIAL_CLIENTE": "CLIENTE TESTE LTDA",
19
+ "PLACA_VEICULO": "ABC1D23",
20
+ "CHASSI": "9BWZZZ377VT004251",
21
+ "CODE_FIPE": "005541-7",
22
+ "NME_BENEFICIO": "Casco PT (VEÍCULO)",
23
+ "VALOR_MENSALIDADE": "100.00",
24
+ "IMPORTANCIA_SEGURADA": "50000",
25
+ "EMPRESA_RASTREAMENTO": "TRACKERLOG",
26
+ "CATEGORIA": "3",
27
+ "MODELO": "FH 540",
28
+ "RENAVAM": "12345678901",
29
+ "MARCA": "VOLVO",
30
+ "COR": "BRANCO",
31
+ "ANO": "2022",
32
+ "EIXOS": "3",
33
+ "CAMBIO": "AUTOMATICO",
34
+ "ALIENADO": "SIM",
35
+ }
36
+ row.update(overrides)
37
+ return row
38
+
39
+
40
+ def _build_item(rows):
41
+ df = pd.DataFrame(rows)
42
+ canonical = ApoliceBuilder().build(df)[0]
43
+ return InternalMapper().map(canonical).data.items[0]
44
+
45
+
46
+ def test_vehicle_details_mapped_from_spreadsheet_columns():
47
+ item = _build_item([_base_row()])
48
+
49
+ assert item.item_type == "veiculo"
50
+ assert item.brand_name == "VOLVO"
51
+ assert item.model_description == "FH 540"
52
+ assert item.brand == "VOLVO"
53
+ assert item.model == "FH 540"
54
+ assert item.vehicle_classification == 3
55
+ assert item.renavam == "12345678901"
56
+ assert item.color == "BRANCO"
57
+ assert item.model_year == 2022
58
+ assert item.manufacture_year == 2022
59
+ assert item.axles == 3
60
+ assert item.transmission == "AUTOMATICO"
61
+ assert item.is_alienated is True
62
+ assert item.has_vehicle_tracker is False
63
+ assert item.vehicle_tracker_company is None
64
+
65
+
66
+ def test_vehicle_tracker_company_only_when_has_tracker():
67
+ with_tracker = _build_item([
68
+ _base_row(NME_BENEFICIO="Casco PT (VEÍCULO)", VALOR_MENSALIDADE="100.00"),
69
+ _base_row(
70
+ NME_BENEFICIO="Rastreador p/ Veículo",
71
+ VALOR_MENSALIDADE="15.00",
72
+ EMPRESA_RASTREAMENTO="TRACKERLOG",
73
+ ),
74
+ ])
75
+ without_tracker = _build_item([_base_row()])
76
+
77
+ assert with_tracker.has_vehicle_tracker is True
78
+ assert with_tracker.vehicle_tracker_company == "TRACKERLOG"
79
+
80
+ assert without_tracker.has_vehicle_tracker is False
81
+ assert without_tracker.vehicle_tracker_company is None
82
+
83
+
84
+ def test_alienado_na_is_false():
85
+ item = _build_item([_base_row(ALIENADO="N/A")])
86
+
87
+ assert item.is_alienated is False