"""Saved case-specific reasons and missing documentary evidence for EVO review.""" from copy import deepcopy from document.mesa_content import content_for from document.fundamentacao import global_foundation, POINTS, number from decimal import Decimal, ROUND_HALF_UP from models.construction_identity import construction_identity def construction_key(row, index): return str(row.get('id') or row.get('seq') or index+1) def public_reason(obj, *keys): for key in keys: if obj.get(key + 'Interna') or obj.get(key + '_interna'): continue value = obj.get(key) if isinstance(value, str) and value.strip(): return value.strip() return '' def saved_complements(source): terrain = source.get('terreno') or {} evaluation = terrain.get('avaliacao') or {} ca = (evaluation.get('ajuste_territorial') or {}).get('configuracao', {}).get('campo_arbitrio') or {} manual = terrain.get('fundamentacaoModelo') or {} construction = source.get('construcao') or {} global_state = source.get('fundamentacaoGlobal') or {} result = { 'justificativa_vu': public_reason(ca, 'justificativa') or public_reason(terrain, 'justificativaVu', 'justificativaValorUnitario'), 'justificativa_area': public_reason(terrain, 'justificativaArea', 'areaAplicacaoJustificativa'), 'justificativa_fc': public_reason(global_state, 'justificativa', 'justificativaFc') or public_reason(source, 'justificativaFc', 'fatorComercializacaoJustificativa'), 'ajustes_nao_aplicados': public_reason(source, 'ajustesNaoAplicados'), 'criterio_adocao': public_reason(source, 'criterioAdocao'), 'fc_criterio': global_state.get('fatorComercializacao') or '', 'bdi': {construction_key(row, i): public_reason(row, 'bdiJustificativa', 'justificativaBdi') for i, row in enumerate(construction.get('construcoes') or [])}, 'caracterizacao_grau': (manual.get('caracterizacao') or {}).get('grau',''), 'caracterizacao_justificativa': public_reason(manual.get('caracterizacao') or {}, 'justificativa'), 'identificacao_grau': (manual.get('identificacaoDados') or {}).get('grau',''), 'identificacao_justificativa': public_reason(manual.get('identificacaoDados') or {}, 'justificativa'), 'requisitos_grau_iii': manual.get('requisitosGrauIII') or {}, } # Future EVO files may preserve these exact documentary fields as a package. saved = source.get('complementosDocumentais') or {} for key in result: if saved.get(key): result[key] = saved[key] return result def apply_complements(source, values): source = deepcopy(source) terrain = source.setdefault('terreno', {}) state = terrain.setdefault('fundamentacaoModelo', {}) for prefix, key in [('caracterizacao','caracterizacao'),('identificacao','identificacaoDados')]: selected = values.get(prefix+'_grau', '') if selected and selected not in POINTS: raise ValueError('Grau de fundamentação inválido.') state[key] = {'grau':selected,'justificativa':values.get(prefix+'_justificativa','')} allowed = {item['value'] for item in content_for(source)['criterios_fc']} if values.get('fc_criterio') and values['fc_criterio'] not in allowed: raise ValueError('Critério do fator de comercialização inválido.') reqs = values.get('requisitos_grau_iii') or {} allowed_reqs = {item['key'] for item in content_for(source)['requisitos_modelo']} if set(reqs) - allowed_reqs or any(v not in ('','sim','nao',True,False) for v in reqs.values()): raise ValueError('Requisitos do Grau III inválidos.') state['requisitosGrauIII'] = reqs source.setdefault('fundamentacaoGlobal', {})['fatorComercializacao'] = values.get('fc_criterio','') source['complementosDocumentais'] = deepcopy(values) return source def review_for(source, complements): terrain = source.get('terreno') or {} area_model = terrain.get('areaModelo', (terrain.get('avaliacao') or {}).get('valor_area')) area_apply = terrain.get('areaAplicacao', terrain.get('area')) pending = [] for key, label, required in [ ('justificativa_vu','Escolha do valor unitário / campo de arbítrio',True), ('justificativa_area','Diferença entre área do modelo e área de aplicação',number(area_model) != number(area_apply)), ('justificativa_fc','Escolha do fator de comercialização',True), ]: if required and not complements.get(key): pending.append(label) constructions = [] construction = source.get('construcao') or {} for index, row in enumerate(construction.get('construcoes') or []): key = construction_key(row,index) name = row.get('nome') or f"Construção {row.get('seq') or index+1} — {row.get('tipoConstrucao') or 'tipo não informado'}" constructions.append({'id':key,'nome':name,'bdi':row.get('bdi'),'grupo':construction_identity(source,row,index)}) if not complements.get('bdi',{}).get(key): pending.append(f'BDI: {name}') foundation = global_foundation(source) for label, item in [('Terreno',foundation['terrain']),('Construções',foundation['construction']),('Global',foundation)]: if item['grade'] == 'Pendente': pending.append(f'Fundamentação {label.lower()}: há critérios não informados') return {'pendencias':pending,'construcoes':constructions,'area_modelo':area_model,'area_aplicacao':area_apply, 'conferencias': check_saved_results(source), 'fundamentacao': {k:foundation[k]['grade'] for k in ['terrain','construction']} | {'global':foundation['grade']}, 'criterios_fc':content_for(source)['criterios_fc'],'requisitos_modelo':content_for(source)['requisitos_modelo']} def check_saved_results(source): """Report inconsistencies using saved precision; never repair source values.""" warnings = [] result = source.get('resultadoComposicao') or {} def check(label, expected, actual): actual = number(actual) if expected is not None and actual is not None: expected = expected.quantize(Decimal('.01'),rounding=ROUND_HALF_UP) if abs(expected-actual) > Decimal('.02'): warnings.append(f'{label}: os dados salvos não reproduzem o resultado registrado (conferência {expected}; registro {actual}). O resultado de origem foi preservado.') terrain = source.get('terreno') or {} vu = number(result.get('valorUnitarioTerreno',result.get('valorCa',terrain.get('valorUnitario')))) area = number(terrain.get('areaAplicacao',terrain.get('area'))) check('Valor territorial',vu*area if vu is not None and area is not None else None,result.get('valorTerrenoPgv')) construction_values = [] for i, item in enumerate((source.get('construcao') or {}).get('construcoes') or []): a,c,bdi,kd = [number(item.get(key)) for key in ['areaCalculada','custoUnitario','bdi','kd']] check(f"Construção {item.get('seq') or i+1}",a*c*(1+bdi/100)*kd if all(v is not None for v in (a,c,bdi,kd)) else None,item.get('vcalcConstSeq')) construction_values.append(number(item.get('vcalcConstSeq'))) if construction_values and all(v is not None for v in construction_values): check('Total das construções',sum(construction_values),result.get('valorConstrucao')) base,fc = number(result.get('valorAposRedutoresImovel')),number(result.get('fatorFcAplicado')) check('Aplicação final do Fc',base*fc if base is not None and fc is not None else None,result.get('valorTotalCalculado')) return warnings