import copy import unittest from decimal import Decimal from unittest.mock import patch from docx import Document from document.evo_adjustments import applied_reducers, visible_memory from document.evolutivo import gerar_corpo_evolutivo, condition_description, construction_characteristics, construction_groups from document.avaliacoes_importadas import calculation_sections from document.numbering import NumeradorSecoes from test_evolutivo_documento import fixture, resolved, all_text def body(imported): doc=Document(); n=NumeradorSecoes(3); n.subsecao(); n.subsecao() with patch('document.evolutivo.matching_local',return_value=(None,'')): gerar_corpo_evolutivo(doc,imported,n) return doc class AppliedAdjustmentsTests(unittest.TestCase): def test_area_compositions_share_one_table_with_construction_names(self): data=fixture((2021,2022)) for year in data['anos']: first=year['construcao']['construcoes'][0] first.update(nome='Casa principal',areaTotal=90,areaCalculada=87.5, areasEquivalentes=[{'descricao':'área privativa','area':80,'coeficiente':1}, {'descricao':'varandas','area':10,'coeficiente':.75}]) second=copy.deepcopy(first) second.update(id='segundo',seq=2,nome='Edícula de apoio',areaTotal=50,areaCalculada=45, areasEquivalentes=[{'descricao':'área privativa','area':40,'coeficiente':1}, {'descricao':'área de serviço','area':10,'coeficiente':.5}]) year['construcao']['construcoes'].append(second) doc=Document() construction_characteristics(doc,construction_groups(resolved(data)['avaliacoes'])) tables=[t for t in doc.tables if t.rows[0].cells[1].text=='Tipo de área'] self.assertEqual(len(tables),1) rows=[[c.text for c in row.cells] for row in tables[0].rows] self.assertEqual(rows[1:], [ ['Construção 1 — Casa principal','área privativa','80,00','1,0000','80,00'], ['Construção 1 — Casa principal','varandas','10,00','0,7500','7,50'], ['Construção 2 — Edícula de apoio','área privativa','40,00','1,0000','40,00'], ['Construção 2 — Edícula de apoio','área de serviço','10,00','0,5000','5,00']]) self.assertEqual([p.text for p in doc.paragraphs if p.text.startswith('Composição das áreas')],['Composição das áreas']) def test_area_changes_preserve_exercise_scope_in_the_unified_table(self): data=fixture((2021,2022)) data['anos'][1]['construcao']['construcoes'][0]['areasEquivalentes'][0]['coeficiente']=.8 doc=Document() construction_characteristics(doc,construction_groups(resolved(data)['avaliacoes'])) table=next(t for t in doc.tables if t.rows[0].cells[1].text=='Tipo de área') self.assertEqual(len(table.rows),3) self.assertIn('IPTU 2022',table.cell(1,0).text) self.assertIn('IPTU 2023',table.cell(2,0).text) self.assertEqual(table.cell(1,4).text,'75,00') self.assertEqual(table.cell(2,4).text,'80,00') def test_neutral_execution_overrides_selected_conditions_and_reference_rates(self): row=resolved(fixture((2021,)))['avaliacoes'][0] row['evolutivo']['resultadoComposicao']['redutor88I']=.7 self.assertEqual(applied_reducers(row,True),[]) self.assertNotIn('redutores_terreno',[s['codigo'] for s in visible_memory(row)]) self.assertIn('redutores_imovel',[s['codigo'] for s in visible_memory(row)]) def test_legacy_unknown_effect_remains_visible_without_inventing_rate(self): row={'evolutivo':{'resultadoComposicao':{'memoria':[{'codigo':'redutores_terreno','resultado':10}]}}} self.assertEqual(applied_reducers(row,True),[('redutorTerreno','Redução territorial registrada',None)]) self.assertEqual(len(visible_memory(row)),1) def test_component_fallback_uses_effective_saved_memory(self): row={'evolutivo':{'resultadoComposicao':{'memoria':[{'codigo':'redutores_terreno','fator':.895,'componentes':[ {'rotulo':'Art. 88, inciso I','reducao':.105},{'rotulo':'Topografia','reducao':0}]}]}}} self.assertEqual(applied_reducers(row,True),[('redutor88I','Art. 88, inciso I',Decimal('.105'))]) def test_only_effective_reducers_and_years_appear_in_body_and_annex(self): data=fixture((2021,2022)) for year in data['anos']: r=year['resultadoComposicao']; r['memoria'][4]['fator']=1 r.update(redutor88I=0,redutor6061=0,redutorTopografia=0,redutorAgua=0,redutor88II=0,redutorImovel=0,redutorApan=0,limiteArt88Aplicado=False) r=data['anos'][0]['resultadoComposicao'];r['redutor88I']=.105;r['memoria'][0].update(fator=.895,componentes=[{'rotulo':'Art. 88, inciso I','reducao':.105},{'rotulo':'Topografia','reducao':0}]) imported=resolved(data); before=copy.deepcopy(imported) doc=body(imported);text=all_text(doc) for absent in ['Topografia:','Inundação — aplicado','Arts. 60 e 61:','Limite do art. 88','Art. 88, inciso II','APAN considerada no campo','Componentes de referência da MESA:']: self.assertNotIn(absent,text) t=next(t for t in doc.tables if any(c.text=='Art. 88, inciso I — redução efetiva' for r in t.rows for c in r.cells)) reduction=next(r for r in t.rows if r.cells[0].text=='Art. 88, inciso I — redução efetiva') self.assertEqual([c.text for c in reduction.cells[1:]],['10,50%','—']) ann=Document();calculation_sections(ann,imported) self.assertNotIn('Redução do imóvel',all_text(ann));self.assertEqual(imported,before) def test_inciso_i_excludes_areas_at_or_below_historical_threshold(self): row=resolved(fixture((2021,)))['avaliacoes'][0] row['evolutivo']['resultadoComposicao']['memoria'][0]['fator']=.895 row['evolutivo']['resultadoComposicao']['redutor88I']=.105 row['evolutivo']['catalogoAjustes']={'art_88_i':{'limite_minimo_area':.1,'condicoes':[ {'codigo':'app','rotulo':'APP aplicada','redutor':.7},{'codigo':'outra','rotulo':'Área não participante','redutor':.85}]}} row['evolutivo']['terreno']['avaliacao']['ajuste_territorial']['configuracao']['art_88_i']={'condicoes':{'app':{'percentual_area':.15},'outra':{'percentual_area':.1}}} text=condition_description(row,True) self.assertIn('APP aplicada',text);self.assertIn('10,50%',text);self.assertNotIn('Área não participante',text) def test_6061_describes_only_maximum_selected_condition(self): row={'evolutivo':{'resultadoComposicao':{'redutor6061':.6},'catalogoAjustes':{'art_60_61':{'condicoes':[ {'codigo':'vila','rotulo':'Vila menor','redutor':.3},{'codigo':'encravado','rotulo':'Encravado aplicado','redutor':.6}]}}, 'terreno':{'avaliacao':{'ajuste_territorial':{'configuracao':{'art_60_61':{'selecionados':['vila','encravado']}}}}}}} text=condition_description(row,True) self.assertIn('Encravado aplicado',text);self.assertNotIn('Vila menor',text) def test_applied_cap_is_documented_with_effective_not_gross_percentage(self): imported=resolved(fixture((2021,)));r=imported['avaliacoes'][0]['evolutivo']['resultadoComposicao'] r.update(redutor88II=.2,redutor88IIBruto=.6,limiteArt88Aplicado=True,valorReferenciaArt88=1000,valorAposIncisoIArt88=187.5,valorAposArt88Bruto=75,valorAposArt88Aplicado=150) r['memoria'][4]['fator']=.8 imported['avaliacoes'][0]['evolutivo']['catalogoAjustes']={'limite_reducao_art_88':.85} text=all_text(body(imported)) self.assertIn('limite conjunto dos incisos I e II do art. 88, previsto em seu § 3º, foi efetivamente acionado',text) self.assertIn('20,00%',text);self.assertNotIn('60,00%',text);self.assertIn('R$ 150,00',text) self.assertIn('85,00%',text);self.assertIn('15,00%',text) def test_composite_cub_and_bdi_tables_deduplicate_across_constructions(self): data=fixture((2021,2022)); for y in data['anos']: c=copy.deepcopy(y['construcao']['construcoes'][0]);c.update(id='segundo',seq=2,nome='Galpão B') y['construcao']['construcoes'].append(c) doc=body(resolved(data)) weighted=[t for t in doc.tables if t.rows[0].cells[1].text=='Projeto CUB'] self.assertEqual(len(weighted),1);self.assertEqual(len(weighted[0].rows),3) self.assertEqual(weighted[0].cell(1,1).text,'CUB composto') self.assertEqual([c.text for c in weighted[0].rows[0].cells][2:],['IPTU 2022','IPTU 2023']) calculations=[t for t in doc.tables if any(r.cells[0].text=='Valor da construção (R$)' for r in t.rows)] self.assertEqual(len(calculations),2) for t in calculations: for label in ['Projeto CUB','CUB (R$/m²)','BDI (%)','Fator BDI','Depreciação K','Kd']: self.assertTrue(any(r.cells[0].text==label for r in t.rows)) def test_changes_in_characteristics_and_compositions_keep_applicability(self): data=fixture((2021,2022));data['anos'][1]['construcao']['construcoes'][0].update(areaCalculada=80,valorResidual=.2) data['anos'][1]['construcao']['construcoes'][0]['cubComposicaoCalculada'][0]['peso']=50 doc=body(resolved(data)) t=next(t for t in doc.tables if t.rows[0].cells[1].text=='Tipo / ano') self.assertEqual(len(t.rows),3) self.assertIn('IPTU 2023',t.rows[2].cells[0].text) t=next(t for t in doc.tables if t.rows[0].cells[1].text=='Projeto CUB') self.assertEqual(len(t.rows),2) from docx.opc.constants import RELATIONSHIP_TYPE as RT from docx.oxml import parse_xml text=' '.join(parse_xml(doc.part.part_related_by(RT.FOOTNOTES).blob).itertext()) for expected in ['IPTU 2022','IPTU 2023','60,00%','50,00%']: self.assertIn(expected,text)