File size: 7,626 Bytes
55d9355
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
"""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