transdesk-importer-python-sdk 1.2.0__tar.gz → 1.3.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.2.0 → transdesk_importer_python_sdk-1.3.0}/PKG-INFO +2 -2
  2. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/README.md +1 -1
  3. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/pyproject.toml +1 -1
  4. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/models/canonical.py +5 -0
  5. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/models/internal.py +3 -1
  6. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/shared/data_normalizer.py +8 -0
  7. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/canonical/apolice_builder.py +21 -2
  8. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/internal/coverage_mapper.py +12 -6
  9. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/internal/internal_mapper.py +32 -1
  10. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/validators/internal_policy_validator.py +5 -1
  11. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk_importer_python_sdk.egg-info/PKG-INFO +2 -2
  12. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/tests/test_importer.py +4 -2
  13. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/tests/test_vehicle_details.py +56 -4
  14. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/LICENSE +0 -0
  15. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/setup.cfg +0 -0
  16. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/__init__.py +0 -0
  17. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/client.py +0 -0
  18. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/exporters/__init__.py +0 -0
  19. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/exporters/json_exporter.py +0 -0
  20. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/models/__init__.py +0 -0
  21. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/readers/__init__.py +0 -0
  22. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/readers/excel_reader.py +0 -0
  23. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/shared/__init__.py +0 -0
  24. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/__init__.py +0 -0
  25. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/canonical/__init__.py +0 -0
  26. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/canonical/cobertura_builder.py +0 -0
  27. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/canonical/item_classifier.py +0 -0
  28. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/internal/__init__.py +0 -0
  29. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/internal/rcf_parser.py +0 -0
  30. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/transformers/internal/towing_parser.py +0 -0
  31. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk/importer/validators/__init__.py +0 -0
  32. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk_importer_python_sdk.egg-info/SOURCES.txt +0 -0
  33. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk_importer_python_sdk.egg-info/dependency_links.txt +0 -0
  34. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk_importer_python_sdk.egg-info/requires.txt +0 -0
  35. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/src/transdesk_importer_python_sdk.egg-info/top_level.txt +0 -0
  36. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/tests/test_data_normalizer.py +0 -0
  37. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.0}/tests/test_excel_reader.py +0 -0
  38. {transdesk_importer_python_sdk-1.2.0 → transdesk_importer_python_sdk-1.3.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.2.0
3
+ Version: 1.3.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
@@ -117,7 +117,7 @@ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
117
117
  | Coluna planilha | Campo `InternalItem` | Observacao |
118
118
  | --------------- | -------------------- | ---------- |
119
119
  | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
120
- | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
120
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (texto) |
121
121
  | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
122
122
  | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
123
123
  | `RENAVAM` | `renavam` | |
@@ -74,7 +74,7 @@ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
74
74
  | Coluna planilha | Campo `InternalItem` | Observacao |
75
75
  | --------------- | -------------------- | ---------- |
76
76
  | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
77
- | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
77
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (texto) |
78
78
  | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
79
79
  | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
80
80
  | `RENAVAM` | `renavam` | |
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "transdesk-importer-python-sdk"
7
- version = "1.2.0"
7
+ version = "1.3.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"
@@ -81,6 +81,11 @@ class CanonicalItem:
81
81
  dados_risco: DadosRisco
82
82
  coberturas: List[Cobertura] = field(default_factory=list)
83
83
  complementos: Optional[List[Complemento]] = None
84
+ # Espelho do prêmio/IS de COMPLEMENTO (também somado no casco via merge).
85
+ mirrored_body_value: float = 0
86
+ mirrored_body_premium: float = 0
87
+ mirrored_repair_assistance_value: float = 0
88
+ mirrored_repair_assistance_premium: float = 0
84
89
 
85
90
 
86
91
  @dataclass
@@ -80,6 +80,8 @@ class InternalItem:
80
80
  # Carroceria / Complemento / Equipamento
81
81
  body_value: float = 0
82
82
  body_premium: float = 0
83
+ # True quando body_*/repair_assistance_* são espelho já embutido em hull_premium.
84
+ equipment_premium_included_in_hull: bool = False
83
85
 
84
86
  # Reparos
85
87
  has_repair_assistance: bool = False
@@ -135,7 +137,7 @@ class InternalItem:
135
137
  model: Optional[str] = None
136
138
  brand_name: Optional[str] = None
137
139
  model_description: Optional[str] = None
138
- vehicle_classification: Optional[int] = None
140
+ vehicle_classification: Optional[str] = None
139
141
  renavam: Optional[str] = None
140
142
  color: Optional[str] = None
141
143
  model_year: Optional[int] = None
@@ -49,6 +49,14 @@ class DataNormalizer:
49
49
  except Exception:
50
50
  return None
51
51
 
52
+ @staticmethod
53
+ def to_str(value):
54
+ if value in (None, ""):
55
+ return None
56
+
57
+ normalized = str(value).strip()
58
+ return normalized or None
59
+
52
60
  @staticmethod
53
61
  def to_bool(value, na_values=("N/A", "n/a", "")):
54
62
  if value is None:
@@ -256,6 +256,10 @@ class ApoliceBuilder:
256
256
  for row in rows:
257
257
  descricao = row.get("COMPLEMENT_DESCRIPTION")
258
258
 
259
+ if not descricao:
260
+ continue
261
+
262
+ descricao = str(descricao).strip()
259
263
  if not descricao:
260
264
  continue
261
265
 
@@ -277,19 +281,30 @@ class ApoliceBuilder:
277
281
  # ---------------------------------------------------------
278
282
 
279
283
  def merge_complement_coverages(self, item):
284
+ """Soma prêmio de COMPLEMENTO no casco e espelha em mirrored_* para visibilidade."""
280
285
  casco_extra = 0
286
+ body_premium = 0
287
+ body_value = 0
288
+ repair_premium = 0
289
+ repair_value = 0
281
290
 
282
291
  novas = []
283
292
 
284
293
  for c in item.coberturas:
285
294
  nome = c.nome
295
+ premium = c.valor_mensalidade or 0
296
+ is_val = c.importancia_segurada or 0
286
297
 
287
298
  if nome == "Casco PT (COMPLEMENTO)":
288
- casco_extra += (c.valor_mensalidade or 0)
299
+ casco_extra += premium
300
+ body_premium += premium
301
+ body_value += is_val
289
302
  continue
290
303
 
291
304
  if nome == "Assistência a Reparos (COMPLEMENTO)":
292
- casco_extra += (c.valor_mensalidade or 0)
305
+ casco_extra += premium
306
+ repair_premium += premium
307
+ repair_value += is_val
293
308
  continue
294
309
 
295
310
  novas.append(c)
@@ -299,3 +314,7 @@ class ApoliceBuilder:
299
314
  c.valor_mensalidade = ((c.valor_mensalidade or 0) + casco_extra)
300
315
 
301
316
  item.coberturas = novas
317
+ item.mirrored_body_premium = body_premium
318
+ item.mirrored_body_value = body_value
319
+ item.mirrored_repair_assistance_premium = repair_premium
320
+ item.mirrored_repair_assistance_value = repair_value
@@ -106,23 +106,29 @@ class CoverageMapper:
106
106
  item.has_rcf = True
107
107
 
108
108
  parsed = self.rcf_parser.parse(coverage.produto)
109
+ dma_dc = parsed["material_damage_value"] or 0
110
+ dmo = parsed["moral_damage_value"] or 0
109
111
 
112
+ # Rateio do prêmio com pesos cheios (comportamento atual).
110
113
  premiums = self._allocate_premium(
111
114
  coverage.valor_mensalidade or 0,
112
115
  {
113
- "property_damage": parsed["material_damage_value"],
114
- "bodily_injury": parsed["bodily_injury_value"],
115
- "moral_damage": parsed["moral_damage_value"],
116
+ "property_damage": dma_dc,
117
+ "bodily_injury": dma_dc,
118
+ "moral_damage": dmo,
116
119
  },
117
120
  )
118
121
 
119
- item.material_damage_value += (parsed["material_damage_value"])
122
+ # IS de DMA/DC dividida entre as duas coberturas (não duplicar o limite).
123
+ split_is = (dma_dc / 2.0) if dma_dc else 0
124
+
125
+ item.material_damage_value += split_is
120
126
  item.property_damage_premium += (premiums["property_damage"])
121
127
 
122
- item.bodily_injury_value += (parsed["bodily_injury_value"])
128
+ item.bodily_injury_value += split_is
123
129
  item.bodily_injury_premium += (premiums["bodily_injury"])
124
130
 
125
- item.moral_damage_value += (parsed["moral_damage_value"])
131
+ item.moral_damage_value += dmo
126
132
  item.moral_damage_premium += (premiums["moral_damage"])
127
133
 
128
134
  def _apply_towing(self, item, coverage):
@@ -170,6 +170,7 @@ class InternalMapper:
170
170
  )
171
171
 
172
172
  self.coverage_mapper.apply_all(internal_item, item.coberturas)
173
+ self._apply_mirrored_equipment(internal_item, item)
173
174
 
174
175
  if item.tipo_item == "vida":
175
176
  self._map_life(internal_item, item)
@@ -178,6 +179,36 @@ class InternalMapper:
178
179
 
179
180
  return internal_item
180
181
 
182
+ def _apply_mirrored_equipment(self, internal_item, item):
183
+ """Espelha COMPLEMENTO já mergeado no casco em body_*/repair_* (visibilidade)."""
184
+ body_premium = getattr(item, "mirrored_body_premium", 0) or 0
185
+ body_value = getattr(item, "mirrored_body_value", 0) or 0
186
+ repair_premium = getattr(item, "mirrored_repair_assistance_premium", 0) or 0
187
+ repair_value = getattr(item, "mirrored_repair_assistance_value", 0) or 0
188
+
189
+ if body_premium or repair_premium or body_value or repair_value:
190
+ internal_item.body_premium += body_premium
191
+ internal_item.body_value += body_value
192
+ if repair_premium or repair_value:
193
+ internal_item.has_repair_assistance = True
194
+ internal_item.repair_assistance_premium += repair_premium
195
+ internal_item.repair_assistance_value += repair_value
196
+ if body_premium or repair_premium:
197
+ internal_item.equipment_premium_included_in_hull = True
198
+
199
+ if internal_item.body_value <= 0 and item.complementos:
200
+ seen = set()
201
+ total_is = 0
202
+ for comp in item.complementos:
203
+ amount = comp.importancia_segurada or 0
204
+ key = ((comp.descricao or "").strip(), amount)
205
+ if key in seen:
206
+ continue
207
+ seen.add(key)
208
+ total_is += amount
209
+ if total_is > 0:
210
+ internal_item.body_value = total_is
211
+
181
212
  def _map_life(self, internal_item, item):
182
213
  risco = item.dados_risco
183
214
 
@@ -207,7 +238,7 @@ class InternalMapper:
207
238
  internal_item.brand_name = marca
208
239
  internal_item.model_description = modelo
209
240
 
210
- internal_item.vehicle_classification = DataNormalizer.to_int(
241
+ internal_item.vehicle_classification = DataNormalizer.to_str(
211
242
  getattr(risco, "categoria", None)
212
243
  )
213
244
  internal_item.renavam = DataNormalizer.clean_doc(
@@ -67,9 +67,13 @@ class InternalPolicyValidator:
67
67
 
68
68
  def _calculate_internal_premium(self, internal_item):
69
69
  total = 0
70
+ skip = set()
71
+ if getattr(internal_item, "equipment_premium_included_in_hull", False):
72
+ # body_*/repair_* são espelho do que já entrou em hull_premium.
73
+ skip.update({"body_premium", "repair_assistance_premium"})
70
74
 
71
75
  for key, value in asdict(internal_item).items():
72
- if key.endswith("_premium"):
76
+ if key.endswith("_premium") and key not in skip:
73
77
  total += (value or 0)
74
78
 
75
79
  return total
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: transdesk-importer-python-sdk
3
- Version: 1.2.0
3
+ Version: 1.3.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
@@ -117,7 +117,7 @@ Campos adicionais de veiculo/reboque (colunas apos `CHAVE_PIX` na planilha):
117
117
  | Coluna planilha | Campo `InternalItem` | Observacao |
118
118
  | --------------- | -------------------- | ---------- |
119
119
  | `EMPRESA_RASTREAMENTO` | `vehicle_tracker_company` | Somente quando `has_vehicle_tracker` |
120
- | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (int) |
120
+ | `CATEGORIA` | `vehicle_classification` | Classificacao do veiculo (texto) |
121
121
  | `MARCA` | `brand_name` / `brand` | Veiculo; reboque usa `MARCA_REBOQUE` |
122
122
  | `MODELO` | `model_description` / `model` | Veiculo; reboque usa `MODELO_REBOQUE` |
123
123
  | `RENAVAM` | `renavam` | |
@@ -41,8 +41,10 @@ def test_policy_structure(apolices_bytes):
41
41
  assert item.item_number == 1
42
42
  assert item.item_type == "veiculo"
43
43
  assert item.hull_premium == 687.12
44
- assert item.repair_assistance_premium == 0
45
- assert item.has_repair_assistance is False
44
+ assert item.body_premium == 25.64
45
+ assert item.repair_assistance_premium == 21.86
46
+ assert item.has_repair_assistance is True
47
+ assert item.equipment_premium_included_in_hull is True
46
48
  assert item.group_name == "COMBO II"
47
49
 
48
50
 
@@ -23,7 +23,7 @@ def _base_row(**overrides):
23
23
  "VALOR_MENSALIDADE": "100.00",
24
24
  "IMPORTANCIA_SEGURADA": "50000",
25
25
  "EMPRESA_RASTREAMENTO": "TRACKERLOG",
26
- "CATEGORIA": "3",
26
+ "CATEGORIA": "PESADO - TRÊS QUARTOS",
27
27
  "MODELO": "FH 540",
28
28
  "RENAVAM": "12345678901",
29
29
  "MARCA": "VOLVO",
@@ -51,7 +51,7 @@ def test_vehicle_details_mapped_from_spreadsheet_columns():
51
51
  assert item.model_description == "FH 540"
52
52
  assert item.brand == "VOLVO"
53
53
  assert item.model == "FH 540"
54
- assert item.vehicle_classification == 3
54
+ assert item.vehicle_classification == "PESADO - TRÊS QUARTOS"
55
55
  assert item.renavam == "12345678901"
56
56
  assert item.color == "BRANCO"
57
57
  assert item.model_year == 2022
@@ -175,7 +175,59 @@ def test_repair_assistance_consolidates_into_hull_premium():
175
175
  ),
176
176
  ])
177
177
 
178
+ # Casco continua incluindo complemento + reparos veículo.
178
179
  assert item.hull_premium == 687.12
179
180
  assert item.hull_value == 308130
180
- assert item.repair_assistance_premium == 0
181
- assert item.has_repair_assistance is False
181
+ # Espelho de equipamento/complemento para visibilidade (já embutido no casco).
182
+ assert item.body_premium == 25.64
183
+ assert item.body_value == 27000
184
+ assert item.repair_assistance_premium == 21.86
185
+ assert item.repair_assistance_value == 27000
186
+ assert item.has_repair_assistance is True
187
+ assert item.equipment_premium_included_in_hull is True
188
+
189
+
190
+ def test_complement_description_dedupes_with_strip():
191
+ item = _build_item([
192
+ _base_row(
193
+ NME_BENEFICIO="Casco PT (VEÍCULO)",
194
+ VALOR_MENSALIDADE="100.00",
195
+ IMPORTANCIA_SEGURADA="50000",
196
+ COMPLEMENT_DESCRIPTION="sider ",
197
+ ),
198
+ _base_row(
199
+ NME_BENEFICIO="Assistência a Reparos (VEÍCULO)",
200
+ VALOR_MENSALIDADE="50.00",
201
+ IMPORTANCIA_SEGURADA="50000",
202
+ COMPLEMENT_DESCRIPTION="sider",
203
+ ),
204
+ ])
205
+
206
+ assert item.body_description == "sider (R$ 50.000,00)"
207
+ assert item.body_value == 50000
208
+
209
+
210
+ def test_rcf_splits_insured_amount_between_dm_and_dc():
211
+ item = _build_item([
212
+ _base_row(
213
+ NME_BENEFICIO="Casco PT (VEÍCULO)",
214
+ VALOR_MENSALIDADE="100.00",
215
+ IMPORTANCIA_SEGURADA="50000",
216
+ PRODUTO="",
217
+ ),
218
+ _base_row(
219
+ NME_BENEFICIO="RCF",
220
+ VALOR_MENSALIDADE="255.36",
221
+ IMPORTANCIA_SEGURADA="550000",
222
+ PRODUTO="DMA E DC 500 MIL/DMO 50 MIL",
223
+ ),
224
+ ])
225
+
226
+ assert item.has_rcf is True
227
+ assert item.material_damage_value == 250000
228
+ assert item.bodily_injury_value == 250000
229
+ assert item.moral_damage_value == 50000
230
+ # Prêmio rateado com pesos cheios (500+500+50).
231
+ assert round(item.property_damage_premium, 2) == 121.6
232
+ assert round(item.bodily_injury_premium, 2) == 121.6
233
+ assert round(item.moral_damage_premium, 2) == 12.16