🏗️ feat(admin): criar scaffold inicial do orquestrador-admin
parent
498836fd38
commit
17583236a6
@ -0,0 +1,3 @@
|
|||||||
|
from admin_app.main import app
|
||||||
|
|
||||||
|
__all__ = ["app"]
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
from fastapi import Request
|
||||||
|
|
||||||
|
from admin_app.core.settings import AdminSettings, get_admin_settings
|
||||||
|
|
||||||
|
|
||||||
|
def get_settings(request: Request) -> AdminSettings:
|
||||||
|
app_settings = getattr(request.app.state, "admin_settings", None)
|
||||||
|
if isinstance(app_settings, AdminSettings):
|
||||||
|
return app_settings
|
||||||
|
return get_admin_settings()
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
from fastapi import APIRouter
|
||||||
|
|
||||||
|
from admin_app.api.routes.system import router as system_router
|
||||||
|
|
||||||
|
api_router = APIRouter()
|
||||||
|
api_router.include_router(system_router)
|
||||||
@ -0,0 +1,27 @@
|
|||||||
|
from fastapi import APIRouter, Depends
|
||||||
|
|
||||||
|
from admin_app.api.dependencies import get_settings
|
||||||
|
from admin_app.api.schemas import AdminHealthResponse, AdminRootResponse, AdminSystemInfoResponse
|
||||||
|
from admin_app.core.settings import AdminSettings
|
||||||
|
from admin_app.services.system_service import SystemService
|
||||||
|
|
||||||
|
router = APIRouter(tags=["system"])
|
||||||
|
|
||||||
|
|
||||||
|
def _build_service(settings: AdminSettings) -> SystemService:
|
||||||
|
return SystemService(settings=settings)
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/", response_model=AdminRootResponse)
|
||||||
|
def root(settings: AdminSettings = Depends(get_settings)):
|
||||||
|
return _build_service(settings).build_root_payload()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/health", response_model=AdminHealthResponse)
|
||||||
|
def health_check(settings: AdminSettings = Depends(get_settings)):
|
||||||
|
return _build_service(settings).build_health_payload()
|
||||||
|
|
||||||
|
|
||||||
|
@router.get("/system/info", response_model=AdminSystemInfoResponse)
|
||||||
|
def system_info(settings: AdminSettings = Depends(get_settings)):
|
||||||
|
return _build_service(settings).build_system_info_payload()
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class AdminRootResponse(BaseModel):
|
||||||
|
service: str
|
||||||
|
status: str
|
||||||
|
message: str
|
||||||
|
environment: str
|
||||||
|
|
||||||
|
|
||||||
|
class AdminHealthResponse(BaseModel):
|
||||||
|
service: str
|
||||||
|
status: str
|
||||||
|
version: str
|
||||||
|
|
||||||
|
|
||||||
|
class AdminSystemInfoResponse(BaseModel):
|
||||||
|
service: str
|
||||||
|
app_name: str
|
||||||
|
environment: str
|
||||||
|
version: str
|
||||||
|
api_prefix: str
|
||||||
|
debug: bool
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
from fastapi import FastAPI
|
||||||
|
|
||||||
|
from admin_app.api.router import api_router
|
||||||
|
from admin_app.core.settings import AdminSettings, get_admin_settings
|
||||||
|
|
||||||
|
|
||||||
|
# Fabrica explicita do runtime administrativo para facilitar testes e futura configuracao.
|
||||||
|
def create_app(settings: AdminSettings | None = None) -> FastAPI:
|
||||||
|
resolved_settings = settings or get_admin_settings()
|
||||||
|
app = FastAPI(
|
||||||
|
title=resolved_settings.admin_app_name,
|
||||||
|
version=resolved_settings.admin_version,
|
||||||
|
debug=resolved_settings.admin_debug,
|
||||||
|
)
|
||||||
|
app.state.admin_settings = resolved_settings
|
||||||
|
app.include_router(api_router, prefix=resolved_settings.admin_api_prefix)
|
||||||
|
return app
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
from functools import lru_cache
|
||||||
|
|
||||||
|
from pydantic import field_validator
|
||||||
|
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||||
|
|
||||||
|
|
||||||
|
class AdminSettings(BaseSettings):
|
||||||
|
model_config = SettingsConfigDict(
|
||||||
|
env_file=".env",
|
||||||
|
extra="ignore",
|
||||||
|
)
|
||||||
|
|
||||||
|
admin_app_name: str = "Orquestrador Admin"
|
||||||
|
admin_environment: str = "production"
|
||||||
|
admin_debug: bool = False
|
||||||
|
admin_version: str = "0.1.0"
|
||||||
|
admin_api_prefix: str = ""
|
||||||
|
|
||||||
|
# Banco administrativo do runtime interno.
|
||||||
|
admin_db_host: str = "127.0.0.1"
|
||||||
|
admin_db_port: int = 3306
|
||||||
|
admin_db_user: str = "root"
|
||||||
|
admin_db_password: str = ""
|
||||||
|
admin_db_name: str = "orquestrador_admin"
|
||||||
|
admin_db_cloud_sql_connection_name: str | None = None
|
||||||
|
|
||||||
|
@field_validator("admin_debug", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def parse_debug_aliases(cls, value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
normalized = value.strip().lower()
|
||||||
|
if normalized in {"debug", "development", "dev"}:
|
||||||
|
return True
|
||||||
|
if normalized in {"release", "production", "prod"}:
|
||||||
|
return False
|
||||||
|
return value
|
||||||
|
|
||||||
|
@field_validator("admin_environment", "admin_api_prefix", mode="before")
|
||||||
|
@classmethod
|
||||||
|
def normalize_text_settings(cls, value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
return value.strip()
|
||||||
|
return value
|
||||||
|
|
||||||
|
|
||||||
|
@lru_cache(maxsize=1)
|
||||||
|
def get_admin_settings() -> AdminSettings:
|
||||||
|
return AdminSettings()
|
||||||
@ -0,0 +1,32 @@
|
|||||||
|
from sqlalchemy import create_engine
|
||||||
|
from sqlalchemy.orm import declarative_base, sessionmaker
|
||||||
|
|
||||||
|
from admin_app.core.settings import get_admin_settings
|
||||||
|
|
||||||
|
settings = get_admin_settings()
|
||||||
|
admin_cloud_sql = settings.admin_db_cloud_sql_connection_name
|
||||||
|
|
||||||
|
if admin_cloud_sql:
|
||||||
|
ADMIN_DATABASE_URL = (
|
||||||
|
f"mysql+pymysql://{settings.admin_db_user}:{settings.admin_db_password}@/{settings.admin_db_name}"
|
||||||
|
f"?unix_socket=/cloudsql/{admin_cloud_sql}"
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ADMIN_DATABASE_URL = (
|
||||||
|
f"mysql+pymysql://{settings.admin_db_user}:{settings.admin_db_password}@"
|
||||||
|
f"{settings.admin_db_host}:{settings.admin_db_port}/{settings.admin_db_name}"
|
||||||
|
)
|
||||||
|
|
||||||
|
admin_engine = create_engine(
|
||||||
|
ADMIN_DATABASE_URL,
|
||||||
|
pool_pre_ping=True,
|
||||||
|
connect_args={"connect_timeout": 5},
|
||||||
|
)
|
||||||
|
|
||||||
|
AdminSessionLocal = sessionmaker(
|
||||||
|
autocommit=False,
|
||||||
|
autoflush=False,
|
||||||
|
bind=admin_engine,
|
||||||
|
)
|
||||||
|
|
||||||
|
AdminBase = declarative_base()
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
from admin_app.db.models.base import AdminTimestampedModel
|
||||||
|
|
||||||
|
__all__ = ["AdminTimestampedModel"]
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
from sqlalchemy import DateTime
|
||||||
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
|
from sqlalchemy.sql import func
|
||||||
|
|
||||||
|
from admin_app.db.database import AdminBase
|
||||||
|
|
||||||
|
|
||||||
|
# Base abstrata com timestamps para futuras entidades administrativas.
|
||||||
|
class AdminTimestampedModel(AdminBase):
|
||||||
|
__abstract__ = True
|
||||||
|
|
||||||
|
created_at: Mapped[object] = mapped_column(DateTime, server_default=func.current_timestamp())
|
||||||
|
updated_at: Mapped[object] = mapped_column(
|
||||||
|
DateTime,
|
||||||
|
server_default=func.current_timestamp(),
|
||||||
|
onupdate=func.current_timestamp(),
|
||||||
|
)
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
from admin_app.app_factory import create_app
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
from admin_app.repositories.base_repository import BaseRepository
|
||||||
|
|
||||||
|
__all__ = ["BaseRepository"]
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
|
||||||
|
class BaseRepository:
|
||||||
|
def __init__(self, db: Session):
|
||||||
|
self.db = db
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
from admin_app.services.system_service import SystemService
|
||||||
|
|
||||||
|
__all__ = ["SystemService"]
|
||||||
@ -0,0 +1,31 @@
|
|||||||
|
from admin_app.core.settings import AdminSettings
|
||||||
|
|
||||||
|
|
||||||
|
class SystemService:
|
||||||
|
def __init__(self, settings: AdminSettings):
|
||||||
|
self.settings = settings
|
||||||
|
|
||||||
|
def build_root_payload(self) -> dict:
|
||||||
|
return {
|
||||||
|
"service": "orquestrador-admin",
|
||||||
|
"status": "ok",
|
||||||
|
"message": "Servico administrativo inicializado.",
|
||||||
|
"environment": self.settings.admin_environment,
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_health_payload(self) -> dict:
|
||||||
|
return {
|
||||||
|
"service": "orquestrador-admin",
|
||||||
|
"status": "ok",
|
||||||
|
"version": self.settings.admin_version,
|
||||||
|
}
|
||||||
|
|
||||||
|
def build_system_info_payload(self) -> dict:
|
||||||
|
return {
|
||||||
|
"service": "orquestrador-admin",
|
||||||
|
"app_name": self.settings.admin_app_name,
|
||||||
|
"environment": self.settings.admin_environment,
|
||||||
|
"version": self.settings.admin_version,
|
||||||
|
"api_prefix": self.settings.admin_api_prefix,
|
||||||
|
"debug": self.settings.admin_debug,
|
||||||
|
}
|
||||||
@ -0,0 +1,139 @@
|
|||||||
|
# ADR 0001 - Separar usuario de atendimento de conta administrativa interna
|
||||||
|
|
||||||
|
## Status
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
Hoje o sistema possui um conceito principal de usuario em `app/db/mock_models.py` (`User`).
|
||||||
|
Esse registro representa a identidade operacional do atendimento e nasce a partir de canais externos, como Telegram.
|
||||||
|
Ele serve para vincular conversas, pedidos, locacoes, revisoes e contexto transacional do usuario final.
|
||||||
|
|
||||||
|
Para a frente de auto-incremento de tools, precisaremos de uma area interna com login, permissao, auditoria e publicacao controlada.
|
||||||
|
Misturar essa conta interna com o `User` atual criaria problemas de seguranca, modelagem e isolamento de dominio.
|
||||||
|
|
||||||
|
## Decisao
|
||||||
|
Vamos separar explicitamente dois dominios de identidade:
|
||||||
|
|
||||||
|
1. `AtendimentoUser`
|
||||||
|
- Continua sendo o `User` atual do banco operacional/mock.
|
||||||
|
- Representa clientes e pessoas atendidas por canais externos.
|
||||||
|
- Continua vinculado a conversa, pedido, revisao, locacao e historico operacional.
|
||||||
|
|
||||||
|
2. `StaffAccount`
|
||||||
|
- Sera uma nova entidade para acesso administrativo interno.
|
||||||
|
- Representa funcionarios e administradores da empresa.
|
||||||
|
- Sera usada para login no painel interno, configuracao do sistema, criacao/aprovacao de tools e auditoria.
|
||||||
|
|
||||||
|
## Fronteira entre os dois tipos de conta
|
||||||
|
|
||||||
|
### AtendimentoUser
|
||||||
|
- Banco: operacional/mock (`MockBase`)
|
||||||
|
- Origem: canal externo (`channel`, `external_id`)
|
||||||
|
- Autenticacao: indireta, via canal de atendimento
|
||||||
|
- Responsabilidade: atendimento ao cliente e contexto de negocio
|
||||||
|
- Nao deve receber credenciais de painel interno
|
||||||
|
|
||||||
|
### StaffAccount
|
||||||
|
- Banco: administrativo/tools (`Base`)
|
||||||
|
- Origem: cadastro interno controlado
|
||||||
|
- Autenticacao: login web proprio
|
||||||
|
- Responsabilidade: administracao, configuracao e governanca de tools
|
||||||
|
- Nao deve ser usado para identificar cliente do atendimento
|
||||||
|
|
||||||
|
## Racional para usar o banco administrativo/tools para StaffAccount
|
||||||
|
O projeto ja possui um banco administrativo ligado a `Base`, hoje usado para `tools`.
|
||||||
|
Como a nova frente trata de governanca do sistema e nao de jornada do cliente final, o lugar mais coerente para `StaffAccount` e para os metadados de geracao/publicacao e esse mesmo dominio administrativo.
|
||||||
|
|
||||||
|
Isso reduz acoplamento com o banco operacional e evita misturar seguranca interna com dados de atendimento.
|
||||||
|
|
||||||
|
## Entidades alvo derivadas desta decisao
|
||||||
|
As proximas fases devem introduzir, no banco administrativo, entidades como:
|
||||||
|
|
||||||
|
- `StaffAccount`
|
||||||
|
- `StaffSession` ou estrategia equivalente de token
|
||||||
|
- `ToolDraft`
|
||||||
|
- `ToolGenerationJob`
|
||||||
|
- `ToolValidationRun`
|
||||||
|
- `ToolPublication`
|
||||||
|
- `AuditLog`
|
||||||
|
|
||||||
|
O banco operacional continua com entidades como:
|
||||||
|
|
||||||
|
- `User`
|
||||||
|
- `Order`
|
||||||
|
- `ReviewSchedule`
|
||||||
|
- `RentalContract`
|
||||||
|
- `ConversationTurn`
|
||||||
|
|
||||||
|
## Regras arquiteturais obrigatorias
|
||||||
|
|
||||||
|
1. Nenhuma rota administrativa deve reutilizar `User` do atendimento como identidade autenticada.
|
||||||
|
2. Nenhuma regra de atendimento deve depender de `StaffAccount` para funcionar.
|
||||||
|
3. O pipeline de geracao/publicacao de tools deve operar fora do caminho critico do atendimento.
|
||||||
|
4. Toda ativacao de tool gerada deve ser auditavel e vinculada a um `StaffAccount`.
|
||||||
|
5. O atendimento continua decidindo execucao com base no modelo; o painel administrativo apenas governa cadastro, validacao e publicacao.
|
||||||
|
|
||||||
|
## Papel inicial de permissao
|
||||||
|
A primeira versao deve prever ao menos estes papeis:
|
||||||
|
|
||||||
|
- `admin`: gerencia contas internas, aprova e publica tools, altera configuracoes sensiveis
|
||||||
|
- `staff`: cria drafts, acompanha geracao, revisa resultados e solicita aprovacao
|
||||||
|
- `viewer`: consulta status e auditoria, sem publicar
|
||||||
|
|
||||||
|
## Estrutura tecnica sugerida
|
||||||
|
|
||||||
|
### Banco administrativo (`Base`)
|
||||||
|
- `app/db/models/staff_account.py`
|
||||||
|
- `app/db/models/tool_draft.py`
|
||||||
|
- `app/db/models/tool_generation_job.py`
|
||||||
|
- `app/db/models/audit_log.py`
|
||||||
|
|
||||||
|
### Repositorios
|
||||||
|
- `app/repositories/staff_account_repository.py`
|
||||||
|
- `app/repositories/tool_draft_repository.py`
|
||||||
|
- `app/repositories/tool_generation_job_repository.py`
|
||||||
|
|
||||||
|
### Servicos
|
||||||
|
- `app/services/admin/auth_service.py`
|
||||||
|
- `app/services/admin/tool_draft_service.py`
|
||||||
|
- `app/services/admin/tool_generation_service.py`
|
||||||
|
- `app/services/admin/audit_service.py`
|
||||||
|
|
||||||
|
### API interna
|
||||||
|
- `app/api/routes/admin_auth.py`
|
||||||
|
- `app/api/routes/admin_tools.py`
|
||||||
|
- `app/api/routes/admin_audit.py`
|
||||||
|
|
||||||
|
## Fluxo alvo de alto nivel
|
||||||
|
1. `StaffAccount` faz login no painel interno.
|
||||||
|
2. O usuario interno cria um `ToolDraft` com nome, descricao e parametros.
|
||||||
|
3. Um job isolado gera a implementacao e executa validacoes.
|
||||||
|
4. O resultado fica disponivel para revisao humana.
|
||||||
|
5. Um `admin` aprova e publica.
|
||||||
|
6. A tool publicada passa a integrar o registry ativo sem afetar o dominio de identidade do atendimento.
|
||||||
|
|
||||||
|
## Impacto nas proximas etapas
|
||||||
|
A partir desta decisao, as proximas implementacoes devem seguir esta ordem:
|
||||||
|
|
||||||
|
1. Criar `StaffAccount` e autenticacao administrativa.
|
||||||
|
2. Criar autorizacao por papel.
|
||||||
|
3. Criar entidades de draft/versionamento/validacao.
|
||||||
|
4. Criar pipeline isolado de geracao.
|
||||||
|
5. Criar painel e rotas administrativas.
|
||||||
|
|
||||||
|
## Consequencias
|
||||||
|
### Positivas
|
||||||
|
- Isola seguranca interna do atendimento ao cliente.
|
||||||
|
- Facilita auditoria e governanca.
|
||||||
|
- Evita acoplamento indevido entre canal externo e painel interno.
|
||||||
|
- Deixa clara a separacao entre operacao e administracao do sistema.
|
||||||
|
|
||||||
|
### Custos
|
||||||
|
- Introduz novo conjunto de entidades e rotas.
|
||||||
|
- Exige autenticacao e autorizacao dedicadas.
|
||||||
|
- Aumenta a complexidade de bootstrap e persistencia do dominio administrativo.
|
||||||
|
|
||||||
|
## Fora do escopo desta ADR
|
||||||
|
- Escolha definitiva do modelo para geracao de codigo.
|
||||||
|
- Implementacao do frontend administrativo.
|
||||||
|
- Definicao detalhada do sandbox de execucao das tools geradas.
|
||||||
@ -0,0 +1,246 @@
|
|||||||
|
# ADR 0002 - Separar o runtime de produto do serviço administrativo
|
||||||
|
|
||||||
|
## Status
|
||||||
|
Accepted
|
||||||
|
|
||||||
|
## Relacao com ADRs anteriores
|
||||||
|
Esta decisao complementa a ADR 0001.
|
||||||
|
A ADR 0001 separa identidade de atendimento e identidade administrativa.
|
||||||
|
A ADR 0002 amplia essa separacao para o nivel de servicos e runtime.
|
||||||
|
|
||||||
|
## Contexto
|
||||||
|
O sistema atual nasceu como um unico runtime orientado ao atendimento.
|
||||||
|
Hoje ele concentra no mesmo projeto e no mesmo ciclo operacional:
|
||||||
|
|
||||||
|
- atendimento conversacional
|
||||||
|
- orquestracao de tools
|
||||||
|
- integracao com Telegram
|
||||||
|
- estado conversacional
|
||||||
|
- regras operacionais de vendas, revisao e locacao
|
||||||
|
- administracao futura do sistema
|
||||||
|
- geracao futura de novas tools
|
||||||
|
- relatorios e configuracoes internas
|
||||||
|
|
||||||
|
A nova frente de evolucao exige um modulo administrativo mais robusto, com:
|
||||||
|
|
||||||
|
- login interno de funcionarios e administradores
|
||||||
|
- configuracao do sistema
|
||||||
|
- relatorios de vendas, arrecadacao e operacao
|
||||||
|
- cadastro, geracao, validacao e publicacao de novas tools
|
||||||
|
- auditoria de alteracoes e aprovacoes
|
||||||
|
|
||||||
|
Se tudo isso continuar no mesmo runtime do atendimento, teremos aumento de risco em quatro eixos:
|
||||||
|
|
||||||
|
1. Performance
|
||||||
|
- jobs pesados de geracao e validacao podem concorrer com o atendimento.
|
||||||
|
|
||||||
|
2. Seguranca
|
||||||
|
- login administrativo, aprovacoes e publicacao de codigo ficariam expostos no mesmo servico do produto.
|
||||||
|
|
||||||
|
3. Operacao
|
||||||
|
- qualquer falha ou deploy administrativo pode impactar diretamente o atendimento.
|
||||||
|
|
||||||
|
4. Evolucao
|
||||||
|
- o painel e a automacao interna possuem cadencia, dependencias e necessidades diferentes do runtime conversacional.
|
||||||
|
|
||||||
|
## Decisao
|
||||||
|
Vamos separar a solucao em dois servicos distintos, inicialmente no mesmo repositorio.
|
||||||
|
|
||||||
|
### 1. Servico de produto
|
||||||
|
Nome conceitual: `orquestrador-product`
|
||||||
|
|
||||||
|
Responsabilidades:
|
||||||
|
- atendimento conversacional
|
||||||
|
- integracao com Telegram e futuros canais de atendimento
|
||||||
|
- orquestracao de tools em tempo de execucao
|
||||||
|
- fluxos operacionais de vendas, revisao e locacao
|
||||||
|
- leitura apenas de tools publicadas e configuracoes ativas
|
||||||
|
|
||||||
|
Esse servico continua sendo o runtime critico do produto.
|
||||||
|
Ele deve permanecer leve, previsivel e protegido de cargas administrativas.
|
||||||
|
|
||||||
|
### 2. Servico administrativo
|
||||||
|
Nome conceitual: `orquestrador-admin`
|
||||||
|
|
||||||
|
Responsabilidades:
|
||||||
|
- autenticacao e autorizacao interna
|
||||||
|
- painel administrativo
|
||||||
|
- configuracoes do sistema
|
||||||
|
- relatorios de vendas, arrecadacao e operacao
|
||||||
|
- cadastro de drafts de tools
|
||||||
|
- geracao de implementacoes
|
||||||
|
- validacao automatica
|
||||||
|
- aprovacao humana
|
||||||
|
- publicacao controlada
|
||||||
|
- auditoria de mudancas
|
||||||
|
|
||||||
|
Esse servico nao participa do hot path do atendimento.
|
||||||
|
Ele governa o sistema, mas nao executa atendimento em tempo real.
|
||||||
|
|
||||||
|
## Decisao sobre repositorio
|
||||||
|
Neste primeiro momento, os dois servicos permanecem no mesmo repositorio.
|
||||||
|
|
||||||
|
Motivos:
|
||||||
|
- menor custo operacional inicial
|
||||||
|
- versionamento conjunto das fronteiras compartilhadas
|
||||||
|
- mais facilidade para evoluir contratos internos
|
||||||
|
- menos atrito no inicio da iniciativa
|
||||||
|
|
||||||
|
No futuro, se a operacao justificar, eles podem ser separados em repositorios diferentes.
|
||||||
|
Essa separacao nao e obrigatoria agora.
|
||||||
|
|
||||||
|
## Fronteira entre os servicos
|
||||||
|
|
||||||
|
### O que pertence ao servico de produto
|
||||||
|
- LLM do atendimento
|
||||||
|
- orquestrador
|
||||||
|
- registry de tools ativas
|
||||||
|
- execucao de tools aprovadas
|
||||||
|
- fluxo de conversa
|
||||||
|
- integracoes com canais externos de atendimento
|
||||||
|
- persistencia operacional do usuario final
|
||||||
|
|
||||||
|
### O que pertence ao servico administrativo
|
||||||
|
- `StaffAccount`
|
||||||
|
- permissao por papel
|
||||||
|
- painel interno
|
||||||
|
- configuracao administrativa
|
||||||
|
- relatorios e dashboards
|
||||||
|
- pipeline de geracao de tools
|
||||||
|
- versionamento de tools
|
||||||
|
- aprovacao/publicacao
|
||||||
|
- trilha de auditoria
|
||||||
|
|
||||||
|
## Principio de integracao entre os servicos
|
||||||
|
A integracao entre `product` e `admin` deve ser preferencialmente assincrona ou orientada a publicacao de estado.
|
||||||
|
O runtime de produto nao deve depender de uma chamada online ao servico administrativo para responder ao cliente.
|
||||||
|
|
||||||
|
Regra obrigatoria:
|
||||||
|
- o atendimento deve continuar funcionando mesmo se o servico administrativo estiver indisponivel.
|
||||||
|
|
||||||
|
## Modelo de acoplamento permitido
|
||||||
|
|
||||||
|
### Permitido
|
||||||
|
- leitura de tools publicadas
|
||||||
|
- leitura de configuracoes marcadas como ativas
|
||||||
|
- leitura de versoes aprovadas
|
||||||
|
- sincronizacao de metadados publicados
|
||||||
|
- consumo de eventos ou snapshots administrativos
|
||||||
|
|
||||||
|
### Nao permitido no hot path do atendimento
|
||||||
|
- gerar tool sob demanda durante o atendimento
|
||||||
|
- validar codigo em tempo real no runtime do produto
|
||||||
|
- depender de login administrativo para executar atendimento
|
||||||
|
- bloquear resposta ao usuario aguardando operacao do servico administrativo
|
||||||
|
|
||||||
|
## Estrategia de dados
|
||||||
|
|
||||||
|
### Banco do servico de produto
|
||||||
|
Responsavel por:
|
||||||
|
- usuarios de atendimento
|
||||||
|
- pedidos
|
||||||
|
- revisoes
|
||||||
|
- locacoes
|
||||||
|
- conversas
|
||||||
|
- estado operacional
|
||||||
|
- referencias de tools ativas necessarias ao runtime
|
||||||
|
|
||||||
|
### Banco do servico administrativo
|
||||||
|
Responsavel por:
|
||||||
|
- contas internas (`StaffAccount`)
|
||||||
|
- sessoes e credenciais administrativas
|
||||||
|
- configuracoes do sistema
|
||||||
|
- relatorios consolidados
|
||||||
|
- drafts de tools
|
||||||
|
- jobs de geracao
|
||||||
|
- execucoes de validacao
|
||||||
|
- publicacoes
|
||||||
|
- auditoria
|
||||||
|
|
||||||
|
## Conexao entre dados dos dois servicos
|
||||||
|
Existem duas estrategias validas para as proximas fases:
|
||||||
|
|
||||||
|
1. Banco administrativo consulta dados consolidados do produto por replicacao, ETL ou views dedicadas para relatorios.
|
||||||
|
2. Banco administrativo recebe snapshots/eventos do produto para alimentar relatorios e auditoria operacional.
|
||||||
|
|
||||||
|
Decisao inicial recomendada:
|
||||||
|
- manter o produto como fonte operacional
|
||||||
|
- usar o servico administrativo para leitura consolidada, auditoria e governanca
|
||||||
|
- evitar escrita administrativa direta nas tabelas operacionais do atendimento, salvo casos explicitamente versionados e controlados
|
||||||
|
|
||||||
|
## Estrutura tecnica sugerida no monorepo
|
||||||
|
|
||||||
|
### Produto
|
||||||
|
- `app/` permanece como nucleo do runtime de atendimento
|
||||||
|
- entrypoints de atendimento e integracoes continuam aqui
|
||||||
|
|
||||||
|
### Administrativo
|
||||||
|
Criar uma nova arvore dedicada, por exemplo:
|
||||||
|
- `admin_app/`
|
||||||
|
- `api/`
|
||||||
|
- `services/`
|
||||||
|
- `repositories/`
|
||||||
|
- `models/`
|
||||||
|
- `main.py`
|
||||||
|
|
||||||
|
Ou, se quisermos maximizar reaproveitamento de convencao atual:
|
||||||
|
- `app_admin/`
|
||||||
|
|
||||||
|
A escolha do nome pode ser definida na fase de scaffold.
|
||||||
|
O importante nesta ADR e a separacao de runtime e responsabilidade.
|
||||||
|
|
||||||
|
## Deploy esperado
|
||||||
|
No medio prazo, o deploy deve prever dois servicos distintos:
|
||||||
|
|
||||||
|
- `orquestrador-product`
|
||||||
|
- `orquestrador-admin`
|
||||||
|
|
||||||
|
Cada um com:
|
||||||
|
- variaveis de ambiente proprias
|
||||||
|
- processo/servico dedicado
|
||||||
|
- observabilidade propria
|
||||||
|
- escala independente
|
||||||
|
|
||||||
|
## Implicacoes para modelo de IA
|
||||||
|
A geracao de tools e automacao administrativa podem usar um modelo diferente do atendimento.
|
||||||
|
Essa escolha fica facilitada pela separacao de servicos, pois:
|
||||||
|
- evita disputa de recurso e custo com o chat principal
|
||||||
|
- permite tuning de latencia e qualidade por caso de uso
|
||||||
|
- reduz risco de sobrecarregar o atendimento
|
||||||
|
|
||||||
|
## Regras obrigatorias decorrentes desta ADR
|
||||||
|
1. O runtime de produto nao executa pipeline de geracao de tools.
|
||||||
|
2. O servico administrativo nao participa do hot path de resposta ao cliente.
|
||||||
|
3. Toda tool nova nasce no servico administrativo e so chega ao produto depois de publicada.
|
||||||
|
4. Relatorios e configuracoes internas pertencem ao servico administrativo.
|
||||||
|
5. O produto so consome estado publicado e aprovado.
|
||||||
|
6. Deploys do servico administrativo nao devem exigir redeploy simultaneo do produto, salvo mudanca de contrato compartilhado.
|
||||||
|
|
||||||
|
## Sequencia recomendada de implementacao
|
||||||
|
1. Formalizar esta arquitetura em documentacao.
|
||||||
|
2. Criar fundacao do servico administrativo no monorepo.
|
||||||
|
3. Implementar `StaffAccount`, auth e papeis.
|
||||||
|
4. Criar area de configuracao e relatorios basicos.
|
||||||
|
5. Criar entidades de draft/publicacao de tools.
|
||||||
|
6. Implementar pipeline isolado de geracao e validacao.
|
||||||
|
7. Integrar publicacao de tools com o runtime de produto.
|
||||||
|
|
||||||
|
## Consequencias
|
||||||
|
### Positivas
|
||||||
|
- isola o atendimento das cargas administrativas
|
||||||
|
- melhora seguranca
|
||||||
|
- facilita escalabilidade independente
|
||||||
|
- prepara o sistema para governanca e auditoria reais
|
||||||
|
- reduz risco operacional no produto
|
||||||
|
|
||||||
|
### Custos
|
||||||
|
- aumenta a complexidade arquitetural
|
||||||
|
- exige contratos claros entre servicos
|
||||||
|
- traz mais trabalho de deploy, observabilidade e configuracao
|
||||||
|
- exige estrategia de compartilhamento de dados para relatorios
|
||||||
|
|
||||||
|
## Fora do escopo desta ADR
|
||||||
|
- implementar o scaffold real do segundo servico
|
||||||
|
- escolher o modelo definitivo de geracao
|
||||||
|
- definir o formato final de sincronizacao de dados analiticos
|
||||||
|
- definir a UI final do painel administrativo
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
import unittest
|
||||||
|
|
||||||
|
from fastapi.testclient import TestClient
|
||||||
|
|
||||||
|
from admin_app.app_factory import create_app
|
||||||
|
from admin_app.core.settings import AdminSettings
|
||||||
|
|
||||||
|
|
||||||
|
class AdminAppBootstrapTests(unittest.TestCase):
|
||||||
|
def test_admin_app_root_endpoint(self):
|
||||||
|
app = create_app(AdminSettings(admin_environment="staging"))
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
response = client.get("/")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(
|
||||||
|
response.json(),
|
||||||
|
{
|
||||||
|
"service": "orquestrador-admin",
|
||||||
|
"status": "ok",
|
||||||
|
"message": "Servico administrativo inicializado.",
|
||||||
|
"environment": "staging",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_admin_app_health_endpoint(self):
|
||||||
|
app = create_app(AdminSettings(admin_version="1.2.3"))
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
response = client.get("/health")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(
|
||||||
|
response.json(),
|
||||||
|
{"service": "orquestrador-admin", "status": "ok", "version": "1.2.3"},
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_admin_app_system_info_endpoint(self):
|
||||||
|
settings = AdminSettings(
|
||||||
|
admin_app_name="Admin Interno",
|
||||||
|
admin_environment="development",
|
||||||
|
admin_version="0.9.0",
|
||||||
|
admin_api_prefix="/admin",
|
||||||
|
admin_debug=True,
|
||||||
|
)
|
||||||
|
app = create_app(settings)
|
||||||
|
client = TestClient(app)
|
||||||
|
|
||||||
|
response = client.get("/admin/system/info")
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(
|
||||||
|
response.json(),
|
||||||
|
{
|
||||||
|
"service": "orquestrador-admin",
|
||||||
|
"app_name": "Admin Interno",
|
||||||
|
"environment": "development",
|
||||||
|
"version": "0.9.0",
|
||||||
|
"api_prefix": "/admin",
|
||||||
|
"debug": True,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Loading…
Reference in New Issue