You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
735 lines
31 KiB
Python
735 lines
31 KiB
Python
from __future__ import annotations
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
from datetime import UTC, datetime
|
|
|
|
from admin_app.core.settings import AdminSettings
|
|
from admin_app.db.models import ToolDraft, ToolVersion
|
|
from admin_app.repositories.tool_draft_repository import ToolDraftRepository
|
|
from admin_app.repositories.tool_version_repository import ToolVersionRepository
|
|
from shared.contracts import (
|
|
ServiceName,
|
|
TOOL_LIFECYCLE_STAGES,
|
|
ToolLifecycleStatus,
|
|
ToolParameterType,
|
|
)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class BootstrapToolCatalogEntry:
|
|
tool_name: str
|
|
display_name: str
|
|
description: str
|
|
domain: str
|
|
parameter_count: int
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ToolIntakeDomainOption:
|
|
value: str
|
|
label: str
|
|
description: str
|
|
|
|
|
|
_BOOTSTRAP_TOOL_CATALOG: tuple[BootstrapToolCatalogEntry, ...] = (
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="consultar_estoque",
|
|
display_name="Consultar estoque",
|
|
description="Consulta veiculos disponiveis no estoque comercial.",
|
|
domain="vendas",
|
|
parameter_count=4,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="validar_cliente_venda",
|
|
display_name="Validar cliente para venda",
|
|
description="Avalia elegibilidade de credito para operacoes de venda.",
|
|
domain="vendas",
|
|
parameter_count=2,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="avaliar_veiculo_troca",
|
|
display_name="Avaliar veiculo de troca",
|
|
description="Estima o valor de entrada de um veiculo usado.",
|
|
domain="vendas",
|
|
parameter_count=3,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="agendar_revisao",
|
|
display_name="Agendar revisao",
|
|
description="Abre um agendamento de revisao ou manutencao.",
|
|
domain="revisao",
|
|
parameter_count=6,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="listar_agendamentos_revisao",
|
|
display_name="Listar agendamentos de revisao",
|
|
description="Consulta a fila de agendamentos de revisao do cliente.",
|
|
domain="revisao",
|
|
parameter_count=3,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="cancelar_agendamento_revisao",
|
|
display_name="Cancelar agendamento de revisao",
|
|
description="Cancela um agendamento existente por protocolo.",
|
|
domain="revisao",
|
|
parameter_count=2,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="editar_data_revisao",
|
|
display_name="Editar data de revisao",
|
|
description="Remarca uma revisao para um novo horario.",
|
|
domain="revisao",
|
|
parameter_count=2,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="realizar_pedido",
|
|
display_name="Realizar pedido",
|
|
description="Efetiva um pedido de compra com o veiculo escolhido.",
|
|
domain="vendas",
|
|
parameter_count=2,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="listar_pedidos",
|
|
display_name="Listar pedidos",
|
|
description="Consulta pedidos ja abertos pelo cliente.",
|
|
domain="vendas",
|
|
parameter_count=3,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="cancelar_pedido",
|
|
display_name="Cancelar pedido",
|
|
description="Cancela um pedido existente com motivo registrado.",
|
|
domain="vendas",
|
|
parameter_count=2,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="consultar_frota_aluguel",
|
|
display_name="Consultar frota de aluguel",
|
|
description="Lista veiculos disponiveis para locacao.",
|
|
domain="locacao",
|
|
parameter_count=6,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="abrir_locacao_aluguel",
|
|
display_name="Abrir locacao de aluguel",
|
|
description="Inicia um contrato de locacao de veiculo.",
|
|
domain="locacao",
|
|
parameter_count=7,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="registrar_devolucao_aluguel",
|
|
display_name="Registrar devolucao de aluguel",
|
|
description="Fecha uma locacao e devolve o veiculo para a frota.",
|
|
domain="locacao",
|
|
parameter_count=4,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="registrar_pagamento_aluguel",
|
|
display_name="Registrar pagamento de aluguel",
|
|
description="Registra comprovantes e pagamentos de contratos de locacao.",
|
|
domain="locacao",
|
|
parameter_count=7,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="limpar_contexto_conversa",
|
|
display_name="Limpar contexto de conversa",
|
|
description="Reinicia o contexto operacional atual do atendimento.",
|
|
domain="orquestracao",
|
|
parameter_count=1,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="continuar_proximo_pedido",
|
|
display_name="Continuar proximo pedido",
|
|
description="Retoma o proximo pedido pendente do fluxo atual.",
|
|
domain="orquestracao",
|
|
parameter_count=0,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="descartar_pedidos_pendentes",
|
|
display_name="Descartar pedidos pendentes",
|
|
description="Descarta apenas a fila pendente de pedidos do contexto.",
|
|
domain="orquestracao",
|
|
parameter_count=1,
|
|
),
|
|
BootstrapToolCatalogEntry(
|
|
tool_name="cancelar_fluxo_atual",
|
|
display_name="Cancelar fluxo atual",
|
|
description="Interrompe o fluxo corrente sem apagar todo o contexto.",
|
|
domain="orquestracao",
|
|
parameter_count=1,
|
|
),
|
|
)
|
|
|
|
_INTAKE_DOMAIN_OPTIONS: tuple[ToolIntakeDomainOption, ...] = (
|
|
ToolIntakeDomainOption(
|
|
value="vendas",
|
|
label="Vendas",
|
|
description="Ferramentas para estoque, negociacao, pedido e conversao comercial.",
|
|
),
|
|
ToolIntakeDomainOption(
|
|
value="revisao",
|
|
label="Revisao",
|
|
description="Ferramentas para agendamento, remarcacao e operacao da oficina.",
|
|
),
|
|
ToolIntakeDomainOption(
|
|
value="locacao",
|
|
label="Locacao",
|
|
description="Ferramentas para frota, contratos, devolucao e arrecadacao de aluguel.",
|
|
),
|
|
ToolIntakeDomainOption(
|
|
value="orquestracao",
|
|
label="Orquestracao",
|
|
description="Ferramentas internas para fluxo conversacional, contexto e decisao do bot.",
|
|
),
|
|
)
|
|
|
|
|
|
_PARAMETER_TYPE_DESCRIPTIONS = {
|
|
ToolParameterType.STRING: "Texto livre, codigos e identificadores.",
|
|
ToolParameterType.INTEGER: "Valores inteiros para limites, anos e contagens.",
|
|
ToolParameterType.NUMBER: "Valores numericos decimais, como preco e diaria.",
|
|
ToolParameterType.BOOLEAN: "Marcadores verdadeiro ou falso para decisoes operacionais.",
|
|
ToolParameterType.OBJECT: "Estruturas compostas para payloads complexos.",
|
|
ToolParameterType.ARRAY: "Colecoes ordenadas de valores.",
|
|
}
|
|
|
|
_TOOL_NAME_PATTERN = re.compile(r"^[a-z][a-z0-9_]{2,63}$")
|
|
_PARAMETER_NAME_PATTERN = re.compile(r"^[a-z][a-z0-9_]{1,63}$")
|
|
|
|
|
|
class ToolManagementService:
|
|
def __init__(
|
|
self,
|
|
settings: AdminSettings,
|
|
draft_repository: ToolDraftRepository | None = None,
|
|
version_repository: ToolVersionRepository | None = None,
|
|
):
|
|
self.settings = settings
|
|
self.draft_repository = draft_repository
|
|
self.version_repository = version_repository
|
|
|
|
def build_overview_payload(self) -> dict:
|
|
catalog = self.list_publication_catalog()
|
|
persisted_draft_count = len(self.draft_repository.list_drafts()) if self.draft_repository else 0
|
|
persisted_version_count = 0
|
|
if self.version_repository is not None:
|
|
persisted_version_count = len(self.version_repository.list_versions())
|
|
elif self.draft_repository is not None:
|
|
persisted_version_count = sum(draft.version_count for draft in self.draft_repository.list_drafts())
|
|
return {
|
|
"mode": "admin_tool_draft_governance",
|
|
"metrics": [
|
|
{
|
|
"key": "active_catalog",
|
|
"label": "Tools mapeadas",
|
|
"value": str(len(catalog)),
|
|
"description": "Catalogo bootstrap refletindo a base de tools conhecida no monorepo.",
|
|
},
|
|
{
|
|
"key": "lifecycle_stages",
|
|
"label": "Etapas de lifecycle",
|
|
"value": str(len(TOOL_LIFECYCLE_STAGES)),
|
|
"description": "Estados compartilhados entre governanca administrativa e publicacao.",
|
|
},
|
|
{
|
|
"key": "parameter_types",
|
|
"label": "Tipos de parametro",
|
|
"value": str(len(ToolParameterType)),
|
|
"description": "Tipos aceitos pelo contrato inicial de publicacao de tools.",
|
|
},
|
|
{
|
|
"key": "persisted_drafts",
|
|
"label": "Drafts persistidos",
|
|
"value": str(persisted_draft_count),
|
|
"description": "Pre-cadastros administrativos ja gravados no armazenamento proprio do admin.",
|
|
},
|
|
{
|
|
"key": "persisted_versions",
|
|
"label": "Versoes administrativas",
|
|
"value": str(persisted_version_count),
|
|
"description": "Historico versionado das iteracoes de cada tool governada pelo admin.",
|
|
},
|
|
],
|
|
"workflow": self.build_lifecycle_payload(),
|
|
"next_steps": [
|
|
"Persistir artefatos de geracao e validacao por versao sem perder o historico administrativo.",
|
|
"Abrir filas de revisao, aprovacao e ativacao com auditoria ponta a ponta.",
|
|
"Conectar publicacoes versionadas ao runtime de produto com rollback controlado.",
|
|
],
|
|
}
|
|
|
|
def build_contracts_payload(self) -> dict:
|
|
return {
|
|
"publication_source_service": ServiceName.ADMIN,
|
|
"publication_target_service": ServiceName.PRODUCT,
|
|
"lifecycle_statuses": self.build_lifecycle_payload(),
|
|
"parameter_types": [
|
|
{
|
|
"code": parameter_type,
|
|
"label": parameter_type.value.upper(),
|
|
"description": _PARAMETER_TYPE_DESCRIPTIONS[parameter_type],
|
|
}
|
|
for parameter_type in ToolParameterType
|
|
],
|
|
"publication_fields": [
|
|
"source_service",
|
|
"target_service",
|
|
"publication_id",
|
|
"published_tool",
|
|
"emitted_at",
|
|
],
|
|
"published_tool_fields": [
|
|
"tool_name",
|
|
"display_name",
|
|
"description",
|
|
"version",
|
|
"status",
|
|
"parameters",
|
|
"implementation_module",
|
|
"implementation_callable",
|
|
"checksum",
|
|
"published_at",
|
|
"published_by",
|
|
],
|
|
}
|
|
|
|
def build_draft_form_payload(self) -> dict:
|
|
return {
|
|
"mode": "validated_preview",
|
|
"domain_options": [
|
|
{
|
|
"value": option.value,
|
|
"label": option.label,
|
|
"description": option.description,
|
|
}
|
|
for option in _INTAKE_DOMAIN_OPTIONS
|
|
],
|
|
"parameter_types": [
|
|
{
|
|
"code": parameter_type,
|
|
"label": parameter_type.value.upper(),
|
|
"description": _PARAMETER_TYPE_DESCRIPTIONS[parameter_type],
|
|
}
|
|
for parameter_type in ToolParameterType
|
|
],
|
|
"naming_rules": [
|
|
"tool_name deve usar snake_case minusculo, sem espacos, com 3 a 64 caracteres.",
|
|
"display_name deve explicar claramente a acao operacional que o bot vai executar.",
|
|
"Cada parametro precisa de nome, tipo, descricao e marcador de obrigatoriedade.",
|
|
],
|
|
"submission_notes": [
|
|
"O colaborador pode preencher, validar e persistir o draft da tool no painel.",
|
|
"Toda tool nova segue para revisao e aprovacao de um diretor antes de qualquer publicacao.",
|
|
"Reenvios da mesma tool reaproveitam o draft raiz e geram uma nova versao administrativa.",
|
|
],
|
|
"approval_notes": [
|
|
"Diretor revisa objetivo, parametros e aderencia ao contrato compartilhado.",
|
|
"A publicacao para o runtime de produto so pode acontecer apos aprovacao humana.",
|
|
"Campos livres e payloads complexos exigem criterio maior na etapa de revisao.",
|
|
],
|
|
}
|
|
|
|
def build_drafts_payload(self) -> dict:
|
|
if self.draft_repository is None:
|
|
return {
|
|
"storage_status": "pending_persistence",
|
|
"message": (
|
|
"A nova tela de cadastro ja valida o pre-cadastro da tool no painel, mas a persistencia de ToolDraft ainda nao foi conectada neste runtime."
|
|
),
|
|
"drafts": [],
|
|
"supported_statuses": [ToolLifecycleStatus.DRAFT],
|
|
}
|
|
|
|
drafts = self.draft_repository.list_drafts(statuses=(ToolLifecycleStatus.DRAFT,))
|
|
message = (
|
|
"Nenhum draft administrativo salvo ainda."
|
|
if not drafts
|
|
else f"{len(drafts)} draft(s) administrativo(s) salvo(s) no admin com historico versionado."
|
|
)
|
|
return {
|
|
"storage_status": "admin_database",
|
|
"message": message,
|
|
"drafts": [self._serialize_draft_summary(draft) for draft in drafts],
|
|
"supported_statuses": [ToolLifecycleStatus.DRAFT],
|
|
}
|
|
|
|
def build_review_queue_payload(self) -> dict:
|
|
return {
|
|
"queue_mode": "bootstrap_empty_state",
|
|
"message": (
|
|
"A fila de revisao ainda opera em estado vazio ate a criacao das entidades de geracao e validacao conectadas as versoes persistidas de cada draft."
|
|
),
|
|
"items": [],
|
|
"supported_statuses": [
|
|
ToolLifecycleStatus.GENERATED,
|
|
ToolLifecycleStatus.VALIDATED,
|
|
ToolLifecycleStatus.APPROVED,
|
|
ToolLifecycleStatus.FAILED,
|
|
],
|
|
}
|
|
|
|
def build_publications_payload(self) -> dict:
|
|
return {
|
|
"source": "bootstrap_catalog",
|
|
"target_service": ServiceName.PRODUCT,
|
|
"publications": self.list_publication_catalog(),
|
|
}
|
|
|
|
def create_draft_submission(
|
|
self,
|
|
payload: dict,
|
|
*,
|
|
owner_staff_account_id: int | None = None,
|
|
owner_name: str | None = None,
|
|
) -> dict:
|
|
normalized = self._normalize_draft_payload(payload)
|
|
warnings = self._build_intake_warnings(normalized)
|
|
required_parameter_count = sum(1 for parameter in normalized["parameters"] if parameter["required"])
|
|
summary = self._build_draft_summary(normalized)
|
|
stored_parameters = self._serialize_parameters_for_storage(normalized["parameters"])
|
|
|
|
if self.draft_repository is None:
|
|
version_number = 1
|
|
version_count = 1
|
|
version_id = self._build_preview_version_id(normalized["tool_name"], version_number)
|
|
return {
|
|
"storage_status": "validated_preview",
|
|
"message": "Pre-cadastro validado no painel. A persistencia definitiva entra na fase de governanca de tools.",
|
|
"draft_preview": {
|
|
"draft_id": f"preview::{normalized['tool_name']}",
|
|
"version_id": version_id,
|
|
"tool_name": normalized["tool_name"],
|
|
"display_name": normalized["display_name"],
|
|
"domain": normalized["domain"],
|
|
"status": ToolLifecycleStatus.DRAFT,
|
|
"summary": summary,
|
|
"business_goal": normalized["business_goal"],
|
|
"version_number": version_number,
|
|
"version_count": version_count,
|
|
"parameter_count": len(normalized["parameters"]),
|
|
"required_parameter_count": required_parameter_count,
|
|
"requires_director_approval": True,
|
|
"owner_name": owner_name,
|
|
"parameters": normalized["parameters"],
|
|
},
|
|
"warnings": warnings,
|
|
"next_steps": [
|
|
"Persistir o draft administrativo em armazenamento proprio do admin na fase 5.",
|
|
"Encaminhar a tool para revisao e aprovacao de um diretor.",
|
|
"Executar pipeline de geracao, validacao e publicacao antes da ativacao no produto.",
|
|
],
|
|
}
|
|
|
|
if owner_staff_account_id is None:
|
|
raise ValueError("owner_staff_account_id e obrigatorio para persistir o draft.")
|
|
|
|
existing_draft = self.draft_repository.get_by_tool_name(normalized["tool_name"])
|
|
next_version_number = self._resolve_next_version_number(normalized["tool_name"], existing_draft)
|
|
next_version_count = next_version_number if existing_draft is None else max(existing_draft.version_count + 1, next_version_number)
|
|
|
|
if existing_draft is None:
|
|
draft = self.draft_repository.create(
|
|
tool_name=normalized["tool_name"],
|
|
display_name=normalized["display_name"],
|
|
domain=normalized["domain"],
|
|
description=normalized["description"],
|
|
business_goal=normalized["business_goal"],
|
|
summary=summary,
|
|
parameters_json=stored_parameters,
|
|
required_parameter_count=required_parameter_count,
|
|
current_version_number=next_version_number,
|
|
version_count=next_version_count,
|
|
owner_staff_account_id=owner_staff_account_id,
|
|
owner_display_name=owner_name or "Autor administrativo",
|
|
requires_director_approval=True,
|
|
)
|
|
else:
|
|
draft = self.draft_repository.update_submission(
|
|
existing_draft,
|
|
display_name=normalized["display_name"],
|
|
domain=normalized["domain"],
|
|
description=normalized["description"],
|
|
business_goal=normalized["business_goal"],
|
|
summary=summary,
|
|
parameters_json=stored_parameters,
|
|
required_parameter_count=required_parameter_count,
|
|
current_version_number=next_version_number,
|
|
version_count=next_version_count,
|
|
owner_staff_account_id=owner_staff_account_id,
|
|
owner_display_name=owner_name or "Autor administrativo",
|
|
requires_director_approval=True,
|
|
)
|
|
|
|
version = None
|
|
if self.version_repository is not None:
|
|
version = self.version_repository.create(
|
|
draft_id=draft.id,
|
|
tool_name=draft.tool_name,
|
|
version_number=next_version_number,
|
|
summary=summary,
|
|
description=normalized["description"],
|
|
business_goal=normalized["business_goal"],
|
|
parameters_json=stored_parameters,
|
|
required_parameter_count=required_parameter_count,
|
|
owner_staff_account_id=owner_staff_account_id,
|
|
owner_display_name=owner_name or "Autor administrativo",
|
|
status=ToolLifecycleStatus.DRAFT,
|
|
requires_director_approval=True,
|
|
)
|
|
|
|
return {
|
|
"storage_status": "admin_database",
|
|
"message": "Draft administrativo persistido com sucesso em fluxo versionado.",
|
|
"draft_preview": self._serialize_draft_preview(draft, version),
|
|
"warnings": warnings,
|
|
"next_steps": [
|
|
f"Encaminhar a versao v{draft.current_version_number} para revisao e aprovacao de um diretor.",
|
|
"Conectar a versao persistida ao pipeline de geracao e validacao automatica da tool.",
|
|
"Persistir artefatos e publicacoes associados a cada versao governada.",
|
|
],
|
|
}
|
|
|
|
def preview_draft_submission(self, payload: dict, *, owner_name: str | None = None) -> dict:
|
|
normalized = self._normalize_draft_payload(payload)
|
|
warnings = self._build_intake_warnings(normalized)
|
|
required_parameter_count = sum(1 for parameter in normalized["parameters"] if parameter["required"])
|
|
summary = self._build_draft_summary(normalized)
|
|
existing_draft = None
|
|
if self.draft_repository is not None:
|
|
existing_draft = self.draft_repository.get_by_tool_name(normalized["tool_name"])
|
|
version_number = self._resolve_next_version_number(normalized["tool_name"], existing_draft)
|
|
version_count = version_number if existing_draft is None else max(existing_draft.version_count + 1, version_number)
|
|
return {
|
|
"storage_status": "validated_preview",
|
|
"message": "Pre-cadastro validado no painel com numeracao de versao reservada para a tool.",
|
|
"draft_preview": {
|
|
"draft_id": existing_draft.draft_id if existing_draft is not None else f"preview::{normalized['tool_name']}",
|
|
"version_id": self._build_preview_version_id(normalized["tool_name"], version_number),
|
|
"tool_name": normalized["tool_name"],
|
|
"display_name": normalized["display_name"],
|
|
"domain": normalized["domain"],
|
|
"status": ToolLifecycleStatus.DRAFT,
|
|
"summary": summary,
|
|
"business_goal": normalized["business_goal"],
|
|
"version_number": version_number,
|
|
"version_count": version_count,
|
|
"parameter_count": len(normalized["parameters"]),
|
|
"required_parameter_count": required_parameter_count,
|
|
"requires_director_approval": True,
|
|
"owner_name": owner_name,
|
|
"parameters": normalized["parameters"],
|
|
},
|
|
"warnings": warnings,
|
|
"next_steps": [
|
|
"Persistir a nova versao administrativa para consolidar o historico da tool.",
|
|
"Encaminhar a versao para revisao e aprovacao de um diretor.",
|
|
"Executar pipeline de geracao, validacao e publicacao antes da ativacao no produto.",
|
|
],
|
|
}
|
|
|
|
def build_lifecycle_payload(self) -> list[dict]:
|
|
return [
|
|
{
|
|
"code": stage.code,
|
|
"label": stage.label,
|
|
"description": stage.description,
|
|
"order": stage.order,
|
|
"terminal": stage.terminal,
|
|
}
|
|
for stage in TOOL_LIFECYCLE_STAGES
|
|
]
|
|
|
|
def list_publication_catalog(self) -> list[dict]:
|
|
published_at = datetime.now(UTC)
|
|
return [
|
|
{
|
|
"publication_id": f"bootstrap::{entry.tool_name}::v1",
|
|
"tool_name": entry.tool_name,
|
|
"display_name": entry.display_name,
|
|
"description": entry.description,
|
|
"domain": entry.domain,
|
|
"version": 1,
|
|
"status": ToolLifecycleStatus.ACTIVE,
|
|
"parameter_count": entry.parameter_count,
|
|
"implementation_module": "app.services.tools.handlers",
|
|
"implementation_callable": entry.tool_name,
|
|
"published_by": "bootstrap_catalog",
|
|
"published_at": published_at,
|
|
}
|
|
for entry in _BOOTSTRAP_TOOL_CATALOG
|
|
]
|
|
|
|
def _serialize_draft_summary(self, draft: ToolDraft) -> dict:
|
|
return {
|
|
"draft_id": draft.draft_id,
|
|
"tool_name": draft.tool_name,
|
|
"display_name": draft.display_name,
|
|
"status": draft.status,
|
|
"summary": draft.summary,
|
|
"current_version_number": draft.current_version_number,
|
|
"version_count": draft.version_count,
|
|
"owner_name": draft.owner_display_name,
|
|
"updated_at": draft.updated_at,
|
|
}
|
|
|
|
def _serialize_draft_preview(
|
|
self,
|
|
draft: ToolDraft,
|
|
version: ToolVersion | None = None,
|
|
) -> dict:
|
|
parameters = self._serialize_parameters_for_response(draft.parameters_json)
|
|
version_id = version.version_id if version is not None else self._build_preview_version_id(
|
|
draft.tool_name,
|
|
draft.current_version_number,
|
|
)
|
|
version_number = version.version_number if version is not None else draft.current_version_number
|
|
return {
|
|
"draft_id": draft.draft_id,
|
|
"version_id": version_id,
|
|
"tool_name": draft.tool_name,
|
|
"display_name": draft.display_name,
|
|
"domain": draft.domain,
|
|
"status": draft.status,
|
|
"summary": draft.summary,
|
|
"business_goal": draft.business_goal,
|
|
"version_number": version_number,
|
|
"version_count": draft.version_count,
|
|
"parameter_count": len(parameters),
|
|
"required_parameter_count": draft.required_parameter_count,
|
|
"requires_director_approval": draft.requires_director_approval,
|
|
"owner_name": draft.owner_display_name,
|
|
"parameters": parameters,
|
|
}
|
|
|
|
@staticmethod
|
|
def _serialize_parameters_for_storage(parameters: list[dict]) -> list[dict]:
|
|
return [
|
|
{
|
|
"name": parameter["name"],
|
|
"parameter_type": parameter["parameter_type"].value,
|
|
"description": parameter["description"],
|
|
"required": parameter["required"],
|
|
}
|
|
for parameter in parameters
|
|
]
|
|
|
|
@staticmethod
|
|
def _serialize_parameters_for_response(parameters_json: list[dict] | None) -> list[dict]:
|
|
return [
|
|
{
|
|
"name": str((parameter or {}).get("name") or "").strip().lower(),
|
|
"parameter_type": ToolParameterType(str((parameter or {}).get("parameter_type") or "string").strip().lower()),
|
|
"description": str((parameter or {}).get("description") or "").strip(),
|
|
"required": bool((parameter or {}).get("required", True)),
|
|
}
|
|
for parameter in (parameters_json or [])
|
|
]
|
|
|
|
@staticmethod
|
|
def _build_draft_summary(payload: dict) -> str:
|
|
return (
|
|
f"{payload['display_name']} pronta para seguir como draft com {len(payload['parameters'])} parametro(s) e revisao obrigatoria de diretor."
|
|
)
|
|
|
|
@staticmethod
|
|
def _build_preview_version_id(tool_name: str, version_number: int) -> str:
|
|
return f"tool_version::{str(tool_name or '').strip().lower()}::v{int(version_number)}"
|
|
|
|
def _resolve_next_version_number(
|
|
self,
|
|
tool_name: str,
|
|
existing_draft: ToolDraft | None,
|
|
) -> int:
|
|
repository_version = (
|
|
self.version_repository.get_next_version_number(tool_name)
|
|
if self.version_repository is not None
|
|
else 1
|
|
)
|
|
if existing_draft is None:
|
|
return repository_version
|
|
return max(repository_version, existing_draft.current_version_number + 1)
|
|
|
|
def _normalize_draft_payload(self, payload: dict) -> dict:
|
|
tool_name = str(payload.get("tool_name") or "").strip().lower()
|
|
if not _TOOL_NAME_PATTERN.fullmatch(tool_name):
|
|
raise ValueError("tool_name deve usar snake_case minusculo com 3 a 64 caracteres.")
|
|
|
|
display_name = str(payload.get("display_name") or "").strip()
|
|
if len(display_name) < 4:
|
|
raise ValueError("display_name precisa ter pelo menos 4 caracteres.")
|
|
|
|
domain = str(payload.get("domain") or "").strip().lower()
|
|
valid_domains = {option.value for option in _INTAKE_DOMAIN_OPTIONS}
|
|
if domain not in valid_domains:
|
|
raise ValueError("Selecione um dominio valido para a nova tool.")
|
|
|
|
description = str(payload.get("description") or "").strip()
|
|
if len(description) < 16:
|
|
raise ValueError("A descricao precisa ter pelo menos 16 caracteres para contextualizar a tool.")
|
|
|
|
business_goal = str(payload.get("business_goal") or "").strip()
|
|
if len(business_goal) < 12:
|
|
raise ValueError("Explique o objetivo operacional da tool com pelo menos 12 caracteres.")
|
|
|
|
raw_parameters = payload.get("parameters") or []
|
|
if not isinstance(raw_parameters, list):
|
|
raise ValueError("Os parametros enviados para a tool sao invalidos.")
|
|
|
|
seen_parameter_names: set[str] = set()
|
|
parameters: list[dict] = []
|
|
for raw_parameter in raw_parameters:
|
|
name = str((raw_parameter or {}).get("name") or "").strip().lower()
|
|
if not name:
|
|
continue
|
|
if not _PARAMETER_NAME_PATTERN.fullmatch(name):
|
|
raise ValueError("Cada parametro deve usar snake_case minusculo com pelo menos 2 caracteres.")
|
|
if name in seen_parameter_names:
|
|
raise ValueError("Nao e permitido repetir nomes de parametro na mesma tool.")
|
|
seen_parameter_names.add(name)
|
|
|
|
raw_parameter_type = (raw_parameter or {}).get("parameter_type") or ""
|
|
parameter_type = (
|
|
raw_parameter_type
|
|
if isinstance(raw_parameter_type, ToolParameterType)
|
|
else ToolParameterType(str(raw_parameter_type).strip().lower())
|
|
)
|
|
parameter_description = str((raw_parameter or {}).get("description") or "").strip()
|
|
if len(parameter_description) < 8:
|
|
raise ValueError("Cada parametro precisa de uma descricao com pelo menos 8 caracteres.")
|
|
|
|
parameters.append(
|
|
{
|
|
"name": name,
|
|
"parameter_type": parameter_type,
|
|
"description": parameter_description,
|
|
"required": bool((raw_parameter or {}).get("required", True)),
|
|
}
|
|
)
|
|
|
|
if len(parameters) > 10:
|
|
raise ValueError("A fase inicial do painel aceita no maximo 10 parametros por tool.")
|
|
|
|
return {
|
|
"tool_name": tool_name,
|
|
"display_name": display_name,
|
|
"domain": domain,
|
|
"description": description,
|
|
"business_goal": business_goal,
|
|
"parameters": parameters,
|
|
}
|
|
|
|
def _build_intake_warnings(self, payload: dict) -> list[str]:
|
|
warnings: list[str] = []
|
|
parameters = payload["parameters"]
|
|
if not parameters:
|
|
warnings.append("A tool foi cadastrada sem parametros. Confirme se a acao realmente nao exige entrada contextual.")
|
|
if len(parameters) >= 6:
|
|
warnings.append("A quantidade de parametros ja pede uma revisao mais cuidadosa antes da aprovacao de diretor.")
|
|
if any(parameter["parameter_type"] in {ToolParameterType.OBJECT, ToolParameterType.ARRAY} for parameter in parameters):
|
|
warnings.append("Parametros compostos exigem atencao extra na revisao porque podem esconder payloads mais sensiveis.")
|
|
if payload["domain"] == "orquestracao":
|
|
warnings.append("Tools de orquestracao precisam confirmar claramente como afetam o fluxo do bot antes da ativacao.")
|
|
return warnings
|
|
|