|
|
|
|
@ -1,9 +1,11 @@
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
import re
|
|
|
|
|
from dataclasses import dataclass
|
|
|
|
|
from datetime import UTC, datetime
|
|
|
|
|
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
from admin_app.catalogs import BOOTSTRAP_TOOL_CATALOG, INTAKE_DOMAIN_OPTIONS
|
|
|
|
|
from admin_app.core.settings import AdminSettings
|
|
|
|
|
from admin_app.db.models import ToolDraft, ToolMetadata, ToolVersion
|
|
|
|
|
from admin_app.db.models.tool_artifact import (
|
|
|
|
|
@ -16,183 +18,18 @@ from admin_app.repositories.tool_draft_repository import ToolDraftRepository
|
|
|
|
|
from admin_app.repositories.tool_metadata_repository import ToolMetadataRepository
|
|
|
|
|
from admin_app.repositories.tool_version_repository import ToolVersionRepository
|
|
|
|
|
from shared.contracts import (
|
|
|
|
|
AdminPermission,
|
|
|
|
|
GENERATED_TOOL_ENTRYPOINT,
|
|
|
|
|
GENERATED_TOOLS_PACKAGE,
|
|
|
|
|
ServiceName,
|
|
|
|
|
StaffRole,
|
|
|
|
|
TOOL_LIFECYCLE_STAGES,
|
|
|
|
|
ToolLifecycleStatus,
|
|
|
|
|
ToolParameterType,
|
|
|
|
|
build_generated_tool_module_name,
|
|
|
|
|
build_generated_tool_module_path,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@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.",
|
|
|
|
|
),
|
|
|
|
|
normalize_staff_role,
|
|
|
|
|
role_has_permission,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -207,7 +44,15 @@ _PARAMETER_TYPE_DESCRIPTIONS = {
|
|
|
|
|
|
|
|
|
|
_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}$")
|
|
|
|
|
_RESERVED_CORE_TOOL_NAMES = frozenset(entry.tool_name for entry in _BOOTSTRAP_TOOL_CATALOG)
|
|
|
|
|
_RESERVED_CORE_TOOL_NAMES = frozenset(entry.tool_name for entry in BOOTSTRAP_TOOL_CATALOG)
|
|
|
|
|
_PUBLISHED_TOOL_STATUSES = (ToolLifecycleStatus.ACTIVE,)
|
|
|
|
|
_REVIEW_QUEUE_STATUSES = (
|
|
|
|
|
ToolLifecycleStatus.DRAFT,
|
|
|
|
|
ToolLifecycleStatus.GENERATED,
|
|
|
|
|
ToolLifecycleStatus.VALIDATED,
|
|
|
|
|
ToolLifecycleStatus.APPROVED,
|
|
|
|
|
ToolLifecycleStatus.FAILED,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ToolManagementService:
|
|
|
|
|
@ -225,6 +70,61 @@ class ToolManagementService:
|
|
|
|
|
self.metadata_repository = metadata_repository
|
|
|
|
|
self.artifact_repository = artifact_repository
|
|
|
|
|
|
|
|
|
|
def _resolve_repository_session(self) -> Session | None:
|
|
|
|
|
repository_sessions = [
|
|
|
|
|
repository.db
|
|
|
|
|
for repository in (
|
|
|
|
|
self.draft_repository,
|
|
|
|
|
self.version_repository,
|
|
|
|
|
self.metadata_repository,
|
|
|
|
|
self.artifact_repository,
|
|
|
|
|
)
|
|
|
|
|
if getattr(repository, "db", None) is not None
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if not repository_sessions:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
primary_session = repository_sessions[0]
|
|
|
|
|
for repository_session in repository_sessions[1:]:
|
|
|
|
|
if repository_session is not primary_session:
|
|
|
|
|
raise RuntimeError("Tool governance repositories must share the same admin database session.")
|
|
|
|
|
return primary_session
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _commit_repository_session(
|
|
|
|
|
repository_session: Session,
|
|
|
|
|
*,
|
|
|
|
|
draft: ToolDraft,
|
|
|
|
|
version: ToolVersion | None = None,
|
|
|
|
|
) -> None:
|
|
|
|
|
repository_session.commit()
|
|
|
|
|
repository_session.refresh(draft)
|
|
|
|
|
if version is not None:
|
|
|
|
|
repository_session.refresh(version)
|
|
|
|
|
|
|
|
|
|
def _build_submission_policy(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
submitter_role: StaffRole | str | None = None,
|
|
|
|
|
) -> dict:
|
|
|
|
|
normalized_role = normalize_staff_role(submitter_role) if submitter_role is not None else None
|
|
|
|
|
submitter_can_publish_now = (
|
|
|
|
|
role_has_permission(normalized_role, AdminPermission.PUBLISH_TOOLS)
|
|
|
|
|
if normalized_role is not None
|
|
|
|
|
else False
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
"mode": "draft_only",
|
|
|
|
|
"submitter_role": normalized_role,
|
|
|
|
|
"submitter_can_publish_now": submitter_can_publish_now,
|
|
|
|
|
"direct_publication_blocked": True,
|
|
|
|
|
"requires_director_approval": True,
|
|
|
|
|
"required_approver_role": StaffRole.DIRETOR,
|
|
|
|
|
"required_review_permission": AdminPermission.REVIEW_TOOL_GENERATIONS,
|
|
|
|
|
"required_publish_permission": AdminPermission.PUBLISH_TOOLS,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def build_overview_payload(self) -> dict:
|
|
|
|
|
catalog_payload = self.build_publications_payload()
|
|
|
|
|
catalog = catalog_payload["publications"]
|
|
|
|
|
@ -325,16 +225,27 @@ class ToolManagementService:
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def build_draft_form_payload(self) -> dict:
|
|
|
|
|
def build_draft_form_payload(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
submitter_role: StaffRole | str | None = None,
|
|
|
|
|
) -> dict:
|
|
|
|
|
submission_policy = self._build_submission_policy(submitter_role=submitter_role)
|
|
|
|
|
submitter_note = (
|
|
|
|
|
"Sua sessao pode cadastrar e salvar o draft, mas nao publica a tool diretamente."
|
|
|
|
|
if not submission_policy["submitter_can_publish_now"]
|
|
|
|
|
else "Mesmo com permissao de publicacao, este formulario sempre salva a tool primeiro como draft versionado."
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
"mode": "validated_preview",
|
|
|
|
|
"submission_policy": submission_policy,
|
|
|
|
|
"domain_options": [
|
|
|
|
|
{
|
|
|
|
|
"value": option.value,
|
|
|
|
|
"label": option.label,
|
|
|
|
|
"description": option.description,
|
|
|
|
|
}
|
|
|
|
|
for option in _INTAKE_DOMAIN_OPTIONS
|
|
|
|
|
for option in INTAKE_DOMAIN_OPTIONS
|
|
|
|
|
],
|
|
|
|
|
"parameter_types": [
|
|
|
|
|
{
|
|
|
|
|
@ -352,6 +263,7 @@ class ToolManagementService:
|
|
|
|
|
],
|
|
|
|
|
"submission_notes": [
|
|
|
|
|
"O colaborador pode preencher, validar e persistir o draft da tool no painel.",
|
|
|
|
|
submitter_note,
|
|
|
|
|
"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.",
|
|
|
|
|
],
|
|
|
|
|
@ -387,36 +299,241 @@ class ToolManagementService:
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def build_review_queue_payload(self) -> dict:
|
|
|
|
|
queued_versions = self._list_latest_versions(statuses=_REVIEW_QUEUE_STATUSES)
|
|
|
|
|
message = (
|
|
|
|
|
"Nenhuma versao aguardando revisao, aprovacao ou publicacao de diretor."
|
|
|
|
|
if not queued_versions
|
|
|
|
|
else f"{len(queued_versions)} versao(oes) aguardando atuacao de diretor antes da ativacao."
|
|
|
|
|
)
|
|
|
|
|
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,
|
|
|
|
|
],
|
|
|
|
|
"queue_mode": "governed_admin_queue",
|
|
|
|
|
"message": message,
|
|
|
|
|
"items": [self._serialize_review_queue_entry(version) for version in queued_versions],
|
|
|
|
|
"supported_statuses": list(_REVIEW_QUEUE_STATUSES),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def build_publications_payload(self) -> dict:
|
|
|
|
|
metadata_entries = self._list_latest_metadata_entries()
|
|
|
|
|
if metadata_entries:
|
|
|
|
|
publications_by_tool_name = {
|
|
|
|
|
publication["tool_name"]: publication
|
|
|
|
|
for publication in self.list_publication_catalog()
|
|
|
|
|
}
|
|
|
|
|
published_metadata_entries = self._list_latest_metadata_entries(
|
|
|
|
|
statuses=_PUBLISHED_TOOL_STATUSES,
|
|
|
|
|
)
|
|
|
|
|
if published_metadata_entries:
|
|
|
|
|
for metadata in published_metadata_entries:
|
|
|
|
|
publications_by_tool_name[metadata.tool_name] = self._serialize_metadata_publication(
|
|
|
|
|
metadata
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
"source": "admin_metadata_catalog",
|
|
|
|
|
"source": "hybrid_runtime_catalog",
|
|
|
|
|
"target_service": ServiceName.PRODUCT,
|
|
|
|
|
"publications": [
|
|
|
|
|
self._serialize_metadata_publication(metadata)
|
|
|
|
|
for metadata in metadata_entries
|
|
|
|
|
],
|
|
|
|
|
"publications": list(publications_by_tool_name.values()),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"source": "bootstrap_catalog",
|
|
|
|
|
"target_service": ServiceName.PRODUCT,
|
|
|
|
|
"publications": self.list_publication_catalog(),
|
|
|
|
|
"publications": list(publications_by_tool_name.values()),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def review_version(
|
|
|
|
|
self,
|
|
|
|
|
version_id: str,
|
|
|
|
|
*,
|
|
|
|
|
reviewer_staff_account_id: int,
|
|
|
|
|
reviewer_name: str,
|
|
|
|
|
reviewer_role: StaffRole | str,
|
|
|
|
|
) -> dict:
|
|
|
|
|
return self._transition_version_status(
|
|
|
|
|
version_id,
|
|
|
|
|
target_status=ToolLifecycleStatus.VALIDATED,
|
|
|
|
|
allowed_current_statuses=(
|
|
|
|
|
ToolLifecycleStatus.DRAFT,
|
|
|
|
|
ToolLifecycleStatus.GENERATED,
|
|
|
|
|
),
|
|
|
|
|
actor_staff_account_id=reviewer_staff_account_id,
|
|
|
|
|
actor_name=reviewer_name,
|
|
|
|
|
actor_role=reviewer_role,
|
|
|
|
|
required_permission=AdminPermission.REVIEW_TOOL_GENERATIONS,
|
|
|
|
|
artifact_kind=ToolArtifactKind.DIRECTOR_REVIEW,
|
|
|
|
|
artifact_summary="Revisao inicial de diretor registrada para a versao governada.",
|
|
|
|
|
success_message="Versao revisada por diretor com sucesso e pronta para aprovacao.",
|
|
|
|
|
next_steps=[
|
|
|
|
|
"A diretoria ainda precisa aprovar formalmente a versao antes da publicacao.",
|
|
|
|
|
"Depois da aprovacao, a publicacao ativa a tool no catalogo governado do produto.",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def approve_version(
|
|
|
|
|
self,
|
|
|
|
|
version_id: str,
|
|
|
|
|
*,
|
|
|
|
|
approver_staff_account_id: int,
|
|
|
|
|
approver_name: str,
|
|
|
|
|
approver_role: StaffRole | str,
|
|
|
|
|
) -> dict:
|
|
|
|
|
return self._transition_version_status(
|
|
|
|
|
version_id,
|
|
|
|
|
target_status=ToolLifecycleStatus.APPROVED,
|
|
|
|
|
allowed_current_statuses=(ToolLifecycleStatus.VALIDATED,),
|
|
|
|
|
actor_staff_account_id=approver_staff_account_id,
|
|
|
|
|
actor_name=approver_name,
|
|
|
|
|
actor_role=approver_role,
|
|
|
|
|
required_permission=AdminPermission.REVIEW_TOOL_GENERATIONS,
|
|
|
|
|
artifact_kind=ToolArtifactKind.DIRECTOR_APPROVAL,
|
|
|
|
|
artifact_summary="Aprovacao de diretor registrada para a versao governada.",
|
|
|
|
|
success_message="Versao aprovada por diretor com sucesso e pronta para publicacao.",
|
|
|
|
|
next_steps=[
|
|
|
|
|
"A publicacao administrativa ainda precisa ser executada antes da ativacao.",
|
|
|
|
|
"Enquanto a versao estiver apenas aprovada, ela permanece fora do catalogo ativo do produto.",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def publish_version(
|
|
|
|
|
self,
|
|
|
|
|
version_id: str,
|
|
|
|
|
*,
|
|
|
|
|
publisher_staff_account_id: int,
|
|
|
|
|
publisher_name: str,
|
|
|
|
|
publisher_role: StaffRole | str,
|
|
|
|
|
) -> dict:
|
|
|
|
|
return self._transition_version_status(
|
|
|
|
|
version_id,
|
|
|
|
|
target_status=ToolLifecycleStatus.ACTIVE,
|
|
|
|
|
allowed_current_statuses=(ToolLifecycleStatus.APPROVED,),
|
|
|
|
|
actor_staff_account_id=publisher_staff_account_id,
|
|
|
|
|
actor_name=publisher_name,
|
|
|
|
|
actor_role=publisher_role,
|
|
|
|
|
required_permission=AdminPermission.PUBLISH_TOOLS,
|
|
|
|
|
artifact_kind=ToolArtifactKind.PUBLICATION_RELEASE,
|
|
|
|
|
artifact_summary="Publicacao administrativa concluida pela diretoria antes da ativacao.",
|
|
|
|
|
success_message="Versao publicada com sucesso e ativada no catalogo governado.",
|
|
|
|
|
next_steps=[
|
|
|
|
|
"A versao ativa agora pode ser consumida pelo runtime governado do produto.",
|
|
|
|
|
"Se uma nova versao for publicada para a mesma tool, a ativa anterior sera arquivada automaticamente.",
|
|
|
|
|
],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _transition_version_status(
|
|
|
|
|
self,
|
|
|
|
|
version_id: str,
|
|
|
|
|
*,
|
|
|
|
|
target_status: ToolLifecycleStatus,
|
|
|
|
|
allowed_current_statuses: tuple[ToolLifecycleStatus, ...],
|
|
|
|
|
actor_staff_account_id: int,
|
|
|
|
|
actor_name: str,
|
|
|
|
|
actor_role: StaffRole | str,
|
|
|
|
|
required_permission: AdminPermission,
|
|
|
|
|
artifact_kind: ToolArtifactKind,
|
|
|
|
|
artifact_summary: str,
|
|
|
|
|
success_message: str,
|
|
|
|
|
next_steps: list[str],
|
|
|
|
|
) -> dict:
|
|
|
|
|
normalized_role = normalize_staff_role(actor_role)
|
|
|
|
|
if not role_has_permission(normalized_role, required_permission):
|
|
|
|
|
raise PermissionError(
|
|
|
|
|
f"Papel '{normalized_role.value}' sem permissao administrativa '{required_permission.value}'."
|
|
|
|
|
)
|
|
|
|
|
if (
|
|
|
|
|
self.draft_repository is None
|
|
|
|
|
or self.version_repository is None
|
|
|
|
|
or self.metadata_repository is None
|
|
|
|
|
):
|
|
|
|
|
raise RuntimeError(
|
|
|
|
|
"Fluxo de governanca de tools ainda nao esta completamente conectado ao armazenamento administrativo."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
normalized_version_id = str(version_id or "").strip().lower()
|
|
|
|
|
version = self.version_repository.get_by_version_id(normalized_version_id)
|
|
|
|
|
if version is None:
|
|
|
|
|
raise LookupError("Versao administrativa nao encontrada.")
|
|
|
|
|
|
|
|
|
|
latest_versions_for_tool = self.version_repository.list_versions(tool_name=version.tool_name)
|
|
|
|
|
if latest_versions_for_tool and latest_versions_for_tool[0].version_id != version.version_id:
|
|
|
|
|
raise ValueError(
|
|
|
|
|
"Somente a versao mais recente da tool pode seguir para revisao, aprovacao e publicacao."
|
|
|
|
|
)
|
|
|
|
|
if version.status not in allowed_current_statuses:
|
|
|
|
|
expected_statuses = ", ".join(status.value for status in allowed_current_statuses)
|
|
|
|
|
raise ValueError(
|
|
|
|
|
f"A transicao solicitada exige status em ({expected_statuses}), mas a versao esta em '{version.status.value}'."
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
draft = self.draft_repository.get_by_tool_name(version.tool_name)
|
|
|
|
|
if draft is None:
|
|
|
|
|
raise RuntimeError("Draft raiz da tool nao encontrado para a versao governada.")
|
|
|
|
|
metadata = self.metadata_repository.get_by_tool_version_id(version.id)
|
|
|
|
|
if metadata is None:
|
|
|
|
|
raise RuntimeError("Metadados persistidos da versao nao encontrados para a governanca administrativa.")
|
|
|
|
|
|
|
|
|
|
previous_status = version.status
|
|
|
|
|
repository_session = self._resolve_repository_session()
|
|
|
|
|
atomic_write_options = {"commit": False} if repository_session is not None else {}
|
|
|
|
|
artifact_commit = False if repository_session is not None else None
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if target_status == ToolLifecycleStatus.ACTIVE:
|
|
|
|
|
self._archive_active_publications(
|
|
|
|
|
tool_name=version.tool_name,
|
|
|
|
|
excluding_version_id=version.id,
|
|
|
|
|
**atomic_write_options,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.version_repository.update_status(
|
|
|
|
|
version,
|
|
|
|
|
status=target_status,
|
|
|
|
|
**atomic_write_options,
|
|
|
|
|
)
|
|
|
|
|
self.metadata_repository.update_status(
|
|
|
|
|
metadata,
|
|
|
|
|
status=target_status,
|
|
|
|
|
**atomic_write_options,
|
|
|
|
|
)
|
|
|
|
|
self.draft_repository.update_status(
|
|
|
|
|
draft,
|
|
|
|
|
status=target_status,
|
|
|
|
|
**atomic_write_options,
|
|
|
|
|
)
|
|
|
|
|
self._persist_governance_artifact(
|
|
|
|
|
draft=draft,
|
|
|
|
|
version=version,
|
|
|
|
|
artifact_kind=artifact_kind,
|
|
|
|
|
summary=artifact_summary,
|
|
|
|
|
previous_status=previous_status,
|
|
|
|
|
current_status=target_status,
|
|
|
|
|
actor_staff_account_id=actor_staff_account_id,
|
|
|
|
|
actor_name=actor_name,
|
|
|
|
|
actor_role=normalized_role,
|
|
|
|
|
commit=artifact_commit,
|
|
|
|
|
)
|
|
|
|
|
if repository_session is not None:
|
|
|
|
|
self._commit_repository_session(
|
|
|
|
|
repository_session,
|
|
|
|
|
draft=draft,
|
|
|
|
|
version=version,
|
|
|
|
|
)
|
|
|
|
|
except Exception:
|
|
|
|
|
if repository_session is not None:
|
|
|
|
|
repository_session.rollback()
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
queue_entry = None
|
|
|
|
|
publication = None
|
|
|
|
|
if target_status == ToolLifecycleStatus.ACTIVE:
|
|
|
|
|
publication = self._serialize_metadata_publication(metadata)
|
|
|
|
|
else:
|
|
|
|
|
queue_entry = self._serialize_review_queue_entry(version)
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"message": success_message,
|
|
|
|
|
"version_id": version.version_id,
|
|
|
|
|
"tool_name": version.tool_name,
|
|
|
|
|
"version_number": version.version_number,
|
|
|
|
|
"status": target_status,
|
|
|
|
|
"queue_entry": queue_entry,
|
|
|
|
|
"publication": publication,
|
|
|
|
|
"next_steps": next_steps,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def create_draft_submission(
|
|
|
|
|
@ -425,12 +542,14 @@ class ToolManagementService:
|
|
|
|
|
*,
|
|
|
|
|
owner_staff_account_id: int | None = None,
|
|
|
|
|
owner_name: str | None = None,
|
|
|
|
|
owner_role: StaffRole | 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"])
|
|
|
|
|
submission_policy = self._build_submission_policy(submitter_role=owner_role)
|
|
|
|
|
|
|
|
|
|
if self.draft_repository is None:
|
|
|
|
|
version_number = 1
|
|
|
|
|
@ -438,7 +557,8 @@ class ToolManagementService:
|
|
|
|
|
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.",
|
|
|
|
|
"message": "Pre-cadastro validado no painel sem publicacao direta. A persistencia definitiva entra na fase de governanca de tools.",
|
|
|
|
|
"submission_policy": submission_policy,
|
|
|
|
|
"draft_preview": {
|
|
|
|
|
"draft_id": f"preview::{normalized['tool_name']}",
|
|
|
|
|
"version_id": version_id,
|
|
|
|
|
@ -467,10 +587,16 @@ class ToolManagementService:
|
|
|
|
|
if owner_staff_account_id is None:
|
|
|
|
|
raise ValueError("owner_staff_account_id e obrigatorio para persistir o draft.")
|
|
|
|
|
|
|
|
|
|
repository_session = self._resolve_repository_session()
|
|
|
|
|
atomic_write_options = {"commit": False} if repository_session is not None else {}
|
|
|
|
|
artifact_commit = False if repository_session is not None else None
|
|
|
|
|
owner_display_name = owner_name or "Autor administrativo"
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
if existing_draft is None:
|
|
|
|
|
draft = self.draft_repository.create(
|
|
|
|
|
tool_name=normalized["tool_name"],
|
|
|
|
|
@ -484,8 +610,9 @@ class ToolManagementService:
|
|
|
|
|
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",
|
|
|
|
|
owner_display_name=owner_display_name,
|
|
|
|
|
requires_director_approval=True,
|
|
|
|
|
**atomic_write_options,
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
draft = self.draft_repository.update_submission(
|
|
|
|
|
@ -500,8 +627,9 @@ class ToolManagementService:
|
|
|
|
|
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",
|
|
|
|
|
owner_display_name=owner_display_name,
|
|
|
|
|
requires_director_approval=True,
|
|
|
|
|
**atomic_write_options,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
version = None
|
|
|
|
|
@ -516,9 +644,10 @@ class ToolManagementService:
|
|
|
|
|
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",
|
|
|
|
|
owner_display_name=owner_display_name,
|
|
|
|
|
status=ToolLifecycleStatus.DRAFT,
|
|
|
|
|
requires_director_approval=True,
|
|
|
|
|
**atomic_write_options,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if version is not None and self.metadata_repository is not None:
|
|
|
|
|
@ -534,6 +663,7 @@ class ToolManagementService:
|
|
|
|
|
status=version.status,
|
|
|
|
|
author_staff_account_id=version.owner_staff_account_id,
|
|
|
|
|
author_display_name=version.owner_display_name,
|
|
|
|
|
**atomic_write_options,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if version is not None and self.artifact_repository is not None:
|
|
|
|
|
@ -545,12 +675,25 @@ class ToolManagementService:
|
|
|
|
|
stored_parameters=stored_parameters,
|
|
|
|
|
required_parameter_count=required_parameter_count,
|
|
|
|
|
owner_staff_account_id=owner_staff_account_id,
|
|
|
|
|
owner_name=owner_name or "Autor administrativo",
|
|
|
|
|
owner_name=owner_display_name,
|
|
|
|
|
commit=artifact_commit,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if repository_session is not None:
|
|
|
|
|
self._commit_repository_session(
|
|
|
|
|
repository_session,
|
|
|
|
|
draft=draft,
|
|
|
|
|
version=version,
|
|
|
|
|
)
|
|
|
|
|
except Exception:
|
|
|
|
|
if repository_session is not None:
|
|
|
|
|
repository_session.rollback()
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
"storage_status": "admin_database",
|
|
|
|
|
"message": "Draft administrativo persistido com sucesso em fluxo versionado.",
|
|
|
|
|
"message": "Draft administrativo persistido com sucesso sem publicacao direta, em fluxo versionado e governado.",
|
|
|
|
|
"submission_policy": submission_policy,
|
|
|
|
|
"draft_preview": self._serialize_draft_preview(draft, version),
|
|
|
|
|
"warnings": warnings,
|
|
|
|
|
"next_steps": [
|
|
|
|
|
@ -560,11 +703,18 @@ class ToolManagementService:
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def preview_draft_submission(self, payload: dict, *, owner_name: str | None = None) -> dict:
|
|
|
|
|
def preview_draft_submission(
|
|
|
|
|
self,
|
|
|
|
|
payload: dict,
|
|
|
|
|
*,
|
|
|
|
|
owner_name: str | None = None,
|
|
|
|
|
owner_role: StaffRole | 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)
|
|
|
|
|
submission_policy = self._build_submission_policy(submitter_role=owner_role)
|
|
|
|
|
existing_draft = None
|
|
|
|
|
if self.draft_repository is not None:
|
|
|
|
|
existing_draft = self.draft_repository.get_by_tool_name(normalized["tool_name"])
|
|
|
|
|
@ -572,7 +722,8 @@ class ToolManagementService:
|
|
|
|
|
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.",
|
|
|
|
|
"message": "Pre-cadastro validado no painel com numeracao de versao reservada para a tool, sem publicacao direta nesta etapa.",
|
|
|
|
|
"submission_policy": submission_policy,
|
|
|
|
|
"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),
|
|
|
|
|
@ -627,9 +778,106 @@ class ToolManagementService:
|
|
|
|
|
"published_by": "bootstrap_catalog",
|
|
|
|
|
"published_at": published_at,
|
|
|
|
|
}
|
|
|
|
|
for entry in _BOOTSTRAP_TOOL_CATALOG
|
|
|
|
|
for entry in BOOTSTRAP_TOOL_CATALOG
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
def _archive_active_publications(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
tool_name: str,
|
|
|
|
|
excluding_version_id: int,
|
|
|
|
|
commit: bool = True,
|
|
|
|
|
) -> None:
|
|
|
|
|
if self.version_repository is not None:
|
|
|
|
|
for active_version in self.version_repository.list_versions(
|
|
|
|
|
tool_name=tool_name,
|
|
|
|
|
statuses=(ToolLifecycleStatus.ACTIVE,),
|
|
|
|
|
):
|
|
|
|
|
if active_version.id == excluding_version_id:
|
|
|
|
|
continue
|
|
|
|
|
self.version_repository.update_status(
|
|
|
|
|
active_version,
|
|
|
|
|
status=ToolLifecycleStatus.ARCHIVED,
|
|
|
|
|
commit=commit,
|
|
|
|
|
)
|
|
|
|
|
if self.metadata_repository is not None:
|
|
|
|
|
for active_metadata in self.metadata_repository.list_metadata(
|
|
|
|
|
tool_name=tool_name,
|
|
|
|
|
statuses=(ToolLifecycleStatus.ACTIVE,),
|
|
|
|
|
):
|
|
|
|
|
if active_metadata.tool_version_id == excluding_version_id:
|
|
|
|
|
continue
|
|
|
|
|
self.metadata_repository.update_status(
|
|
|
|
|
active_metadata,
|
|
|
|
|
status=ToolLifecycleStatus.ARCHIVED,
|
|
|
|
|
commit=commit,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def _persist_governance_artifact(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
draft: ToolDraft,
|
|
|
|
|
version: ToolVersion,
|
|
|
|
|
artifact_kind: ToolArtifactKind,
|
|
|
|
|
summary: str,
|
|
|
|
|
previous_status: ToolLifecycleStatus,
|
|
|
|
|
current_status: ToolLifecycleStatus,
|
|
|
|
|
actor_staff_account_id: int,
|
|
|
|
|
actor_name: str,
|
|
|
|
|
actor_role: StaffRole,
|
|
|
|
|
commit: bool | None = None,
|
|
|
|
|
) -> None:
|
|
|
|
|
if self.artifact_repository is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
artifact_write_options = {"commit": commit} if commit is not None else {}
|
|
|
|
|
self.artifact_repository.upsert_version_artifact(
|
|
|
|
|
draft_id=draft.id,
|
|
|
|
|
tool_version_id=version.id,
|
|
|
|
|
tool_name=version.tool_name,
|
|
|
|
|
version_number=version.version_number,
|
|
|
|
|
artifact_stage=ToolArtifactStage.GOVERNANCE,
|
|
|
|
|
artifact_kind=artifact_kind,
|
|
|
|
|
artifact_status=ToolArtifactStatus.SUCCEEDED,
|
|
|
|
|
summary=summary,
|
|
|
|
|
payload_json=self._build_governance_artifact_payload(
|
|
|
|
|
version=version,
|
|
|
|
|
artifact_kind=artifact_kind,
|
|
|
|
|
previous_status=previous_status,
|
|
|
|
|
current_status=current_status,
|
|
|
|
|
actor_staff_account_id=actor_staff_account_id,
|
|
|
|
|
actor_name=actor_name,
|
|
|
|
|
actor_role=actor_role,
|
|
|
|
|
),
|
|
|
|
|
author_staff_account_id=actor_staff_account_id,
|
|
|
|
|
author_display_name=actor_name,
|
|
|
|
|
**artifact_write_options,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _build_governance_artifact_payload(
|
|
|
|
|
*,
|
|
|
|
|
version: ToolVersion,
|
|
|
|
|
artifact_kind: ToolArtifactKind,
|
|
|
|
|
previous_status: ToolLifecycleStatus,
|
|
|
|
|
current_status: ToolLifecycleStatus,
|
|
|
|
|
actor_staff_account_id: int,
|
|
|
|
|
actor_name: str,
|
|
|
|
|
actor_role: StaffRole,
|
|
|
|
|
) -> dict:
|
|
|
|
|
return {
|
|
|
|
|
"source": "director_governance",
|
|
|
|
|
"action": artifact_kind.value,
|
|
|
|
|
"tool_name": version.tool_name,
|
|
|
|
|
"version_id": version.version_id,
|
|
|
|
|
"version_number": version.version_number,
|
|
|
|
|
"previous_status": previous_status.value,
|
|
|
|
|
"current_status": current_status.value,
|
|
|
|
|
"actor_staff_account_id": actor_staff_account_id,
|
|
|
|
|
"actor_display_name": actor_name,
|
|
|
|
|
"actor_role": actor_role.value,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _persist_initial_version_artifacts(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
@ -641,10 +889,13 @@ class ToolManagementService:
|
|
|
|
|
required_parameter_count: int,
|
|
|
|
|
owner_staff_account_id: int,
|
|
|
|
|
owner_name: str,
|
|
|
|
|
commit: bool | None = None,
|
|
|
|
|
) -> None:
|
|
|
|
|
if self.artifact_repository is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
artifact_write_options = {"commit": commit} if commit is not None else {}
|
|
|
|
|
|
|
|
|
|
generation_payload = self._build_generation_artifact_payload(
|
|
|
|
|
draft=draft,
|
|
|
|
|
version=version,
|
|
|
|
|
@ -671,6 +922,7 @@ class ToolManagementService:
|
|
|
|
|
payload_json=generation_payload,
|
|
|
|
|
author_staff_account_id=owner_staff_account_id,
|
|
|
|
|
author_display_name=owner_name,
|
|
|
|
|
**artifact_write_options,
|
|
|
|
|
)
|
|
|
|
|
self.artifact_repository.upsert_version_artifact(
|
|
|
|
|
draft_id=draft.id,
|
|
|
|
|
@ -684,6 +936,7 @@ class ToolManagementService:
|
|
|
|
|
payload_json=validation_payload,
|
|
|
|
|
author_staff_account_id=owner_staff_account_id,
|
|
|
|
|
author_display_name=owner_name,
|
|
|
|
|
**artifact_write_options,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
@ -743,12 +996,66 @@ class ToolManagementService:
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _list_latest_metadata_entries(self) -> list[ToolMetadata]:
|
|
|
|
|
def _list_latest_versions(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
statuses: tuple[ToolLifecycleStatus, ...] | None = None,
|
|
|
|
|
) -> list[ToolVersion]:
|
|
|
|
|
if self.version_repository is None:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
latest_by_tool_name: dict[str, ToolVersion | None] = {}
|
|
|
|
|
for version in self.version_repository.list_versions():
|
|
|
|
|
normalized_tool_name = str(version.tool_name or "").strip().lower()
|
|
|
|
|
if normalized_tool_name in latest_by_tool_name:
|
|
|
|
|
continue
|
|
|
|
|
if statuses is not None and version.status not in statuses:
|
|
|
|
|
latest_by_tool_name[normalized_tool_name] = None
|
|
|
|
|
continue
|
|
|
|
|
latest_by_tool_name[normalized_tool_name] = version
|
|
|
|
|
return [version for version in latest_by_tool_name.values() if version is not None]
|
|
|
|
|
|
|
|
|
|
def _serialize_review_queue_entry(self, version: ToolVersion) -> dict:
|
|
|
|
|
metadata = (
|
|
|
|
|
self.metadata_repository.get_by_tool_version_id(version.id)
|
|
|
|
|
if self.metadata_repository is not None
|
|
|
|
|
else None
|
|
|
|
|
)
|
|
|
|
|
display_name = metadata.display_name if metadata is not None else version.tool_name.replace("_", " ").title()
|
|
|
|
|
return {
|
|
|
|
|
"entry_id": version.version_id,
|
|
|
|
|
"version_id": version.version_id,
|
|
|
|
|
"version_number": version.version_number,
|
|
|
|
|
"tool_name": version.tool_name,
|
|
|
|
|
"display_name": display_name,
|
|
|
|
|
"status": version.status,
|
|
|
|
|
"gate": self._build_review_gate(version.status),
|
|
|
|
|
"summary": version.summary,
|
|
|
|
|
"owner_name": version.owner_display_name,
|
|
|
|
|
"queued_at": version.updated_at or version.created_at,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
def _build_review_gate(status: ToolLifecycleStatus) -> str:
|
|
|
|
|
gate_by_status = {
|
|
|
|
|
ToolLifecycleStatus.DRAFT: "director_review_required",
|
|
|
|
|
ToolLifecycleStatus.GENERATED: "validation_confirmation_required",
|
|
|
|
|
ToolLifecycleStatus.VALIDATED: "director_approval_required",
|
|
|
|
|
ToolLifecycleStatus.APPROVED: "director_publication_required",
|
|
|
|
|
ToolLifecycleStatus.FAILED: "revision_required",
|
|
|
|
|
}
|
|
|
|
|
return gate_by_status.get(status, "governance_required")
|
|
|
|
|
|
|
|
|
|
def _list_latest_metadata_entries(
|
|
|
|
|
self,
|
|
|
|
|
*,
|
|
|
|
|
statuses: tuple[ToolLifecycleStatus, ...] | None = None,
|
|
|
|
|
) -> list[ToolMetadata]:
|
|
|
|
|
if self.metadata_repository is None:
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
latest_by_tool_name: dict[str, ToolMetadata] = {}
|
|
|
|
|
for metadata in self.metadata_repository.list_metadata():
|
|
|
|
|
for metadata in self.metadata_repository.list_metadata(statuses=statuses):
|
|
|
|
|
normalized_tool_name = str(metadata.tool_name or "").strip().lower()
|
|
|
|
|
if normalized_tool_name in latest_by_tool_name:
|
|
|
|
|
continue
|
|
|
|
|
@ -878,7 +1185,7 @@ class ToolManagementService:
|
|
|
|
|
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}
|
|
|
|
|
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.")
|
|
|
|
|
|
|
|
|
|
|