#!/usr/bin/env python3
"""Coleta e analise de precos praticados em contratos publicos (PNCP).

Reproduz a base de docs/PESQUISA-PRECOS.md. Roda sem dependencia externa.

    python3 coleta-pncp.py            # coleta e salva pncp-contratos-AAAA-MM-DD.json
    python3 coleta-pncp.py --analisa  # le o arquivo existente e imprime a tabela
    python3 coleta-pncp.py --objetos "Prestação de contas / TCE" --uf DF,GO
                                      # lista os objetos de uma linha, um a um, para leitura
    python3 coleta-pncp.py --gera-json # reescreve src/data/estudo-precos.json (o que o site publica)
    python3 coleta-pncp.py --anonimiza-base  # reescreve a base ja coletada tirando CPF e nome

O que NAO e guardado: nome de fornecedor, nome de servidor citado no objeto, nem CPF. Boa parte
e pessoa fisica, e a pesquisa e de preco, nao de gente — a mesma regra que vale nas ferramentas
civicas do titular. A funcao anonimiza() abaixo faz essa limpeza antes de gravar; use tambem
`--anonimiza-base` para reescrever uma base ja coletada.
"""
import json, re, sys, time, urllib.parse, urllib.request
from datetime import date
from collections import Counter

BASE = "https://pncp.gov.br/api/search/?"
CONSULTAS = [
    "capacitação servidores públicos", "curso inteligência artificial servidores",
    "capacitação lei 14.133 licitações contratos", "treinamento elaboração termo de referência",
    "consultoria transparência pública", "consultoria lei de acesso à informação",
    "implantação portal da transparência", "assessoria técnica câmara municipal",
    "consultoria gestão pública planejamento", "elaboração de estudo técnico diagnóstico",
    "instrutoria curso presencial servidores", "curso capacitação servidores prefeitura",
    "assessoria contábil prestação de contas município",
    "assessoria prestação de contas tribunal de contas",
]


# ---------------------------------------------------------------------------
# Anonimizacao do campo "objeto".
#
# O cabecalho deste arquivo sempre prometeu que nenhum nome de fornecedor seria guardado — e a
# promessa valia para o campo de fornecedor, que nunca foi coletado. Mas o OBJETO do contrato,
# escrito por quem contrata, frequentemente traz nome de servidor inscrito em curso e, em dois
# casos da base de 31/08/2026, CPF em claro. A auditoria de seguranca de 04/09/2026 achou isso.
#
# A pesquisa e de preco, nao de gente: o objeto so serve para classificar a LINHA de servico, e
# nenhuma expressao regular de classificacao depende de nome de pessoa. Entao o objeto passa por
# aqui antes de ser gravado — e a base ja coletada foi reescrita com esta mesma funcao.
CNPJ_RE = re.compile(r"\b\d{2}\.\d{3}\.\d{3}/\d{4}-\d{2}\b")
CPF_RE = re.compile(r"\b\d{3}\.?\d{3}\.?\d{3}-?\d{2}\b")
# Depois destes marcadores costuma vir nome de pessoa (um, ou uma lista separada por virgula e "e").
MARCADOR_RE = re.compile(
    r"(?i)\b(servidor(?:es|as|a)?|servidor\(a\)|sr\.?|sra\.?|senhor(?:a)?|"
    r"inscri[çc][ãa]o d[oae]s?|inscri[çc][õo]es d[oae]s?|participa[çc][ãa]o d[oae]s?|"
    r"representad[oa] (?:legalmente )?pel[oa]|aluno(?:s)?|discente(?:s)?|matr[íi]cula d[oae]|sendo)"
    r"\s*[:,-]?\s*"
)
CONECTOR = {"de", "da", "do", "das", "dos", "e", "dr", "dra"}
# Palavras que NAO sao nome proprio de pessoa. Em texto todo em maiusculas tudo "parece" nome —
# esta lista e o que impede a captura de engolir meia frase.
PARE = set("""a o as os um uma no na nos nas ao aos em por para com sem sob apos conforme referente
lotado lotada lotados lotadas participar participacao curso cursos capacitacao treinamento evento
oficina congresso seminario workshop turma inscricao inscricoes taxa empenho pagamento contratacao
contratado contratada empresa organizacao instituicao secretaria secretario secretaria municipal
estadual federal distrital diretoria departamento setor divisao coordenacao gerencia superintendencia
subsecretaria prefeitura camara assembleia fundo instituto universidade faculdade escola tribunal
conselho ministerio agencia autarquia fundacao consorcio processo inexigibilidade dispensa pregao
licitacao contrato contratos termo valor total dias dia periodo mes meses ano anos realizado realizar
realizacao modalidade presencial online remoto cidade estado brasilia goiania sao rio belo porto
publico publica gestao fiscalizacao transparencia inteligencia artificial lei nº n cpf cnpj
codigo objeto item itens fls conforme anexo""".split())

def _sem_acento(p):
    import unicodedata
    return "".join(c for c in unicodedata.normalize("NFD", p) if unicodedata.category(c) != "Mn")

TOKEN_RE = re.compile(r"[^\s,;:()\[\]/]+")

def anonimiza(objeto):
    """Remove CPF e nome de pessoa do objeto do contrato. Nao toca em CNPJ nem em nome de orgao.

    Depois de um marcador ("servidor", "inscricao do", "Sra."), consome a LISTA de nomes que vier —
    palavras iniciadas em maiuscula, ligadas por conectores, separadas por virgula ou "e" — parando
    na primeira palavra que seja vocabulario administrativo (PARE) ou que comece em minuscula.
    """
    if not objeto:
        return objeto
    guardados = []
    def guarda(m):
        guardados.append(m.group(0)); return f"\x00{len(guardados)-1}\x00"
    t = CNPJ_RE.sub(guarda, objeto)          # CNPJ e de pessoa juridica: fica
    t = CPF_RE.sub("[CPF removido]", t)

    def e_nome(palavra):
        base = _sem_acento(palavra.strip(".,;:()")).lower()
        if not base or base in PARE or base in CONECTOR:
            return False
        return palavra[:1].isupper()

    saida, i = [], 0
    for m in MARCADOR_RE.finditer(t):
        if m.start() < i:
            continue
        j, achou, palavras = m.end(), False, 0
        while palavras < 24:
            while j < len(t) and t[j] in " ,;":     # separadores entre nomes de uma lista
                j += 1
            tok = TOKEN_RE.match(t, j)
            if not tok:
                break
            palavra = tok.group(0)
            base = _sem_acento(palavra.strip(".,;:()")).lower()
            if base in CONECTOR:
                if not achou:
                    break
                # conector so vale se depois dele vier outro nome
                seg = TOKEN_RE.match(t, tok.end() + 1)
                if not seg or not e_nome(seg.group(0)):
                    break
                j = tok.end(); palavras += 1; continue
            if not e_nome(palavra):
                break
            j = tok.end(); palavras += 1; achou = True
        if achou:
            saida.append(t[i:m.end()]); saida.append("[nome removido] ")
            i = j
    saida.append(t[i:])
    t = "".join(saida)
    t = re.sub(r"\x00(\d+)\x00", lambda m: guardados[int(m.group(1))], t)
    return re.sub(r"\s+", " ", t).strip()

def busca(q, paginas=3, tam=100):
    out = []
    for p in range(1, paginas + 1):
        url = BASE + urllib.parse.urlencode({"q": q, "tipos_documento": "contrato",
                                             "ordenacao": "-data", "pagina": p, "tam_pagina": tam})
        d = None
        for tentativa in range(4):  # o PNCP derruba conexao sob rajada; backoff resolve
            try:
                req = urllib.request.Request(url, headers={"Accept": "application/json",
                                                           "User-Agent": "pesquisa-precos/1.0"})
                with urllib.request.urlopen(req, timeout=60) as r:
                    d = json.load(r)
                break
            except Exception as e:
                if tentativa == 3: print(f"  ! {q} p{p}: {e}", file=sys.stderr)
                else: time.sleep(3 * (tentativa + 1))
        if not d or not d.get("items"): break
        for x in d["items"]:
            out.append({"q": q, "orgao": x.get("orgao_nome"), "esfera": x.get("esfera_nome"),
                        "uf": x.get("uf"), "municipio": x.get("municipio_nome"),
                        "modalidade": x.get("modalidade_licitacao_nome"), "valor": x.get("valor_global"),
                        "ano": x.get("ano"), "objeto": anonimiza(re.sub(r"\s+", " ", x.get("description") or ""))[:300],
                        "ini": x.get("data_inicio_vigencia"), "fim": x.get("data_fim_vigencia")})
        if p * tam >= d.get("total", 0): break
        time.sleep(1.5)
    return out

SOFTWARE = r"licenciamento|software|sistema (integrad|de gest)|programa de computador|plataforma digital|hospedagem|SaaS"
OBRA = r"obra|reforma|constru[çc][ãa]o|pavimenta|reurb|regulariza[çc][ãa]o fundi[áa]ria|ve[íi]culo|combust[íi]vel|merenda|medicamento|limpeza|vigil[âa]ncia"
GRUPOS = {
    "Curso in company (turma fechada)": r"in.?company|turma fechada",
    "Curso/instrutoria — IA": r"(intelig[êe]ncia artificial).{0,120}(curso|capacita|treinamento|oficina)|(curso|capacita|treinamento|oficina).{0,120}intelig[êe]ncia artificial",
    "Curso/instrutoria — Lei 14.133": r"(14\.?133|licita|contrata[çc][õo]es p[úu]blicas|preg[ãa]o|termo de refer[êe]ncia).{0,150}(curso|capacita|treinamento|instrutor|docente)|(curso|capacita|treinamento|instrutor|docente).{0,150}(14\.?133|licita)",
    "Palestra avulsa": r"palestra",
    "Consultoria em transparência/LAI": r"(transpar[êe]ncia|acesso [àa] informa[çc][ãa]o|\bLAI\b|dados abertos|e-?SIC).{0,150}(consultoria|assessoria|implanta|diagn[óo]stico|adequa)|(consultoria|assessoria).{0,150}(transpar[êe]ncia|acesso [àa] informa[çc][ãa]o)",
    "Estudo/diagnóstico/nota técnica": r"estudo t[ée]cnico|diagn[óo]stico|nota t[ée]cnica|parecer t[ée]cnico",
    "Assessoria a câmara municipal": r"c[âa]mara municipal|processo legislativo|assessoria parlamentar",
    "Prestação de contas / TCE": r"presta[çc][ãa]o de contas|tribunal de contas|\bTCE\b|SICONFI",
    "Escolas de governo": r"(?!)x",  # classificada por orgao, abaixo
}

def q_(v, p):
    v = sorted(v)
    if not v: return 0
    i = (len(v) - 1) * p; lo, hi = int(i), min(int(i) + 1, len(v) - 1)
    return v[lo] + (v[hi] - v[lo]) * (i - lo)

def meses(x):
    try:
        i = date.fromisoformat(x["ini"][:10]); f = date.fromisoformat(x["fim"][:10])
        m = (f - i).days / 30.44
        return m if 0 < m < 120 else None
    except Exception: return None

def limpa(dados):
    u, vistos = [], set()
    for x in dados:
        if not isinstance(x.get("valor"), (int, float)) or x["valor"] <= 0: continue
        k = (x["orgao"], x["valor"], x["objeto"][:80])
        if k in vistos: continue
        vistos.add(k)
        if re.search(SOFTWARE, x["objeto"], re.I) or re.search(OBRA, x["objeto"], re.I): continue
        u.append(x)
    return u

# Sub-classificacao de uma linha para leitura (docs/comercial/10): o que o objeto diz que se entrega.
NATUREZA = [
    ("escrituracao", r"escritura[çc][ãa]o|cont[áa]bil|contabilidade|folha de pagamento|or[çc]ament[áa]ri[ao]|financeir[ao]|patrimonial|balancete|balan[çc]o"),
    ("obrigacoes declaratorias", r"SICONFI|RREO|RGF|DCA|MSC|e-?Social|DCTF|SIOPE|SIOPS|SICAP|SAGRES|obriga[çc][õo]es acess[óo]rias|declara[çc][õo]es"),
    ("peca de resposta/defesa", r"defesa|impugna|recurso|resposta|alega[çc][õo]es|contrarraz[õo]es|apontamento|cita[çc][ãa]o|tomada de contas"),
    ("sistema/software", r"sistema|software|licen[çc]a|plataforma"),
    ("capacitacao", r"curso|capacita|treinamento"),
]
def natureza(objeto):
    tags = [n for n, pat in NATUREZA if re.search(pat, objeto, re.I)]
    return ", ".join(tags) or "assessoria generica"

def objetos(dados, linha, ufs):
    u = [x for x in limpa(dados) if re.search(GRUPOS[linha], x["objeto"], re.I) and (not ufs or x["uf"] in ufs)]
    u.sort(key=lambda x: -x["valor"])
    print(f"{len(u)} contratos na linha '{linha}'" + (f" em {','.join(ufs)}" if ufs else "") + "\n")
    cont = Counter()
    for i, x in enumerate(u, 1):
        m = meses(x); nat = natureza(x["objeto"]); cont[nat] += 1
        print(f"#{i:02d} R$ {x['valor']:>12,.0f}  {x['uf']}  {x['esfera']:<9} {x['modalidade'] or '-':<18} {(f'{m:.0f} meses' if m else 'pontual'):>9}  [{nat}]")
        print(f"     {x['orgao']}")
        print(f"     {x['objeto']}\n")
    print("por natureza:", dict(cont.most_common()))


# ---------------------------------------------------------------------------
# Geracao do JSON que o site publica. Antes, os numeros do estudo eram digitados a mao em
# src/data/estudo-precos.json — duas fontes de verdade para o mesmo dado, e nada impedia que
# divergissem. Agora o site le o que este script calcula.
#
# PUBLICADAS: rotulo publicado, grupo do GRUPOS, e quais recortes entram como sub-linha.
PUBLICADAS = [
    ("Curso in company (turma fechada)", "Curso in company (turma fechada)", []),
    ("Curso e instrutoria — inteligência artificial", "Curso/instrutoria — IA", ["dfgo"]),
    ("Curso e instrutoria — Lei 14.133 e contratações", "Curso/instrutoria — Lei 14.133", ["dfgo"]),
    ("Palestra avulsa", "Palestra avulsa", []),
    ("Consultoria em transparência e acesso à informação", "Consultoria em transparência/LAI", ["mensal", "dfgo"]),
    ("Estudo, diagnóstico e nota técnica", "Estudo/diagnóstico/nota técnica", ["avulsa"]),
    ("Assessoria técnica a câmara municipal", "Assessoria a câmara municipal", ["mensal", "dfgo"]),
    ("Prestação de contas e Tribunal de Contas", "Prestação de contas / TCE", ["mensal", ("dfgo", True)]),
    ("Escolas de governo (instrutoria)", "Escolas de governo", []),
]
ROTULO_SUB = {"mensal": "equivalente mensal", "dfgo": "só DF e Goiás", "avulsa": "peça avulsa (até 3 meses)"}

def reconcilia(dados):
    """Separa as etapas que a frase "unicos apos remover duplicatas, software e obra" juntava.

    A revisao critica de 06/09/2026 apontou, com razao, que somar operacoes diferentes numa frase so
    impede conferir o universo — e que a soma dos n da tabela nao bate com o total, porque as linhas
    se sobrepoem e porque parte dos contratos nao cai em nenhuma delas.
    """
    com_valor = [x for x in dados if isinstance(x.get("valor"), (int, float)) and x["valor"] > 0]
    vistos, dedup = set(), []
    for x in com_valor:
        k = (x["orgao"], x["valor"], (x["objeto"] or "")[:80])
        if k in vistos:
            continue
        vistos.add(k); dedup.append(x)
    tema = [x for x in dedup if re.search(SOFTWARE, x["objeto"], re.I) or re.search(OBRA, x["objeto"], re.I)]
    u = limpa(dados)
    em_linhas = Counter()
    classificados = 0
    for x in u:
        n = sum(1 for g, pat in GRUPOS.items() if pat != r"(?!)x" and re.search(pat, x["objeto"], re.I))
        if g_escola(x):
            n += 1
        em_linhas[n] += 1
        if n:
            classificados += 1
    return {
        "porNumeroDeGrupos": [[k, v] for k, v in sorted(em_linhas.items())],
        "coletados": len(dados),
        "semValor": len(dados) - len(com_valor),
        "duplicatas": len(com_valor) - len(dedup),
        "removidosPorTema": len(tema),
        "unicos": len(u),
        "classificados": classificados,
        "semLinha": len(u) - classificados,
        "emMaisDeUmaLinha": sum(v for k, v in em_linhas.items() if k > 1),
    }

def g_escola(x):
    return bool(re.search(r"ESCOLA (DE|NACIONAL|SUPERIOR|DO)|ESESP|EGAP", x["orgao"] or "", re.I))

# Rotulos publicados de modalidade sao mais legiveis que os do PNCP.
RENOMEIA_MODALIDADE = {"Pregão - Eletrônico": "Pregão eletrônico", "Concorrência - Eletrônica": "Concorrência eletrônica",
                       "Pregão - Presencial": "Pregão presencial", "Concorrência - Presencial": "Concorrência presencial"}
NAO_INFORMADO = "Não informado no registro"

def distribuicao(u, campo, renomeia, agrupa_apos=None, rotulo_outras="Outras"):
    """Distribuicao de um campo categorico sobre TODOS os registros de u.

    Ate 06/09/2026 as tabelas publicadas mostravam so as 4 categorias mais frequentes (most_common(4)),
    e a revisao externa notou que a soma nao fechava com o total: faltavam 15 registros na de
    modalidade (pregao presencial, concorrencia presencial e credenciamento) e 5 na de esfera
    (Distrital). Agora toda categoria presente entra — as pequenas, se `agrupa_apos` for dado,
    somadas numa linha "outras" com a contagem real — e uma linha "nao informado" so quando o campo
    esta de fato vazio. Percentual sobre len(u), arredondado; a soma das contagens e sempre len(u).

    Devolve (linhas, outras): linhas = [[rotulo, pct, n], ...]; outras = [[rotulo, n], ...] detalha
    o que a linha agrupada contem (vazio se nada foi agrupado).
    """
    vazios = sum(1 for x in u if not x.get(campo))
    cont = Counter(x[campo] for x in u if x.get(campo)).most_common()
    pct = lambda n: round(100 * n / len(u))
    principais = cont if agrupa_apos is None else cont[:agrupa_apos]
    resto = [] if agrupa_apos is None else cont[agrupa_apos:]
    linhas = [[renomeia.get(k, k), pct(n), n] for k, n in principais]
    outras = [[renomeia.get(k, k), n] for k, n in resto]
    if resto:
        n = sum(n for _, n in resto)
        linhas.append([rotulo_outras, pct(n), n])
    if vazios:
        linhas.append([NAO_INFORMADO, pct(vazios), vazios])
    assert sum(n for _, _, n in linhas) == len(u)
    return linhas, outras

def gera_json(dados, destino):
    u = limpa(dados)
    fmt = lambda v: f"{v:,.0f}".replace(",", ".")
    sel_grupo = lambda g: ([x for x in u if g_escola(x)]
                           if g == "Escolas de governo" else [x for x in u if re.search(GRUPOS[g], x["objeto"], re.I)])
    linhas = []
    for rotulo, grupo, subs in PUBLICADAS:
        sel = sel_grupo(grupo)
        if len(sel) < 6:
            continue
        vs = [x["valor"] for x in sel]
        linha = {"nome": rotulo, "n": len(sel), "p25": fmt(q_(vs, .25)), "mediana": fmt(q_(vs, .5)),
                 "p75": fmt(q_(vs, .75)), "p90": fmt(q_(vs, .9))}
        saida_sub = []
        for item in subs:
            chave, destaque = item if isinstance(item, tuple) else (item, False)
            if chave == "mensal":
                v = [x["valor"] / meses(x) for x in sel if meses(x) and meses(x) > 3]; minimo = 8
            elif chave == "avulsa":
                v = [x["valor"] for x in sel if (meses(x) or 99) <= 3]; minimo = 8
            else:
                v = [x["valor"] for x in sel if x["uf"] in ("DF", "GO")]; minimo = 5
            if len(v) < minimo:
                continue
            sub = {"nome": ROTULO_SUB[chave], "n": len(v), "mediana": fmt(q_(v, .5)), "p75": fmt(q_(v, .75))}
            if destaque:
                sub["destaque"] = True
            saida_sub.append(sub)
        if saida_sub:
            linha["sub"] = saida_sub
        linhas.append(linha)
    atual = json.load(open(destino))
    novo = {**atual, **reconcilia(dados), "consultas": len(CONSULTAS), "linhas": linhas,
            "denominadorDistribuicoes": len(u)}
    novo["modalidade"], novo["modalidadeOutras"] = distribuicao(u, "modalidade", RENOMEIA_MODALIDADE, agrupa_apos=4,
                                                                rotulo_outras="Outras modalidades informadas")
    novo["esfera"], novo["esferaOutras"] = distribuicao(u, "esfera", {}, agrupa_apos=None)
    for chave in ("semModalidade", "semEsfera"):   # substituidos pela linha "nao informado" (06/09/2026)
        novo.pop(chave, None)
    for tabela in ("modalidade", "esfera"):
        assert sum(n for _, _, n in novo[tabela]) == len(u), f"tabela de {tabela} nao soma o total"
    json.dump(novo, open(destino, "w"), ensure_ascii=False, indent=2)
    open(destino, "a").write("\n")
    print(f"{destino}: {len(linhas)} linhas, {len(u)} contratos unicos")

def analisa(dados):
    u = limpa(dados)
    f = lambda v: f"{v:,.0f}".replace(",", ".")
    print(f"{len(u)} contratos unicos apos limpeza\n")
    print(f"{'linha':44} {'n':>4} {'p25':>10} {'mediana':>10} {'p75':>10} {'p90':>10}")
    print("-" * 92)
    for g, pat in GRUPOS.items():
        sel = ([x for x in u if re.search(r"ESCOLA (DE|NACIONAL|SUPERIOR|DO)|ESESP|EGAP", x["orgao"] or "", re.I)]
               if g == "Escolas de governo" else [x for x in u if re.search(pat, x["objeto"], re.I)])
        if len(sel) < 6: continue
        vs = [x["valor"] for x in sel]
        print(f"{g:44} {len(sel):>4} {f(q_(vs,.25)):>10} {f(q_(vs,.5)):>10} {f(q_(vs,.75)):>10} {f(q_(vs,.9)):>10}")
        pont = [x["valor"] for x in sel if (meses(x) or 99) <= 3]
        if len(pont) >= 8:
            print(f"{'   so peca avulsa (ate 3 meses)':44} {len(pont):>4} {'':>10} {f(q_(pont,.5)):>10} {f(q_(pont,.75)):>10}")
        mens = [x["valor"] / meses(x) for x in sel if meses(x) and meses(x) > 3]
        if len(mens) >= 8:
            print(f"{'   equivalente MENSAL':44} {len(mens):>4} {'':>10} {f(q_(mens,.5)):>10} {f(q_(mens,.75)):>10}")
        dfgo = [x["valor"] for x in sel if x["uf"] in ("DF", "GO")]
        if len(dfgo) >= 5:
            print(f"{'   so DF e GO':44} {len(dfgo):>4} {'':>10} {f(q_(dfgo,.5)):>10} {f(q_(dfgo,.75)):>10}")
    for campo in ("modalidade", "esfera"):   # todas as categorias, para a soma fechar com len(u)
        print(f"\n{campo}:", {k: f"{n} ({100*n/len(u):.1f}%)" for k, n in Counter(x[campo] or "(vazio)" for x in u).most_common()})

if __name__ == "__main__":
    arq = f"pncp-contratos-{date.today().isoformat()}.json"
    if "--gera-json" in sys.argv:
        import glob, os
        aqui = os.path.dirname(os.path.abspath(__file__))
        arq = sorted(glob.glob(os.path.join(aqui, "pncp-contratos-*.json")))[-1]
        gera_json(json.load(open(arq)), os.path.join(aqui, "..", "..", "src", "data", "estudo-precos.json"))
        sys.exit(0)
    if "--anonimiza-base" in sys.argv:
        import glob, os
        aqui = os.path.dirname(os.path.abspath(__file__))
        for arq in sorted(glob.glob(os.path.join(aqui, "pncp-contratos-*.json"))):
            dados = json.load(open(arq))
            mudou = 0
            for x in dados:
                novo = anonimiza(x.get("objeto") or "")
                if novo != (x.get("objeto") or ""):
                    x["objeto"] = novo; mudou += 1
            json.dump(dados, open(arq, "w"), ensure_ascii=False)
            print(f"{os.path.basename(arq)}: {mudou} objeto(s) anonimizado(s) de {len(dados)}")
        sys.exit(0)
    if "--analisa" in sys.argv or "--objetos" in sys.argv:
        import glob, os
        aqui = os.path.dirname(os.path.abspath(__file__))
        arq = sorted(glob.glob(os.path.join(aqui, "pncp-contratos-*.json")))[-1]
        dados = json.load(open(arq))
        if "--objetos" in sys.argv:
            linha = sys.argv[sys.argv.index("--objetos") + 1]
            ufs = sys.argv[sys.argv.index("--uf") + 1].split(",") if "--uf" in sys.argv else []
            objetos(dados, linha, ufs)
        else:
            analisa(dados)
    else:
        todos = []
        for q in CONSULTAS:
            r = busca(q); print(f"{q}: {len(r)}", file=sys.stderr); todos += r; time.sleep(2)
        json.dump(todos, open(arq, "w"), ensure_ascii=False)
        print(f"\n{len(todos)} contratos em {arq}", file=sys.stderr)
        analisa(todos)
