Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .config.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Arquivo de configuração para variáveis de ambiente
# Copie para .config e preencha com seus valores

# **ÚNICA configuração necessária**: ID do documento Google Docs central
# Este documento contém TODAS as chaves de configuração em formato CHAVE=VALOR
# Exemplo: DEMOGRAFIA_CSV_URL=https://...
# DEFAULT_DOCS_URL=https://...

CONFIG_DOC_ID=12o-W-VtSl9ytbF6CD9S14ACJL63XsmYJnmZValPqKKA

# TUDO MAIS VEM DO DOCUMENTO CENTRAL
# Não adicione URLs aqui - elas devem estar no Google Docs!
127 changes: 127 additions & 0 deletions CONFIG_SETUP.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Sistema de Configuração Centralizado

## Visão Geral

A aplicação usa **UMA ÚNICA URL** apontando para um documento Google Docs central que armazena **TODAS** as configurações em formato `CHAVE=VALOR`.

Não há URLs hardcoded, não há múltiplos .env files. Tudo vem do documento central.

## Como Funciona

### 1. **Única URL de Configuração**

Edite `.env` ou `main.py` para definir:

```env
CONFIG_DOC_ID=12o-W-VtSl9ytbF6CD9S14ACJL63XsmYJnmZValPqKKA
```

Ou no código:
```python
CONFIG_DOC_ID = os.getenv("CONFIG_DOC_ID", "12o-W-VtSl9ytbF6CD9S14ACJL63XsmYJnmZValPqKKA")
```

### 2. **Documento Central (Google Docs)**

O documento deve estar **publicamente acessível** (Qualquer pessoa com o link - Leitor) e ter o seguinte formato:

```
DEMOGRAFIA_CSV_URL=https://drive.google.com/file/d/1zH6Yri2EdchUUjoTDtXgdHrmo6SVBfBG/view
DEFAULT_DOCS_URL=https://docs.google.com/document/d/1WA3LcQAWIKFYu6MmuF4RSrGFSdYvbpnn/edit?userstoinvite=lucianna.mrf@gmail.com&sharingaction=manageaccess&role=writer

EDUCACAO_CSV_URL=https://drive.google.com/file/d/SEU_ID_AQUI/view
EDUCACAO_DOCS_URL=https://docs.google.com/document/d/SEU_ID_AQUI/edit

# Comentários começam com #
# Linhas em branco são ignoradas
```

### 3. **Inicialização da Aplicação**

Na inicialização:
1. ✓ Conecta ao documento central (Google Docs export URL)
2. ✓ Lê o conteúdo em plain text
3. ✓ Parse cada linha `CHAVE=VALOR`
4. ✓ Armazena em memória no dicionário `CONFIG`
5. ✗ Se falhar aqui, a aplicação não inicia (erro crítico)

### 4. **Uso Durante Execução**

Toda a aplicação acessa `CONFIG["CHAVE"]`:

```python
# Ao gerar relatório
csv_url = CONFIG["DEMOGRAFIA_CSV_URL"]
docs_url = CONFIG["DEFAULT_DOCS_URL"]

# Se a chave não existir, erro 503 Service Unavailable
```

## APIs

### `GET /config`

Retorna a configuração carregada (para debug):

```json
{
"config_doc_url": "https://docs.google.com/document/d/12o-W-VtSl9ytbF6CD9S14ACJL63XsmYJnmZValPqKKA/export?format=txt",
"config_loaded": true,
"config_keys": ["DEMOGRAFIA_CSV_URL", "DEFAULT_DOCS_URL"],
"config_values": {
"DEMOGRAFIA_CSV_URL": "https://drive.google.com/...",
"DEFAULT_DOCS_URL": "https://docs.google.com/..."
}
}
```

## Troubleshooting

### "ERRO CRÍTICO: Não foi possível carregar a configuração central"

Verifique:
1. **Documento público?** Acesse o link direto no navegador. Deve estar acessível sem login.
2. **Formato correto?** Cada linha: `CHAVE=VALOR`
3. **Internet?** Teste: `curl -I https://docs.google.com/document/d/SEU_ID/export?format=txt`
4. **Timeout?** O Google Docs pode ser lento. Timeout está em 20s.

### "Configuração XXX_URL não encontrada"

A chave não existe no documento central. Adicione ao Google Docs:

```
MINHA_CHAVE=https://exemplo.com/meu-arquivo.csv
```

## Exemplo Completo

**Documento Google Docs (URL: `https://docs.google.com/document/d/12o-W-VtSl9ytbF6CD9S14ACJL63XsmYJnmZValPqKKA`)**

```
# Configurações de Demografia
DEMOGRAFIA_CSV_URL=https://drive.google.com/file/d/1zH6Yri2EdchUUjoTDtXgdHrmo6SVBfBG/view
DEFAULT_DOCS_URL=https://docs.google.com/document/d/1WA3LcQAWIKFYu6MmuF4RSrGFSdYvbpnn/edit

# Futuras configurações
EDUCACAO_CSV_URL=https://drive.google.com/file/d/ABC123/view
EDUCACAO_DOCS_URL=https://docs.google.com/document/d/DEF456/edit
```

**Código Python:**

```python
# Carrega tudo na inicialização
CONFIG = carregar_config_central()

# Usa em qualquer lugar
csv_url = CONFIG["DEMOGRAFIA_CSV_URL"]
docs_url = CONFIG["DEFAULT_DOCS_URL"]
```

## Benefícios

✓ **Única fonte de verdade** - Um documento, todas as configs
✓ **Sem deploy** - Muda URL no Google Docs, aplicação já usa
✓ **Escalável** - Novos macrotemas? Apenas adicione linhas ao documento
✓ **Seguro** - Futuramente, pode ser um secret no GitHub Actions
✓ **Sem hardcoding** - Nenhuma URL no código ou .env
2 changes: 1 addition & 1 deletion frontend/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect, useMemo, useState } from 'react'
// Default to same-origin when served by the API (Docker/prod).
// In dev (Vite on :5173), set VITE_API_BASE_URL=http://127.0.0.1:8000.

const API_BASE = import.meta.env.VITE_API_BASE_URL || window.location.origin
const API_BASE = 'http://127.0.0.1:8000'
const MACROTEMAS = [
'Demografia'
]
Expand Down
123 changes: 117 additions & 6 deletions main.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,21 @@
OUTPUT_DIR = BASE_DIR / "output"
CITIES_FILE = BASE_DIR / "citys.txt"

# ÚNICA URL que aponta para o documento central de configuração
CONFIG_DOC_ID = os.getenv("CONFIG_DOC_ID", "12o-W-VtSl9ytbF6CD9S14ACJL63XsmYJnmZValPqKKA")
CONFIG_DOC_URL = f"https://docs.google.com/document/d/{CONFIG_DOC_ID}/export?format=txt"

carregado = load_dotenv(dotenv_path='.config')
DEMOGRAFIA_CSV_URL = os.getenv("DEMOGRAFIA_CSV_URL")
DEFAULT_DOCS_URL = os.getenv("DEFAULT_DOCS_URL")

FALLBACK_DOC_TEXT = """deu erro.
"""

CONFIG_KEY_ALIASES = {
"DEMOGRAFIA_CSV": "DEMOGRAFIA_CSV_URL",
"DEMOGRAFIA_TEMPLATE": "DEFAULT_DOCS_URL",
"DEFAULT_DOCS": "DEFAULT_DOCS_URL",
}

TEMPLATE_STRING = """
<html lang="pt-BR">
<head>
Expand Down Expand Up @@ -117,6 +125,70 @@
"""


def carregar_config_central() -> dict:
"""
Reads ONLY from the central configuration Google Docs.
Parses KEY=VALUE pairs and returns a dictionary.

This is the SINGLE SOURCE OF TRUTH for all URLs and configuration.
If this fails, the application cannot start.
"""
config = {}
try:
with urlopen(CONFIG_DOC_URL, timeout=20) as response:
conteudo = response.read().decode("utf-8")

for linha in conteudo.splitlines():
linha = linha.strip()
if not linha or linha.startswith("#"):
continue
if "=" in linha:
chave, valor = linha.split("=", 1)
chave_normalizada = chave.strip().lstrip("\ufeff").upper()
valor_limpo = valor.strip().strip('"').strip("'")
config[chave_normalizada] = valor_limpo

for origem, destino in CONFIG_KEY_ALIASES.items():
if destino not in config and origem in config:
config[destino] = config[origem]

if not config:
raise ValueError("Documento de configuração está vazio ou mal formatado.")

print(f"✓ Configuração carregada com sucesso: {len(config)} chaves")
return config
except Exception as err:
raise RuntimeError(
f"ERRO CRÍTICO: Não foi possível carregar a configuração central.\n"
f"URL: {CONFIG_DOC_URL}\n"
f"Erro: {err}\n\n"
f"Verifique se:\n"
f" 1. O documento é acessível publicamente\n"
f" 2. O formato está correto (CHAVE=VALOR)\n"
f" 3. A conexão com Google Docs está funcionando"
) from err


def obter_config(chave: str) -> str:
chave = chave.upper()
if chave in CONFIG and CONFIG[chave]:
return CONFIG[chave]

chave_bom = f"\ufeff{chave}"
if chave_bom in CONFIG and CONFIG[chave_bom]:
return CONFIG[chave_bom]

alias = next((dest for src, dest in CONFIG_KEY_ALIASES.items() if dest == chave and src in CONFIG), None)
if alias and CONFIG.get(alias):
return CONFIG[alias]

raise HTTPException(status_code=503, detail=f"Configuração {chave} não encontrada no documento central.")


# Load config from central Google Docs on startup
CONFIG = carregar_config_central()


def extrair_doc_id(link_ou_id: str) -> str:
valor = link_ou_id.strip()
if "/document/d/" not in valor:
Expand Down Expand Up @@ -256,25 +328,50 @@ def filtrar_linhas_por_cidade(df: pd.DataFrame, cidade: str) -> pd.DataFrame:
return df[mascara_sem_uf]


def normalizar_url_csv(link_ou_id: str) -> str:
"""
Converts a Google Drive sharing link to a direct download URL.
If it already is a direct URL, returns it as-is.
"""
valor = link_ou_id.strip()
if "/file/d/" not in valor:
return valor

parsed = urlparse(valor)
partes = [p for p in parsed.path.split("/") if p]
if "d" in partes:
idx = partes.index("d")
if idx + 1 < len(partes):
file_id = partes[idx + 1]
return f"https://drive.google.com/uc?export=download&id={file_id}"

raise ValueError("Não foi possível extrair o ID do arquivo CSV do Google Drive.")


@app.get("/cities")
async def listar_cidades():
return carregar_cidades()

@app.get("/relatorio/{cidade}", response_class=HTMLResponse)
async def gerar_relatorio(cidade: str):
df = pd.read_csv(DEMOGRAFIA_CSV_URL, delimiter=";")
# Tudo vem do CONFIG (que vem do documento central)
csv_url = obter_config("DEMOGRAFIA_CSV_URL")
df = pd.read_csv(normalizar_url_csv(csv_url), delimiter=";")
linhas_df = filtrar_linhas_por_cidade(df, cidade)
linhas = linhas_df.to_dict("records")

if not linhas:
raise HTTPException(status_code=404, detail=f"Cidade '{cidade}' não encontrada.")

# If DATANE_DOCS_URL is set but empty (common in docker-compose), fall back to default.
docs_url = os.getenv("DATANE_DOCS_URL") or DEFAULT_DOCS_URL
# Get docs URL from CONFIG (central source only)
docs_url = obter_config("DEFAULT_DOCS_URL")
try:
docs_texto = carregar_texto_do_docs(docs_url)
except ValueError as err:
raise HTTPException(status_code=400, detail=str(err)) from err
raise HTTPException(
status_code=400,
detail=f"Erro ao carregar o documento de template: {str(err)}"
) from err

docs_html = texto_para_html(docs_texto, linhas[0])

Expand All @@ -293,6 +390,20 @@ async def gerar_relatorio(cidade: str):
return HTMLResponse(content=html)


@app.get("/config")
async def verificar_config():
"""
Debug endpoint to verify the loaded configuration.
Shows all KEY=VALUE pairs loaded from the central Google Docs.
"""
return {
"config_doc_url": CONFIG_DOC_URL,
"config_loaded": len(CONFIG) > 0,
"config_keys": list(CONFIG.keys()),
"config_values": CONFIG,
}


# If the frontend has been built (e.g., via Docker), serve it from the same app.
FRONTEND_DIST_DIR = BASE_DIR / "frontend" / "dist"
if FRONTEND_DIST_DIR.exists():
Expand Down
Loading
Loading