Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
# ============================================================
|
||||
# Omika - Completion des packs de langue deja exportes
|
||||
# ============================================================
|
||||
# Les packs exportes avant l'introduction du vocabulaire de modele contiennent
|
||||
# quatre fichiers : encoder_model.onnx, decoder_model.onnx, source.spm,
|
||||
# target.spm. Il en manque deux, sans lesquels le moteur ne peut pas traduire
|
||||
# correctement :
|
||||
#
|
||||
# vocab.json - les identifiants REELLEMENT attendus par le modele. Le .spm
|
||||
# numerote ses pieces dans un ordre qui lui est propre : sur
|
||||
# opus-mt-es-fr, la piece <U+2581>Ho vaut 5293 pour le .spm mais
|
||||
# 584 pour le modele. Envoyer les identifiants du .spm revient a
|
||||
# faire lire un autre texte, sans la moindre erreur levee.
|
||||
# config.json - l'amorce et la fin du decodeur (74821 et 0 sur opus-mt-es-fr),
|
||||
# que le .spm ne contient pas non plus.
|
||||
#
|
||||
# Ces deux fichiers pesent quelques mega-octets : rien n'est reexporte, les poids
|
||||
# deja sur le disque (23 Gio) restent valables.
|
||||
#
|
||||
# GARDE-FOU : le modele d'origine n'est pas note dans le pack. Il est deduit du
|
||||
# code de langue et du sens (es + versFrancais -> opus-mt-es-fr), puis VERIFIE :
|
||||
# le source.spm telecharge doit etre identique, octet pour octet, a celui du
|
||||
# pack. Sans cette comparaison, on attacherait le vocabulaire d'un autre modele
|
||||
# - exactement le defaut que ce script repare, en pire.
|
||||
#
|
||||
# Usage :
|
||||
# .\scripts\Completer-Packs-Langues.ps1 -Verifier
|
||||
# .\scripts\Completer-Packs-Langues.ps1
|
||||
# .\scripts\Completer-Packs-Langues.ps1 -Codes "es,pt,it"
|
||||
# ============================================================
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
# Racine des packs. Par defaut : <racine du depot>\packs
|
||||
[string]$RacinePacks = '',
|
||||
|
||||
# N'ecrit rien : dit seulement quels packs sont deja alignes ou non.
|
||||
[switch]$Verifier,
|
||||
|
||||
# Codes de langue a traiter (ex. "es,pt"). Vide = tous les dossiers trouves.
|
||||
[string]$Codes = ''
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# --- Sortie ----------------------------------------------------------------
|
||||
function Write-Etape { param([string]$Message) Write-Host ''; Write-Host $Message -ForegroundColor Cyan }
|
||||
function Write-Info { param([string]$Message) Write-Host (' ' + $Message) }
|
||||
function Write-Ok { param([string]$Message) Write-Host (' [OK] ' + $Message) -ForegroundColor Green }
|
||||
function Write-Alerte{ param([string]$Message) Write-Host (' [ALERTE] ' + $Message) -ForegroundColor Yellow }
|
||||
function Write-Ecart { param([string]$Message) Write-Host (' [ECART] ' + $Message) -ForegroundColor DarkYellow }
|
||||
|
||||
function Stop-AvecMessage {
|
||||
param([string]$Message)
|
||||
Write-Host ''
|
||||
Write-Host '=== ARRET ===' -ForegroundColor Red
|
||||
Write-Host $Message -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Logique ---------------------------------------------------------------
|
||||
|
||||
# Nom du depot HuggingFace correspondant a un pack. Convention de l'exporteur :
|
||||
# versFrancais = <langue> -> francais, depuisFrancais = francais -> <langue>.
|
||||
function Get-NomModele {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Code,
|
||||
[Parameter(Mandatory = $true)][string]$Sens,
|
||||
[string]$Auteur = 'Helsinki-NLP'
|
||||
)
|
||||
|
||||
$codeNormalise = $Code.Trim().ToLowerInvariant()
|
||||
if ($Sens -eq 'versFrancais') {
|
||||
return ('{0}/opus-mt-{1}-fr' -f $Auteur, $codeNormalise)
|
||||
}
|
||||
|
||||
return ('{0}/opus-mt-fr-{1}' -f $Auteur, $codeNormalise)
|
||||
}
|
||||
|
||||
# Empreinte SHA-256 en minuscules, comparable telle quelle.
|
||||
function Get-Empreinte {
|
||||
param([Parameter(Mandatory = $true)][string]$Chemin)
|
||||
return (Get-FileHash -LiteralPath $Chemin -Algorithm SHA256).Hash.ToLowerInvariant()
|
||||
}
|
||||
|
||||
# Inventaire des couples (code, sens) presents sur le disque.
|
||||
function Get-PacksPresents {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Racine,
|
||||
[string[]]$CodesFiltres = @()
|
||||
)
|
||||
|
||||
$presents = New-Object 'System.Collections.Generic.List[object]'
|
||||
if (-not (Test-Path -LiteralPath $Racine -PathType Container)) {
|
||||
return $presents
|
||||
}
|
||||
|
||||
foreach ($dossierLangue in @(Get-ChildItem -LiteralPath $Racine -Directory | Sort-Object -Property Name)) {
|
||||
$code = $dossierLangue.Name
|
||||
# Dossiers techniques (.hf-cache) et fichiers d'essai : jamais des langues.
|
||||
if ($code.StartsWith('.')) { continue }
|
||||
if ($CodesFiltres.Count -gt 0 -and $CodesFiltres -notcontains $code) { continue }
|
||||
|
||||
foreach ($dossierSens in @(Get-ChildItem -LiteralPath $dossierLangue.FullName -Directory | Sort-Object -Property Name)) {
|
||||
$sens = $dossierSens.Name
|
||||
if ($sens -ne 'versFrancais' -and $sens -ne 'depuisFrancais') { continue }
|
||||
|
||||
# Un pack FAMILIAL (modele multilingue) range ses poids dans
|
||||
# <code>\commun\ et ne laisse qu'un modele.json par sens : il n'a
|
||||
# aucun source.spm par sens, donc RIEN a aligner. Sans ce garde-fou
|
||||
# on lui cherchait une paire bilingue qui n'existe pas et on le
|
||||
# declarait « ecarte » a tort.
|
||||
$spm = Join-Path $dossierSens.FullName 'source.spm'
|
||||
if (-not (Test-Path -LiteralPath $spm -PathType Leaf)) { continue }
|
||||
|
||||
$presents.Add([pscustomobject]@{
|
||||
Code = $code
|
||||
Sens = $sens
|
||||
Dossier = $dossierSens.FullName
|
||||
Source = $spm
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return $presents
|
||||
}
|
||||
|
||||
# Complete un pack. $Recuperer recoit (modele, fichier, destination) et doit
|
||||
# renvoyer $true si le fichier a ete obtenu. Il est injecte pour que les tests
|
||||
# s'executent sans reseau.
|
||||
function Invoke-Completion {
|
||||
param(
|
||||
[Parameter(Mandatory = $true)][string]$Racine,
|
||||
[Parameter(Mandatory = $true)][scriptblock]$Recuperer,
|
||||
[switch]$SansEcrire,
|
||||
[string[]]$CodesFiltres = @()
|
||||
)
|
||||
|
||||
$bilan = [pscustomobject]@{
|
||||
Packs = 0
|
||||
Alignes = 0
|
||||
Ecartes = 0
|
||||
Echecs = 0
|
||||
Ecrits = 0
|
||||
}
|
||||
|
||||
$packs = @(Get-PacksPresents -Racine $Racine -CodesFiltres $CodesFiltres)
|
||||
if ($packs.Count -eq 0) {
|
||||
Write-Alerte ("aucun pack trouve sous {0}" -f $Racine)
|
||||
return $bilan
|
||||
}
|
||||
|
||||
$bac = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-completion-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
$null = New-Item -ItemType Directory -Path $bac -Force
|
||||
|
||||
try {
|
||||
foreach ($pack in $packs) {
|
||||
$bilan.Packs++
|
||||
$modele = Get-NomModele -Code $pack.Code -Sens $pack.Sens
|
||||
$nomCourt = '{0}/{1}' -f $pack.Code, $pack.Sens
|
||||
|
||||
$spmTelecharge = Join-Path $bac ('{0}-{1}-source.spm' -f $pack.Code, $pack.Sens)
|
||||
$vocabTelecharge = Join-Path $bac ('{0}-{1}-vocab.json' -f $pack.Code, $pack.Sens)
|
||||
$configTelecharge = Join-Path $bac ('{0}-{1}-config.json' -f $pack.Code, $pack.Sens)
|
||||
|
||||
$obtenus = $true
|
||||
foreach ($paire in @(
|
||||
@{ Fichier = 'source.spm'; Destination = $spmTelecharge },
|
||||
@{ Fichier = 'vocab.json'; Destination = $vocabTelecharge },
|
||||
@{ Fichier = 'config.json'; Destination = $configTelecharge })) {
|
||||
if (-not (& $Recuperer $modele $paire.Fichier $paire.Destination)) {
|
||||
$obtenus = $false
|
||||
Write-Alerte ("{0} : {1} introuvable pour {2}" -f $nomCourt, $paire.Fichier, $modele)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $obtenus) {
|
||||
$bilan.Echecs++
|
||||
continue
|
||||
}
|
||||
|
||||
# LE garde-fou : le modele deduit doit etre celui du pack.
|
||||
$empreinteLocale = Get-Empreinte -Chemin $pack.Source
|
||||
$empreinteDistante = Get-Empreinte -Chemin $spmTelecharge
|
||||
if ($empreinteLocale -ne $empreinteDistante) {
|
||||
Write-Ecart ("{0} : {1} n'est PAS le modele de ce pack (source.spm different) - non complete" -f $nomCourt, $modele)
|
||||
$bilan.Ecartes++
|
||||
continue
|
||||
}
|
||||
|
||||
$bilan.Alignes++
|
||||
if ($SansEcrire) {
|
||||
Write-Ok ("{0} : aligne sur {1}" -f $nomCourt, $modele)
|
||||
continue
|
||||
}
|
||||
|
||||
Copy-Item -LiteralPath $vocabTelecharge -Destination (Join-Path $pack.Dossier 'vocab.json') -Force
|
||||
Copy-Item -LiteralPath $configTelecharge -Destination (Join-Path $pack.Dossier 'config.json') -Force
|
||||
$bilan.Ecrits++
|
||||
Write-Ok ("{0} : vocabulaire et configuration ajoutes ({1})" -f $nomCourt, $modele)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $bac -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
return $bilan
|
||||
}
|
||||
|
||||
# --- Execution -------------------------------------------------------------
|
||||
# Les tests dot-sourcent ce fichier pour appeler les fonctions : dans ce cas,
|
||||
# rien ne s'execute. Seul un lancement direct declenche le traitement.
|
||||
if ($MyInvocation.InvocationName -ne '.') {
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($RacinePacks)) {
|
||||
$RacinePacks = Join-Path (Split-Path -Parent $PSScriptRoot) 'packs'
|
||||
}
|
||||
|
||||
$codesFiltres = @()
|
||||
if (-not [string]::IsNullOrWhiteSpace($Codes)) {
|
||||
$codesFiltres = @($Codes -split '[,;]' | ForEach-Object { $_.Trim().ToLowerInvariant() } | Where-Object { $_ })
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host '=== Omika - Completion des packs de langue ===' -ForegroundColor Cyan
|
||||
Write-Info ('Racine des packs : {0}' -f $RacinePacks)
|
||||
Write-Info ('Mode : {0}' -f $(if ($Verifier) { 'verification seule (aucune ecriture)' } else { 'ecriture' }))
|
||||
if ($codesFiltres.Count -gt 0) { Write-Info ('Langues : {0}' -f ($codesFiltres -join ', ')) }
|
||||
|
||||
# Recuperation reelle : HuggingFace, sans jeton (les depots sont publics).
|
||||
$recuperer = {
|
||||
param($modele, $fichier, $destination)
|
||||
$uri = 'https://huggingface.co/{0}/resolve/main/{1}' -f $modele, $fichier
|
||||
try {
|
||||
Invoke-WebRequest -Uri $uri -OutFile $destination -UseBasicParsing -ErrorAction Stop
|
||||
return (Test-Path -LiteralPath $destination -PathType Leaf)
|
||||
}
|
||||
catch {
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
$bilan = Invoke-Completion -Racine $RacinePacks -Recuperer $recuperer -SansEcrire:$Verifier -CodesFiltres $codesFiltres
|
||||
|
||||
Write-Etape '=== RESULTAT ==='
|
||||
Write-Info ('Packs examines : {0}' -f $bilan.Packs)
|
||||
Write-Info ('Alignes : {0}' -f $bilan.Alignes)
|
||||
Write-Info ('Completes : {0}' -f $bilan.Ecrits)
|
||||
Write-Info ('Ecartes (autre modele) : {0}' -f $bilan.Ecartes)
|
||||
Write-Info ('Echecs : {0}' -f $bilan.Echecs)
|
||||
|
||||
if ($bilan.Echecs -gt 0) {
|
||||
Stop-AvecMessage ("{0} pack(s) n'ont pas pu etre completes (reseau ou depots introuvables).`n" -f $bilan.Echecs +
|
||||
" Relancer la MEME commande : les packs deja completes ne sont pas retraites.")
|
||||
}
|
||||
|
||||
if ($bilan.Ecartes -gt 0) {
|
||||
Write-Alerte 'Les packs ecartes viennent d''une autre famille de modeles que celle deduite du code.'
|
||||
Write-Alerte 'Ils doivent etre reexportes avec scripts\Exporter-Packs-Langues.ps1 (le modele d''origine y sera note).'
|
||||
}
|
||||
|
||||
exit 0
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,472 @@
|
||||
# ============================================================
|
||||
# Omika — Fixtures de référence SentencePiece
|
||||
# ============================================================
|
||||
# Usage (poste de développement, Python 3 + sentencepiece installés) :
|
||||
# .\scripts\Generer-Fixtures-SentencePiece.ps1 -DossierModeles C:\ModelesLangues
|
||||
# .\scripts\Generer-Fixtures-SentencePiece.ps1 -DossierModeles .\artifacts -CopierModeles
|
||||
# .\scripts\Generer-Fixtures-SentencePiece.ps1 -DossierModeles C:\M -Sortie C:\Temp\fixtures
|
||||
#
|
||||
# Pour chaque modèle .spm trouvé RÉCURSIVEMENT sous -DossierModeles, écrit un
|
||||
# fixture JSON (<nom>.fixtures.json) contenant, pour une série de textes
|
||||
# français et espagnols, les identifiants produits par la bibliothèque de
|
||||
# RÉFÉRENCE (sentencepiece) et le texte qu'elle reconstruit à partir de ces
|
||||
# identifiants.
|
||||
#
|
||||
# À quoi ça sert : le tokeniseur de src\Omika.App.Local\Services\
|
||||
# SentencePieceTokenizer.cs est réécrit à la main (lecture protobuf, Viterbi
|
||||
# UNIGRAM, BPE). Son risque n'est pas de lever une exception, c'est de découper
|
||||
# LÉGÈREMENT autrement que le modèle — donc de dégrader les traductions sans
|
||||
# aucun signal. Ces fixtures sont la seule mesure de cette divergence : elles
|
||||
# transforment un soupçon en un test rouge qui nomme la phrase fautive.
|
||||
#
|
||||
# Le script est idempotent : il réécrit ses propres fixtures (fichiers
|
||||
# <nom>.fixtures.json du dossier -Sortie) et ne supprime jamais rien d'autre. Le
|
||||
# programme Python temporaire qu'il crée est supprimé par lui, dans un finally.
|
||||
# ============================================================
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[Parameter(Mandatory = $true)]
|
||||
[string]$DossierModeles,
|
||||
|
||||
# Dossier des fixtures, relatif à la racine du dépôt s'il ne l'est pas déjà.
|
||||
[string]$Sortie = 'tests\fixtures\sentencepiece',
|
||||
|
||||
# Copie chaque .spm à côté de son fixture : le test .NET cherche le modèle
|
||||
# dans le dossier des fixtures, et la copie évite de le faire à la main. Les
|
||||
# .spm restent hors du dépôt (artefacts de modèle lourds et sous licence).
|
||||
[switch]$CopierModeles
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Racine du dépôt = parent du dossier scripts\.
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
function Stop-AvecMessage {
|
||||
param([string]$Message)
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "ERREUR : $Message" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
||||
function Format-CheminRelatif {
|
||||
param([string]$Chemin)
|
||||
|
||||
# Les chemins absolus de la machine de développement n'ont rien à faire dans
|
||||
# une sortie destinée à être recopiée dans un ticket : on les rend relatifs
|
||||
# au dépôt dès que c'est possible.
|
||||
if ($Chemin.StartsWith($RepoRoot, [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
return $Chemin.Substring($RepoRoot.Length).TrimStart('\', '/')
|
||||
}
|
||||
|
||||
return $Chemin
|
||||
}
|
||||
|
||||
function Find-Python {
|
||||
# « python » sur Windows peut être l'alias du Microsoft Store, qui n'exécute
|
||||
# rien : on vérifie donc que la commande RÉPOND « Python 3 », et pas
|
||||
# seulement qu'un fichier de ce nom existe. Le lanceur « py -3 » couvre les
|
||||
# installations qui n'exposent pas « python » dans le PATH.
|
||||
$candidats = @(
|
||||
@{ Nom = 'python'; Arguments = @() },
|
||||
@{ Nom = 'py'; Arguments = @('-3') }
|
||||
)
|
||||
|
||||
foreach ($candidat in $candidats) {
|
||||
$commande = Get-Command $candidat.Nom -ErrorAction SilentlyContinue
|
||||
if (-not $commande) { continue }
|
||||
|
||||
$arguments = @($candidat.Arguments) + @('--version')
|
||||
$sortie = $null
|
||||
$code = -1
|
||||
|
||||
# Windows PowerShell 5.1 transforme la sortie d'ERREUR d'un programme
|
||||
# natif en erreur TERMINANTE dès que $ErrorActionPreference vaut Stop :
|
||||
# l'alias Microsoft Store de « python », qui écrit sur stderr, ferait
|
||||
# donc échouer la détection au lieu d'être simplement écarté. On
|
||||
# assouplit la préférence le temps de la capture.
|
||||
$preferenceErreur = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
try {
|
||||
$sortie = (& $commande.Source @arguments 2>&1 | Out-String)
|
||||
$code = $LASTEXITCODE
|
||||
}
|
||||
catch {
|
||||
continue
|
||||
}
|
||||
finally {
|
||||
$ErrorActionPreference = $preferenceErreur
|
||||
}
|
||||
|
||||
if ($code -eq 0 -and $sortie -match 'Python 3') {
|
||||
return @{ Nom = $candidat.Nom; Chemin = $commande.Source; Arguments = @($candidat.Arguments) }
|
||||
}
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
# --- Dossiers -----------------------------------------------------------------
|
||||
|
||||
if (-not (Test-Path -LiteralPath $DossierModeles -PathType Container)) {
|
||||
Stop-AvecMessage "Le dossier de modèles « $DossierModeles » n'existe pas. Indiquez -DossierModeles <dossier contenant les .spm>."
|
||||
}
|
||||
|
||||
if (-not [System.IO.Path]::IsPathRooted($Sortie)) {
|
||||
$Sortie = Join-Path $RepoRoot $Sortie
|
||||
}
|
||||
|
||||
$modeles = @(Get-ChildItem -LiteralPath $DossierModeles -Filter '*.spm' -Recurse -File | Sort-Object FullName)
|
||||
|
||||
if ($modeles.Count -eq 0) {
|
||||
Stop-AvecMessage "Aucun fichier .spm trouvé sous « $DossierModeles ». Le vocabulaire d'un pack de langue s'appelle vocab.spm (arborescence langues\{code}\{sens}\vocab.spm) : passez le dossier qui le contient."
|
||||
}
|
||||
|
||||
# --- Interpréteur Python et bibliothèque de référence -------------------------
|
||||
# Ces vérifications précèdent la création du dossier de sortie : un script qui
|
||||
# échoue faute de Python ne doit laisser AUCUNE trace derrière lui.
|
||||
|
||||
$python = Find-Python
|
||||
if (-not $python) {
|
||||
Stop-AvecMessage @"
|
||||
Python 3 est introuvable sur ce poste (ni « python », ni « py -3 »).
|
||||
Installez Python 3 depuis https://www.python.org/downloads/ puis la bibliothèque
|
||||
de référence utilisée par ce script :
|
||||
|
||||
pip install sentencepiece
|
||||
|
||||
Aucune fixture n'a été écrite.
|
||||
"@
|
||||
}
|
||||
|
||||
$commandePip = "$($python.Chemin) $($python.Arguments -join ' ') -m pip install sentencepiece".Trim()
|
||||
|
||||
$verification = @($python.Arguments) + @('-c', 'import sentencepiece')
|
||||
|
||||
# Même précaution que dans Find-Python : l'échec d'import écrit sur stderr, et
|
||||
# sous Windows PowerShell 5.1 une erreur native redirigée devient terminante
|
||||
# quand $ErrorActionPreference vaut Stop — on veut ici la LIRE pour la traduire.
|
||||
$preferenceErreur = $ErrorActionPreference
|
||||
$ErrorActionPreference = 'Continue'
|
||||
|
||||
try {
|
||||
$sortieImport = (& $python.Chemin @verification 2>&1 | Out-String).Trim()
|
||||
$codeImport = $LASTEXITCODE
|
||||
}
|
||||
finally {
|
||||
$ErrorActionPreference = $preferenceErreur
|
||||
}
|
||||
|
||||
if ($codeImport -ne 0) {
|
||||
Stop-AvecMessage @"
|
||||
La bibliothèque « sentencepiece » n'est pas installée pour l'interpréteur détecté ($($python.Chemin)).
|
||||
Installez-la :
|
||||
|
||||
$commandePip
|
||||
|
||||
Erreur renvoyée par Python :
|
||||
|
||||
$sortieImport
|
||||
|
||||
Aucune fixture n'a été écrite.
|
||||
"@
|
||||
}
|
||||
|
||||
if (-not (Test-Path -LiteralPath $Sortie)) {
|
||||
New-Item -ItemType Directory -Path $Sortie -Force | Out-Null
|
||||
}
|
||||
|
||||
# --- Programme Python embarqué ------------------------------------------------
|
||||
# Le programme est ÉCRIT DANS UN FICHIER TEMPORAIRE puis supprimé : l'humain n'a
|
||||
# qu'un seul script à connaître, et il ne peut pas désynchroniser un second
|
||||
# fichier oublié dans le dépôt.
|
||||
#
|
||||
# ATTENTION ENCODAGE : le programme contient des textes accentués, arabes et
|
||||
# cyrilliques. Il est donc écrit explicitement en UTF-8 SANS BOM (voir l'écriture
|
||||
# ci-dessous) ; il en va de même du JSON qu'il produit, un BOM en tête de fichier
|
||||
# faisant échouer les lecteurs JSON stricts.
|
||||
#
|
||||
# Corollaire pour CE fichier : le script doit lui-même être enregistré en UTF-8
|
||||
# AVEC BOM, sinon Windows PowerShell 5.1 (qui lit l'UTF-8 sans BOM comme de
|
||||
# l'ANSI) transformerait les textes de référence en caractères parasites — et un
|
||||
# fixture de textes corrompus ne mesurerait plus rien.
|
||||
|
||||
$ProgrammePython = @'
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Écrit le fixture JSON d'un modèle SentencePiece .spm.
|
||||
|
||||
Usage : python <ce_fichier.py> <modele.spm> <sortie.fixtures.json>
|
||||
|
||||
Le fixture décrit le découpage de la bibliothèque de RÉFÉRENCE, jamais celui de
|
||||
l'implémentation maison : `ids` vient de EncodeAsIds et `decode` de DecodeIds
|
||||
appliqué à CES identifiants. Le fixture est donc auto-cohérent, et le test .NET
|
||||
compare le tokeniseur C# à la référence au lieu de le comparer à une
|
||||
transcription manuelle, qui figerait une erreur de recopie.
|
||||
"""
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
from sentencepiece import sentencepiece as spm
|
||||
|
||||
|
||||
def lire_varint(donnees, position):
|
||||
"""Lit un entier variable protobuf : (valeur, position suivante)."""
|
||||
valeur = 0
|
||||
decalage = 0
|
||||
while True:
|
||||
octet = donnees[position]
|
||||
position += 1
|
||||
valeur |= (octet & 0x7F) << decalage
|
||||
if not octet & 0x80:
|
||||
return valeur, position
|
||||
decalage += 7
|
||||
|
||||
|
||||
def champs(donnees):
|
||||
"""Parcourt les champs d'un message protobuf : (numéro, type de fil, valeur)."""
|
||||
position = 0
|
||||
while position < len(donnees):
|
||||
cle, position = lire_varint(donnees, position)
|
||||
champ, fil = cle >> 3, cle & 0x07
|
||||
|
||||
if fil == 0:
|
||||
valeur, position = lire_varint(donnees, position)
|
||||
elif fil == 1:
|
||||
valeur, position = donnees[position:position + 8], position + 8
|
||||
elif fil == 2:
|
||||
longueur, position = lire_varint(donnees, position)
|
||||
valeur, position = donnees[position:position + longueur], position + longueur
|
||||
elif fil == 5:
|
||||
valeur, position = donnees[position:position + 4], position + 4
|
||||
else:
|
||||
raise ValueError("type de fil protobuf non pris en charge : %d" % fil)
|
||||
|
||||
yield champ, fil, valeur
|
||||
|
||||
|
||||
def spec_normalisation_a_la_main(chemin):
|
||||
"""Repli sans dépendance : lit normalizer_spec (champ 3 du ModelProto).
|
||||
|
||||
`sentencepiece_model_pb2` n'est pas toujours importable (il dépend du runtime
|
||||
protobuf, absent de certaines installations minimales) ; or ces quatre
|
||||
réglages décident du découpage — les deviner fausserait tout le fixture. Les
|
||||
valeurs par défaut reproduites ici sont celles du proto2 de SentencePiece :
|
||||
un champ absent vaut True.
|
||||
"""
|
||||
with open(chemin, "rb") as fichier:
|
||||
donnees = fichier.read()
|
||||
|
||||
for champ, fil, valeur in champs(donnees):
|
||||
if champ != 3 or fil != 2: # ModelProto.normalizer_spec
|
||||
continue
|
||||
|
||||
nom, prefixe, superflus, echapper = "nmt_nfkc", True, True, True
|
||||
for numero, type_fil, contenu in champs(valeur):
|
||||
if numero == 1 and type_fil == 2:
|
||||
nom = contenu.decode("utf-8") or nom
|
||||
elif numero == 3 and type_fil == 0:
|
||||
prefixe = bool(contenu)
|
||||
elif numero == 4 and type_fil == 0:
|
||||
superflus = bool(contenu)
|
||||
elif numero == 5 and type_fil == 0:
|
||||
echapper = bool(contenu)
|
||||
|
||||
return nom, prefixe, superflus, echapper
|
||||
|
||||
return "nmt_nfkc", True, True, True
|
||||
|
||||
|
||||
def spec_normalisation(chemin):
|
||||
"""Réglages de normalisation enregistrés dans le modèle, table lue en priorité."""
|
||||
try:
|
||||
from sentencepiece import sentencepiece_model_pb2
|
||||
|
||||
modele = sentencepiece_model_pb2.ModelProto()
|
||||
with open(chemin, "rb") as fichier:
|
||||
modele.ParseFromString(fichier.read())
|
||||
|
||||
spec = modele.normalizer_spec
|
||||
return (
|
||||
spec.name or "nmt_nfkc",
|
||||
bool(spec.add_dummy_prefix),
|
||||
bool(spec.remove_extra_whitespaces),
|
||||
bool(spec.escape_whitespaces),
|
||||
)
|
||||
except Exception:
|
||||
return spec_normalisation_a_la_main(chemin)
|
||||
|
||||
|
||||
def version_sentencepiece():
|
||||
"""Version de la référence, écrite dans le fixture : elle DATE la comparaison.
|
||||
|
||||
Sans elle, une divergence constatée dans six mois ne se distingue pas d'un
|
||||
changement de version de la bibliothèque.
|
||||
"""
|
||||
import sentencepiece
|
||||
|
||||
version = getattr(sentencepiece, "__version__", None)
|
||||
if not version:
|
||||
version = getattr(getattr(sentencepiece, "sentencepiece", None), "__version__", None)
|
||||
|
||||
return str(version) if version else "inconnue"
|
||||
|
||||
|
||||
def cas_de_reference():
|
||||
"""Textes comparés, avec leur étiquette.
|
||||
|
||||
Les deux langues visées par les packs sont couvertes, plus les entrées qui
|
||||
font diverger un tokeniseur réécrit à la main : espaces de bord et doubles,
|
||||
chaîne vide, chiffres et ponctuation, casse mêlée, alphabets non latins, et
|
||||
une phrase longue — c'est là, et nulle part ailleurs, que se voit le coût du
|
||||
pire cas d'un Viterbi naïf.
|
||||
"""
|
||||
longue = (
|
||||
"La traduction hors ligne doit rester fidèle même sur une phrase très "
|
||||
"longue, parce que c'est exactement le genre d'entrée qui fait diverger "
|
||||
"une implémentation réécrite à la main. "
|
||||
) * 12
|
||||
|
||||
return [
|
||||
("francais-accents", "Bonjour le monde, l'été dernier j'ai mangé une crêpe à Nîmes."),
|
||||
("espagnol-accents", "¿Qué tal? ¡Hola, señor! El niño pequeño añadió más café."),
|
||||
("espaces-bords-et-doubles", " Bonjour le monde "),
|
||||
("chaine-vide", ""),
|
||||
("espaces-seulement", " "),
|
||||
("chiffres-et-ponctuation", "Le total est 1 234,56 € (soit 42 %), lundi 03/02/2025 ; OK ?"),
|
||||
("phrase-tres-longue", longue.strip()),
|
||||
("casse-melangee", "OMIKA Omika omika BonJour LE MONDE Traduction HORS LIGNE"),
|
||||
("arabe", "مرحبا بالعالم، كيف حالك اليوم؟"),
|
||||
("cyrillique", "Здравствуйте, мир! Как ваши дела сегодня?"),
|
||||
("nouvelle-ligne-et-tabulation", "Bonjour\nle\tmonde"),
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
sys.stderr.write("Usage : python <programme> <modele.spm> <sortie.fixtures.json>\n")
|
||||
return 2
|
||||
|
||||
chemin_modele, chemin_sortie = sys.argv[1], sys.argv[2]
|
||||
|
||||
processeur = spm.SentencePieceProcessor(model_file=chemin_modele)
|
||||
|
||||
nom, prefixe, superflus, echapper = spec_normalisation(chemin_modele)
|
||||
|
||||
cas = []
|
||||
for etiquette, texte in cas_de_reference():
|
||||
try:
|
||||
ids = processeur.EncodeAsIds(texte)
|
||||
decode = processeur.DecodeIds(ids)
|
||||
except Exception as erreur:
|
||||
# Le nom du cas est indispensable : sans lui, un modèle exotique
|
||||
# ferait échouer la génération sans dire sur quelle entrée.
|
||||
sys.stderr.write("Cas « %s » en échec : %s\n" % (etiquette, erreur))
|
||||
raise
|
||||
|
||||
cas.append({"texte": texte, "ids": [int(i) for i in ids], "decode": decode})
|
||||
|
||||
fixture = {
|
||||
"modele": os.path.basename(chemin_modele),
|
||||
"sentencepieceVersion": version_sentencepiece(),
|
||||
"vocabSize": int(processeur.get_piece_size()),
|
||||
"addDummyPrefix": bool(prefixe),
|
||||
"removeExtraWhitespaces": bool(superflus),
|
||||
"escapeWhitespaces": bool(echapper),
|
||||
"normalizerName": nom,
|
||||
"cas": cas,
|
||||
}
|
||||
|
||||
# ensure_ascii=False : les textes accentués, arabes et cyrilliques restent
|
||||
# lisibles dans le JSON, ce qui rend une divergence lisible à l'œil.
|
||||
# encoding="utf-8" SANS BOM (Python n'en écrit jamais par défaut) : un BOM
|
||||
# ferait échouer les lecteurs JSON stricts.
|
||||
with open(chemin_sortie, "w", encoding="utf-8", newline="\n") as fichier:
|
||||
json.dump(fixture, fichier, ensure_ascii=False, indent=2)
|
||||
fichier.write("\n")
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
'@
|
||||
|
||||
$cheminProgramme = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-fixtures-sentencepiece-' + (New-Guid).ToString('N') + '.py')
|
||||
[System.IO.File]::WriteAllText($cheminProgramme, $ProgrammePython, (New-Object System.Text.UTF8Encoding($false)))
|
||||
|
||||
$lignes = New-Object System.Collections.Generic.List[object]
|
||||
$copies = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
try {
|
||||
foreach ($modele in $modeles) {
|
||||
$nomFixture = $modele.BaseName + '.fixtures.json'
|
||||
$cheminFixture = Join-Path $Sortie $nomFixture
|
||||
|
||||
Write-Host ("Génération du fixture de « {0} » ({1} Ko)…" -f $modele.Name, [int]($modele.Length / 1KB))
|
||||
|
||||
$arguments = @($python.Arguments) + @($cheminProgramme, $modele.FullName, $cheminFixture)
|
||||
& $python.Chemin @arguments
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Stop-AvecMessage "La génération du fixture de « $($modele.FullName) » a échoué (code $LASTEXITCODE). Aucune fixture n'a été écrite pour ce modèle ; les messages de la bibliothèque sentencepiece ci-dessus indiquent la cause (fichier tronqué, modèle d'un type non géré…)."
|
||||
}
|
||||
|
||||
# Relecture du fixture écrit : c'est ce que lira le test .NET, et c'est
|
||||
# donc cette relecture qui alimente le tableau récapitulatif.
|
||||
$fixture = Get-Content -LiteralPath $cheminFixture -Raw -Encoding UTF8 | ConvertFrom-Json
|
||||
|
||||
if (-not $fixture.vocabSize -or -not $fixture.cas) {
|
||||
Stop-AvecMessage "Le fixture « $(Format-CheminRelatif $cheminFixture) » vient d'être écrit mais ne contient ni taille de vocabulaire ni cas de test : le fichier est inexploitable, ne le commitez pas."
|
||||
}
|
||||
|
||||
if ($CopierModeles) {
|
||||
$destination = Join-Path $Sortie $modele.Name
|
||||
|
||||
if (Test-Path -LiteralPath $destination) {
|
||||
# Deux modèles de même nom venant de dossiers différents : on ne
|
||||
# remplace jamais un .spm existant par un autre (le fixture déjà
|
||||
# écrit pourrait décrire le premier).
|
||||
if ((Get-FileHash -LiteralPath $modele.FullName).Hash -ne (Get-FileHash -LiteralPath $destination).Hash) {
|
||||
Write-Host " ATTENTION : « $(Format-CheminRelatif $destination) » existe avec un contenu différent — copie ignorée, copiez le bon modèle à la main." -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
else {
|
||||
Copy-Item -LiteralPath $modele.FullName -Destination $destination
|
||||
$copies.Add($modele.Name)
|
||||
}
|
||||
}
|
||||
|
||||
$lignes.Add([pscustomobject]@{
|
||||
'Modèle' = $modele.Name
|
||||
'Vocabulaire' = [int]$fixture.vocabSize
|
||||
'Cas' = @($fixture.cas).Count
|
||||
'Fixture' = Format-CheminRelatif $cheminFixture
|
||||
})
|
||||
}
|
||||
}
|
||||
finally {
|
||||
# Rien d'autre que ce fichier temporaire n'est supprimé par ce script.
|
||||
Remove-Item -LiteralPath $cheminProgramme -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host 'Résumé :'
|
||||
$lignes | Format-Table -AutoSize | Out-String | Write-Host
|
||||
|
||||
Write-Host ("{0} fixture(s) écrite(s) dans {1}." -f $lignes.Count, (Format-CheminRelatif $Sortie))
|
||||
|
||||
if ($CopierModeles) {
|
||||
if ($copies.Count -gt 0) {
|
||||
Write-Host ("{0} modèle(s) copié(s) à côté de leur fixture : {1}." -f $copies.Count, ($copies -join ', '))
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "Le test .NET cherche le .spm DANS le dossier des fixtures : relancez avec -CopierModeles, ou copiez-le à la main (il n'est pas versionné)."
|
||||
}
|
||||
|
||||
Write-Host "Ensuite : dotnet test tests\Omika.Tests\Omika.Tests.csproj --filter FullyQualifiedName~SentencePieceFixturesTests"
|
||||
@@ -0,0 +1,42 @@
|
||||
-- =====================================================================
|
||||
-- Omika Diagnostic Center — NETTOYAGE DU SEED LOCAL (FAKE DATA)
|
||||
-- clear_diagnostics_local_seed.sql
|
||||
-- =====================================================================
|
||||
-- ⚠️ RÉSERVÉ AU DÉVELOPPEMENT LOCAL. Supprime UNIQUEMENT les données
|
||||
-- synthétiques marquées source='local-seed'. Aucun TRUNCATE, aucune
|
||||
-- suppression générale, jamais les vraies données.
|
||||
-- ⚠️ À lancer manuellement (aucune exécution automatique).
|
||||
-- =====================================================================
|
||||
|
||||
-- Garde-fou : refuse de s'exécuter hors de la base locale omika_diagnostics.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF current_database() <> 'omika_diagnostics' THEN
|
||||
RAISE EXCEPTION 'REFUS : nettoyage LOCAL uniquement sur la base omika_diagnostics (base détectée : %).', current_database();
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Suppression ciblée et sûre.
|
||||
DELETE FROM diagnostic_event
|
||||
WHERE source = 'local-seed';
|
||||
|
||||
-- =====================================================================
|
||||
-- VÉRIFICATION (T13-G §28) — procédure de contrôle à exécuter séparément.
|
||||
-- Objectif : démontrer que le nettoyage supprime UNIQUEMENT le seed local
|
||||
-- et préserve toute ligne réelle (source <> 'local-seed').
|
||||
--
|
||||
-- AVANT nettoyage (état attendu : 1 vraie ligne + N lignes local-seed) :
|
||||
-- SELECT count(*) FILTER (WHERE source = 'local-seed') AS seed_rows,
|
||||
-- count(*) FILTER (WHERE source <> 'local-seed') AS real_rows
|
||||
-- FROM diagnostic_event;
|
||||
--
|
||||
-- APRÈS exécution de ce script, vérifier :
|
||||
-- SELECT count(*) FROM diagnostic_event WHERE source = 'local-seed';
|
||||
-- -- attendu : 0
|
||||
-- SELECT count(*) FROM diagnostic_event WHERE source <> 'local-seed';
|
||||
-- -- attendu : inchangé (la vraie ligne est conservée)
|
||||
--
|
||||
-- GARANTIE STRUCTURELLE : l'unique instruction de suppression est bornée par
|
||||
-- le marqueur source = 'local-seed'. Aucun TRUNCATE, aucun DELETE sans filtre.
|
||||
-- =====================================================================
|
||||
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"Enabled": true
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,949 @@
|
||||
-- =====================================================================
|
||||
-- Omika Diagnostic Center — SEED PRÉPROD LOCAL (100 % FAKE DATA)
|
||||
-- seed_diagnostics_preprod.sql — T21
|
||||
-- =====================================================================
|
||||
-- ⚠️ RÉSERVÉ À LA PRÉPROD LOCALE (base omika_diagnostics_preprod).
|
||||
-- ⚠️ NE JAMAIS EXÉCUTER EN PRODUCTION.
|
||||
-- ⚠️ Données 100 % SYNTHÉTIQUES : source='local-seed', message préfixé
|
||||
-- '[FAKE LOCAL]'. Aucune donnée réelle, aucune donnée personnelle,
|
||||
-- aucun token, aucun mot de passe, aucun CodeClient réel, aucun nom
|
||||
-- de société cliente réelle, aucun nom de machine réel.
|
||||
-- ⚠️ Ré-exécutable : DELETE source='local-seed' puis réinsertion.
|
||||
-- Ne touche JAMAIS aux lignes réelles (source <> 'local-seed').
|
||||
-- ⚠️ Ne crée AUCUN diagnostics_clients : les clients PREPROD-CLIENT-A/B
|
||||
-- sont créés via l'interface /Clients (clé SHA-256 générée Web).
|
||||
-- =====================================================================
|
||||
|
||||
-- Garde-fou : base préprod uniquement.
|
||||
DO $$
|
||||
BEGIN
|
||||
IF current_database() <> 'omika_diagnostics_preprod' THEN
|
||||
RAISE EXCEPTION 'REFUS : seed préprod uniquement sur omika_diagnostics_preprod (base détectée : %).', current_database();
|
||||
END IF;
|
||||
END
|
||||
$$;
|
||||
|
||||
-- Nettoyage idempotent du seed précédent (source = 'local-seed' uniquement).
|
||||
DELETE FROM diagnostic_event WHERE source = 'local-seed';
|
||||
|
||||
-- =====================================================================
|
||||
-- CONVENTIONS CLIENTS (T21 §7)
|
||||
-- FAKE-CLIENT-A (fort volume) : API-A-01, WORKER-A-01 (Production)
|
||||
-- FAKE-CLIENT-B (moyen) : API-B-01, WORKER-B-01 (Production),
|
||||
-- API-B-STG-01 (Staging)
|
||||
-- FAKE-CLIENT-C (faible) : API-C-DEV-01 (Development)
|
||||
-- =====================================================================
|
||||
|
||||
-- =====================================================================
|
||||
-- 1. VOLUME DE FOND MULTI-CLIENTS (HTTP API + Worker)
|
||||
-- =====================================================================
|
||||
|
||||
-- 1.1 FAKE-CLIENT-A / API-A-01 / Production — 100 événements HTTP API.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2a000000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - CASE (g % 4)
|
||||
WHEN 0 THEN (random() * interval '6 hours')
|
||||
WHEN 1 THEN interval '6 hours' + (random() * interval '18 hours')
|
||||
WHEN 2 THEN interval '1 day' + (random() * interval '6 days')
|
||||
ELSE interval '7 days' + (random() * interval '23 days')
|
||||
END,
|
||||
(ARRAY['P2','P3','P2','P1','P3'])[1 + (g % 5)],
|
||||
(ARRAY['Info','Info','Debug','Warning','Info'])[1 + (g % 5)],
|
||||
(ARRAY['OMK-API-HTTP-001','OMK-API-HTTP-002'])[1 + (g % 2)],
|
||||
'Omika.Api',
|
||||
NULL, NULL, NULL,
|
||||
(ARRAY['fake-actor-001','fake-actor-002',NULL])[1 + (g % 3)],
|
||||
1,
|
||||
(ARRAY['1.0.0','1.1.0'])[1 + (g % 2)],
|
||||
'api',
|
||||
NULL,
|
||||
'[FAKE LOCAL] api FAKE-CLIENT-A production ' || g,
|
||||
NULL, NULL,
|
||||
(ARRAY[25,150,350,1200,NULL,80])[1 + (g % 6)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"volume"}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production'
|
||||
FROM generate_series(1, 100) AS g;
|
||||
|
||||
-- 1.2 FAKE-CLIENT-A / WORKER-A-01 / Production — 60 traitements Worker réussis.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2b000000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - CASE (g % 4)
|
||||
WHEN 0 THEN (random() * interval '6 hours')
|
||||
WHEN 1 THEN interval '6 hours' + (random() * interval '18 hours')
|
||||
WHEN 2 THEN interval '1 day' + (random() * interval '6 days')
|
||||
ELSE interval '7 days' + (random() * interval '23 days')
|
||||
END,
|
||||
'P3',
|
||||
(ARRAY['Info','Debug','Info'])[1 + (g % 3)],
|
||||
'OMK-WKR-SYNC-001',
|
||||
'Omika.Worker',
|
||||
NULL, NULL, NULL,
|
||||
NULL, NULL,
|
||||
(ARRAY['1.0.0','1.1.0'])[1 + (g % 2)],
|
||||
'worker',
|
||||
NULL,
|
||||
'[FAKE LOCAL] worker FAKE-CLIENT-A production ' || g,
|
||||
NULL, NULL,
|
||||
(ARRAY[300,450,600,900])[1 + (g % 4)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"volume","retryCount":0}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-A', 'WORKER-A-01', 'Production'
|
||||
FROM generate_series(1, 60) AS g;
|
||||
|
||||
-- 1.3 FAKE-CLIENT-B / API-B-01 / Production — 50 événements HTTP API.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2c000000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - CASE (g % 4)
|
||||
WHEN 0 THEN (random() * interval '6 hours')
|
||||
WHEN 1 THEN interval '6 hours' + (random() * interval '18 hours')
|
||||
WHEN 2 THEN interval '1 day' + (random() * interval '6 days')
|
||||
ELSE interval '7 days' + (random() * interval '23 days')
|
||||
END,
|
||||
(ARRAY['P2','P3','P2','P1','P3'])[1 + (g % 5)],
|
||||
(ARRAY['Info','Info','Debug','Warning','Info'])[1 + (g % 5)],
|
||||
(ARRAY['OMK-API-HTTP-001','OMK-API-HTTP-002'])[1 + (g % 2)],
|
||||
'Omika.Api',
|
||||
NULL, NULL, NULL,
|
||||
(ARRAY['fake-actor-002','fake-actor-003',NULL])[1 + (g % 3)],
|
||||
1,
|
||||
(ARRAY['1.0.0','1.0.1'])[1 + (g % 2)],
|
||||
'api',
|
||||
NULL,
|
||||
'[FAKE LOCAL] api FAKE-CLIENT-B production ' || g,
|
||||
NULL, NULL,
|
||||
(ARRAY[30,140,360,1100,NULL,90])[1 + (g % 6)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"volume"}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production'
|
||||
FROM generate_series(1, 50) AS g;
|
||||
|
||||
-- 1.4 FAKE-CLIENT-B / WORKER-B-01 / Production — 25 traitements Worker réussis.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2d000000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - CASE (g % 4)
|
||||
WHEN 0 THEN (random() * interval '6 hours')
|
||||
WHEN 1 THEN interval '6 hours' + (random() * interval '18 hours')
|
||||
WHEN 2 THEN interval '1 day' + (random() * interval '6 days')
|
||||
ELSE interval '7 days' + (random() * interval '23 days')
|
||||
END,
|
||||
'P3',
|
||||
(ARRAY['Info','Debug','Info'])[1 + (g % 3)],
|
||||
'OMK-WKR-SYNC-001',
|
||||
'Omika.Worker',
|
||||
NULL, NULL, NULL,
|
||||
NULL, NULL,
|
||||
(ARRAY['1.0.0','1.0.1'])[1 + (g % 2)],
|
||||
'worker',
|
||||
NULL,
|
||||
'[FAKE LOCAL] worker FAKE-CLIENT-B production ' || g,
|
||||
NULL, NULL,
|
||||
(ARRAY[280,420,580,850])[1 + (g % 4)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"volume","retryCount":0}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-B', 'WORKER-B-01', 'Production'
|
||||
FROM generate_series(1, 25) AS g;
|
||||
|
||||
-- 1.5 FAKE-CLIENT-B / API-B-STG-01 / Staging — 15 événements HTTP API.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2e000000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - CASE (g % 4)
|
||||
WHEN 0 THEN (random() * interval '6 hours')
|
||||
WHEN 1 THEN interval '6 hours' + (random() * interval '18 hours')
|
||||
WHEN 2 THEN interval '1 day' + (random() * interval '6 days')
|
||||
ELSE interval '7 days' + (random() * interval '23 days')
|
||||
END,
|
||||
(ARRAY['P2','P3','P2'])[1 + (g % 3)],
|
||||
(ARRAY['Info','Debug','Info'])[1 + (g % 3)],
|
||||
(ARRAY['OMK-API-HTTP-001','OMK-API-HTTP-002'])[1 + (g % 2)],
|
||||
'Omika.Api',
|
||||
NULL, NULL, NULL,
|
||||
'fake-actor-003', 1,
|
||||
(ARRAY['1.0.1','1.1.0'])[1 + (g % 2)],
|
||||
'api',
|
||||
NULL,
|
||||
'[FAKE LOCAL] api FAKE-CLIENT-B staging ' || g,
|
||||
NULL, NULL,
|
||||
(ARRAY[40,150,380])[1 + (g % 3)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"volume"}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-B', 'API-B-STG-01', 'Staging'
|
||||
FROM generate_series(1, 15) AS g;
|
||||
|
||||
-- 1.6 FAKE-CLIENT-C / API-C-DEV-01 / Development — 20 événements HTTP API.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2f000000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - CASE (g % 4)
|
||||
WHEN 0 THEN (random() * interval '6 hours')
|
||||
WHEN 1 THEN interval '6 hours' + (random() * interval '18 hours')
|
||||
WHEN 2 THEN interval '1 day' + (random() * interval '6 days')
|
||||
ELSE interval '7 days' + (random() * interval '23 days')
|
||||
END,
|
||||
'P3',
|
||||
(ARRAY['Info','Debug','Warning','Info'])[1 + (g % 4)],
|
||||
(ARRAY['OMK-API-HTTP-001','OMK-API-HTTP-002'])[1 + (g % 2)],
|
||||
'Omika.Api',
|
||||
NULL, NULL, NULL,
|
||||
NULL, NULL,
|
||||
'1.1.0',
|
||||
'api',
|
||||
NULL,
|
||||
'[FAKE LOCAL] api FAKE-CLIENT-C development ' || g,
|
||||
NULL, NULL,
|
||||
(ARRAY[35,160,400])[1 + (g % 3)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"volume"}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-C', 'API-C-DEV-01', 'Development'
|
||||
FROM generate_series(1, 20) AS g;
|
||||
|
||||
-- =====================================================================
|
||||
-- 2. ERREURS FAKE (Info/Warning/Error/Critical + codes répétés, §15)
|
||||
-- Normalisation component/error_code testée par variations casse/espaces.
|
||||
-- =====================================================================
|
||||
|
||||
-- 2.1 TIMEOUT — 15 occurrences réparties A/B (variations de casse/espaces).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2e100000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - (random() * interval '7 days'),
|
||||
'P1',
|
||||
(ARRAY['Error','error'])[1 + (g % 2)],
|
||||
'OMK-API-HTTP-003',
|
||||
(ARRAY['Omika.Api',' omika.api ','OMIKA.API'])[1 + (g % 3)],
|
||||
NULL, NULL, NULL,
|
||||
'fake-actor-001', 1,
|
||||
(ARRAY['1.0.0','1.1.0'])[1 + (g % 2)],
|
||||
'api',
|
||||
(ARRAY['TIMEOUT',' timeout ','timeout'])[1 + (g % 3)],
|
||||
'[FAKE LOCAL] Timeout simulation ' || g,
|
||||
(ARRAY['System.TimeoutException','system.timeoutexception'])[1 + (g % 2)],
|
||||
E'[FAKE LOCAL]\nSystem.TimeoutException: operation timed out\n at Omika.Api.Tests.FakeService.Execute()',
|
||||
(ARRAY[1500,2500,3000,5000])[1 + (g % 4)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"errors","method":"GET","endpoint":"/api/v1/fake","statusCode":504}'::jsonb,
|
||||
now(),
|
||||
(ARRAY['FAKE-CLIENT-A','FAKE-CLIENT-B'])[1 + (g % 2)],
|
||||
(ARRAY['API-A-01','API-B-01'])[1 + (g % 2)],
|
||||
'Production'
|
||||
FROM generate_series(1, 15) AS g;
|
||||
|
||||
-- 2.2 NULL_REF — 10 occurrences FAKE-CLIENT-A.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2e200000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - (random() * interval '14 days'),
|
||||
'P1',
|
||||
'Error',
|
||||
'OMK-API-REQ-001',
|
||||
'Omika.Api',
|
||||
NULL, NULL, NULL,
|
||||
'fake-actor-001', 1,
|
||||
(ARRAY['1.0.0','1.1.0'])[1 + (g % 2)],
|
||||
'api',
|
||||
'NULL_REF',
|
||||
'[FAKE LOCAL] NullReferenceException simulation ' || g,
|
||||
'System.NullReferenceException',
|
||||
E'[FAKE LOCAL]\nSystem.NullReferenceException: Object reference not set\n at Omika.Api.Tests.FakeService.Execute()',
|
||||
NULL,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"errors","endpoint":"/api/v1/fake"}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production'
|
||||
FROM generate_series(1, 10) AS g;
|
||||
|
||||
-- 2.3 HTTP_500 — 12 occurrences FAKE-CLIENT-B (Production + Staging).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2e300000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - (random() * interval '30 days'),
|
||||
'P1',
|
||||
'Error',
|
||||
'OMK-API-HTTP-003',
|
||||
'Omika.Api',
|
||||
NULL, NULL, NULL,
|
||||
'fake-actor-002', 1,
|
||||
(ARRAY['1.0.0','1.0.1'])[1 + (g % 2)],
|
||||
'api',
|
||||
'HTTP_500',
|
||||
'[FAKE LOCAL] HttpRequestException simulation ' || g,
|
||||
'System.Net.Http.HttpRequestException',
|
||||
E'[FAKE LOCAL]\nSystem.Net.Http.HttpRequestException: 500 Internal Server Error\n at Omika.Api.Tests.FakeService.Send()',
|
||||
(ARRAY[800,1200,2000])[1 + (g % 3)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"errors","statusCode":500}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-B',
|
||||
(ARRAY['API-B-01','API-B-STG-01'])[1 + (g % 2)],
|
||||
(ARRAY['Production','Staging'])[1 + (g % 2)]
|
||||
FROM generate_series(1, 12) AS g;
|
||||
|
||||
-- 2.4 SYNC_FAILED — 8 occurrences (échec de synchronisation).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2e400000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - (random() * interval '10 days'),
|
||||
'P1',
|
||||
'Error',
|
||||
'OMK-WKR-SYNC-002',
|
||||
'Omika.Worker',
|
||||
NULL, NULL, NULL,
|
||||
NULL, NULL,
|
||||
(ARRAY['1.0.0','1.0.1'])[1 + (g % 2)],
|
||||
'worker',
|
||||
'SYNC_FAILED',
|
||||
'[FAKE LOCAL] sync failure simulation ' || g,
|
||||
'System.InvalidOperationException',
|
||||
E'[FAKE LOCAL]\nSystem.InvalidOperationException: sync rejected\n at Omika.Worker.Tests.FakeHandler.Apply()',
|
||||
(ARRAY[500,600,900])[1 + (g % 3)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"errors","retryCount":2}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-B', 'WORKER-B-01', 'Production'
|
||||
FROM generate_series(1, 8) AS g;
|
||||
|
||||
-- 2.5 SYNC_RETRY — 8 occurrences (retry transitoire).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2e500000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - (random() * interval '8 days'),
|
||||
'P2',
|
||||
'Warning',
|
||||
'OMK-WKR-SYNC-002',
|
||||
'Omika.Worker',
|
||||
NULL, NULL, NULL,
|
||||
NULL, NULL,
|
||||
(ARRAY['1.0.0','1.1.0'])[1 + (g % 2)],
|
||||
'worker',
|
||||
'SYNC_RETRY',
|
||||
'[FAKE LOCAL] sync retry simulation ' || g,
|
||||
NULL, NULL,
|
||||
(ARRAY[200,300,400])[1 + (g % 3)]::bigint,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"errors","retryCount":1}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-A', 'WORKER-A-01', 'Production'
|
||||
FROM generate_series(1, 8) AS g;
|
||||
|
||||
-- 2.6 CRASH critique — 4 occurrences FAKE-CLIENT-C (Critical).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
SELECT
|
||||
('2e600000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - (random() * interval '2 days'),
|
||||
'P0',
|
||||
'Critical',
|
||||
'OMK-API-HTTP-003',
|
||||
'Omika.Api',
|
||||
NULL, NULL, NULL,
|
||||
NULL, NULL,
|
||||
'1.1.0',
|
||||
'api',
|
||||
'CRASH',
|
||||
'[FAKE LOCAL] Critical crash simulation ' || g,
|
||||
'System.OutOfMemoryException',
|
||||
E'[FAKE LOCAL]\nSystem.OutOfMemoryException: insufficient memory\n at Omika.Api.Tests.FakeService.Allocate()',
|
||||
NULL,
|
||||
'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"errors"}'::jsonb,
|
||||
now(),
|
||||
'FAKE-CLIENT-C', 'API-C-DEV-01', 'Development'
|
||||
FROM generate_series(1, 4) AS g;
|
||||
|
||||
-- =====================================================================
|
||||
-- 3. TRACES SYNTHÉTIQUES (TraceId partagé) — pour /Traces (§12)
|
||||
-- =====================================================================
|
||||
|
||||
-- Trace T1 : FAKE-CLIENT-A / API-A-01 — mobile → API → réponse (4 événements).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('3a000000-0000-0000-0000-000000000001', now() - interval '35 minutes', 'P2', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
'aaaaaaaa-1111-4111-8111-111111111111', NULL, NULL, 'fake-actor-001', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] trace T1 - mobile started', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production'),
|
||||
('3a000000-0000-0000-0000-000000000002', now() - interval '34 minutes', 'P2', 'Info', 'OMK-API-HTTP-001', 'Omika.Api',
|
||||
'aaaaaaaa-1111-4111-8111-111111111111', NULL, NULL, NULL, NULL, '1.1.0', 'api', NULL,
|
||||
'[FAKE LOCAL] trace T1 - api request', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace","method":"POST","endpoint":"/api/v1/sync"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production'),
|
||||
('3a000000-0000-0000-0000-000000000003', now() - interval '33 minutes', 'P2', 'Info', 'OMK-API-HTTP-002', 'Omika.Api',
|
||||
'aaaaaaaa-1111-4111-8111-111111111111', NULL, NULL, NULL, NULL, '1.1.0', 'api', NULL,
|
||||
'[FAKE LOCAL] trace T1 - api processing', NULL, NULL, 350, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace","statusCode":200}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production'),
|
||||
('3a000000-0000-0000-0000-000000000004', now() - interval '32 minutes', 'P2', 'Info', 'OMK-API-HTTP-002', 'Omika.Api',
|
||||
'aaaaaaaa-1111-4111-8111-111111111111', NULL, NULL, NULL, NULL, '1.1.0', 'api', NULL,
|
||||
'[FAKE LOCAL] trace T1 - api response', NULL, NULL, 420, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace","statusCode":200}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production');
|
||||
|
||||
-- Trace T2 : FAKE-CLIENT-B / API-B-01 — API → traitement → erreur (3 événements).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('3b000000-0000-0000-0000-000000000001', now() - interval '6 hours', 'P2', 'Info', 'OMK-API-HTTP-001', 'Omika.Api',
|
||||
'bbbbbbbb-2222-4222-8222-222222222222', NULL, NULL, NULL, NULL, '1.0.1', 'api', NULL,
|
||||
'[FAKE LOCAL] trace T2 - api request', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace","method":"GET","endpoint":"/api/v1/fake"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production'),
|
||||
('3b000000-0000-0000-0000-000000000003', now() - interval '6 hours' + interval '25 ms', 'P2', 'Info', 'OMK-API-HTTP-002', 'Omika.Api',
|
||||
'bbbbbbbb-2222-4222-8222-222222222222', NULL, NULL, NULL, NULL, '1.0.1', 'api', NULL,
|
||||
'[FAKE LOCAL] trace T2 - api processing', NULL, NULL, 300, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace","statusCode":200}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production'),
|
||||
('3b000000-0000-0000-0000-000000000002', now() - interval '6 hours' + interval '50 ms', 'P1', 'Error', 'OMK-API-HTTP-003', 'Omika.Api',
|
||||
'bbbbbbbb-2222-4222-8222-222222222222', NULL, NULL, NULL, NULL, '1.0.1', 'api', 'HTTP_500',
|
||||
'[FAKE LOCAL] trace T2 - traitement en erreur', 'System.Net.Http.HttpRequestException',
|
||||
E'[FAKE LOCAL]\nSystem.Net.Http.HttpRequestException: 500\n at Omika.Api.Tests.FakeService.Send()', 1200, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace","statusCode":500}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production');
|
||||
|
||||
-- Trace T3 : FAKE-CLIENT-C / API-C-DEV-01 / Development — succès rapide (3 événements).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('3c000000-0000-0000-0000-000000000001', now() - interval '12 hours', 'P3', 'Info', 'OMK-API-HTTP-001', 'Omika.Api',
|
||||
'cccccccc-3333-4333-8333-333333333333', NULL, NULL, NULL, NULL, '1.1.0', 'api', NULL,
|
||||
'[FAKE LOCAL] trace T3 - api request', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-C', 'API-C-DEV-01', 'Development'),
|
||||
('3c000000-0000-0000-0000-000000000002', now() - interval '12 hours' + interval '30 ms', 'P3', 'Info', 'OMK-API-HTTP-002', 'Omika.Api',
|
||||
'cccccccc-3333-4333-8333-333333333333', NULL, NULL, NULL, NULL, '1.1.0', 'api', NULL,
|
||||
'[FAKE LOCAL] trace T3 - api processing', NULL, NULL, 80, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace","statusCode":200}'::jsonb, now(),
|
||||
'FAKE-CLIENT-C', 'API-C-DEV-01', 'Development'),
|
||||
('3c000000-0000-0000-0000-000000000003', now() - interval '12 hours' + interval '45 ms', 'P3', 'Info', 'OMK-API-HTTP-002', 'Omika.Api',
|
||||
'cccccccc-3333-4333-8333-333333333333', NULL, NULL, NULL, NULL, '1.1.0', 'api', NULL,
|
||||
'[FAKE LOCAL] trace T3 - api response', NULL, NULL, 90, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"trace","statusCode":200}'::jsonb, now(),
|
||||
'FAKE-CLIENT-C', 'API-C-DEV-01', 'Development');
|
||||
|
||||
-- =====================================================================
|
||||
-- 4. SYNCHRONISATION SYNTHÉTIQUE (OperationId partagé) — succès/retry/échec (§25)
|
||||
-- EventCodes exacts : OMK-MOB-SYNC-001 / OMK-WKR-SYNC-001 / OMK-WKR-SYNC-002
|
||||
-- =====================================================================
|
||||
|
||||
-- Opération OP-SUCCESS (FAKE-CLIENT-A / Production) : succès mobile → worker.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('45000000-0000-0000-0000-000000000001', now() - interval '1 hour', 'P2', 'Info', 'OMK-MOB-SYNC-001', 'Omika.App',
|
||||
NULL, '5e5e5e5e-5000-4000-8000-0000000000a1', '0a0a0a0a-1000-4000-8000-000000000001', 'fake-actor-001', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] sync OP-SUCCESS - operation reçue', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"sync"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production'),
|
||||
('45000000-0000-0000-0000-000000000002', now() - interval '59 minutes', 'P3', 'Info', 'OMK-WKR-SYNC-001', 'Omika.Worker',
|
||||
NULL, NULL, '0a0a0a0a-1000-4000-8000-000000000001', NULL, NULL, '1.1.0', 'worker', NULL,
|
||||
'[FAKE LOCAL] sync OP-SUCCESS - worker processing', NULL, NULL, 450, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"sync","retryCount":0}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'WORKER-A-01', 'Production'),
|
||||
('45000000-0000-0000-0000-000000000003', now() - interval '58 minutes', 'P3', 'Info', 'OMK-WKR-SYNC-001', 'Omika.Worker',
|
||||
NULL, NULL, '0a0a0a0a-1000-4000-8000-000000000001', NULL, NULL, '1.1.0', 'worker', NULL,
|
||||
'[FAKE LOCAL] sync OP-SUCCESS - worker success', NULL, NULL, 480, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"sync","retryCount":0}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'WORKER-A-01', 'Production');
|
||||
|
||||
-- Opération OP-FAIL (FAKE-CLIENT-B / Production) : échec définitif worker.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('45000000-0000-0000-0000-000000000004', now() - interval '3 hours', 'P1', 'Info', 'OMK-MOB-SYNC-001', 'Omika.App',
|
||||
NULL, '5e5e5e5e-5000-4000-8000-0000000000a2', '0b0b0b0b-1000-4000-8000-000000000002', 'fake-actor-002', 1, '1.0.0', 'iOS', NULL,
|
||||
'[FAKE LOCAL] sync OP-FAIL - operation reçue', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"sync"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production'),
|
||||
('45000000-0000-0000-0000-000000000005', now() - interval '3 hours' + interval '10 seconds', 'P1', 'Error', 'OMK-WKR-SYNC-002', 'Omika.Worker',
|
||||
NULL, NULL, '0b0b0b0b-1000-4000-8000-000000000002', NULL, NULL, '1.0.0', 'worker', 'SYNC_FAILED',
|
||||
'[FAKE LOCAL] sync OP-FAIL - worker failure', 'System.InvalidOperationException',
|
||||
E'[FAKE LOCAL]\nSystem.InvalidOperationException: sync rejected\n at Omika.Worker.Tests.FakeHandler.Apply()', 600, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"sync","retryCount":2}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'WORKER-B-01', 'Production');
|
||||
|
||||
-- Opération OP-RETRY (FAKE-CLIENT-B / Production) : retry transitoire worker.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('45000000-0000-0000-0000-000000000006', now() - interval '9 hours', 'P2', 'Warning', 'OMK-MOB-SYNC-001', 'Omika.App',
|
||||
NULL, '5e5e5e5e-5000-4000-8000-0000000000a3', '0c0c0c0c-1000-4000-8000-000000000003', 'fake-actor-003', 1, '1.1.0', 'Desktop', NULL,
|
||||
'[FAKE LOCAL] sync OP-RETRY - operation reçue', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"sync"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production'),
|
||||
('45000000-0000-0000-0000-000000000007', now() - interval '9 hours' + interval '5 seconds', 'P2', 'Warning', 'OMK-WKR-SYNC-002', 'Omika.Worker',
|
||||
NULL, NULL, '0c0c0c0c-1000-4000-8000-000000000003', NULL, NULL, '1.1.0', 'worker', 'SYNC_RETRY',
|
||||
'[FAKE LOCAL] sync OP-RETRY - transient retry', NULL, NULL, 300, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"sync","retryCount":1}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'WORKER-B-01', 'Production');
|
||||
|
||||
-- =====================================================================
|
||||
-- 5. SESSIONS PARTAGÉES (SessionId) — corrélation du détail.
|
||||
-- =====================================================================
|
||||
|
||||
-- Session S1 (FAKE-CLIENT-A / Production).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('5a000000-0000-0000-0000-000000000001', now() - interval '2 hours', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, '5e5e5e5e-5000-4000-8000-0000000000b1', NULL, 'fake-actor-001', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] session S1 - app started', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"session"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production'),
|
||||
('5a000000-0000-0000-0000-000000000002', now() - interval '1 hour 50 minutes', 'P3', 'Info', 'OMK-MOB-DIAG-001', 'Omika.App',
|
||||
NULL, '5e5e5e5e-5000-4000-8000-0000000000b1', NULL, 'fake-actor-001', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] session S1 - diagnostic init', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"session"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production'),
|
||||
('5a000000-0000-0000-0000-000000000003', now() - interval '1 hour 40 minutes', 'P2', 'Warning', 'OMK-MOB-APP-002', 'Omika.App',
|
||||
NULL, '5e5e5e5e-5000-4000-8000-0000000000b1', NULL, 'fake-actor-001', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] session S1 - app session stopped', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"session"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production');
|
||||
|
||||
-- Session S2 (FAKE-CLIENT-B / Production).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('5b000000-0000-0000-0000-000000000001', now() - interval '30 minutes', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, '5f5f5f5f-5000-4000-8000-0000000000b2', NULL, 'fake-actor-002', 1, '1.0.0', 'iOS', NULL,
|
||||
'[FAKE LOCAL] session S2 - app started', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"session"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production'),
|
||||
('5b000000-0000-0000-0000-000000000002', now() - interval '29 minutes', 'P3', 'Info', 'OMK-MOB-DIAG-001', 'Omika.App',
|
||||
NULL, '5f5f5f5f-5000-4000-8000-0000000000b2', NULL, 'fake-actor-002', 1, '1.0.0', 'iOS', NULL,
|
||||
'[FAKE LOCAL] session S2 - diagnostic init', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"session"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production');
|
||||
|
||||
-- =====================================================================
|
||||
-- 6. SCREENVIEW (usage, §13) — 250 vues pondérées sur 12 écrans du catalogue.
|
||||
-- EventCode OMK-MOB-UI-001. AGENDA dominant, SETTINGS rare.
|
||||
-- =====================================================================
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment,
|
||||
license_number, device_key, device_key_version, device_manufacturer,
|
||||
device_model, operating_system, operating_system_version, app_build,
|
||||
screen_width, screen_height, logical_width, logical_height, display_scale,
|
||||
screen_code, feature_code)
|
||||
SELECT
|
||||
('61000000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - (g % 60) * interval '5 hours',
|
||||
'P4', 'Info', 'OMK-MOB-UI-001', 'Navigation',
|
||||
NULL, ('6a000000-0000-0000-0000-' || lpad((g % 30)::text, 12, '0'))::uuid, NULL,
|
||||
(ARRAY['fake-actor-001','fake-actor-002','fake-actor-003'])[1 + (g % 3)], 1,
|
||||
(ARRAY['1.0.0','1.0.1','1.1.0'])[1 + (g % 3)],
|
||||
(ARRAY['Android','Android','iOS','Desktop'])[1 + (g % 4)],
|
||||
NULL, '[FAKE LOCAL] ScreenView', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"screen-view"}'::jsonb, now(),
|
||||
(ARRAY['FAKE-CLIENT-A','FAKE-CLIENT-B','FAKE-CLIENT-C'])[1 + (g % 3)],
|
||||
(ARRAY['API-A-01','API-B-01','API-C-DEV-01'])[1 + (g % 3)],
|
||||
(ARRAY['Production','Production','Development'])[1 + (g % 3)],
|
||||
(ARRAY['FAKE-LIC-C','FAKE-LIC-D','FAKE-LIC-E','FAKE-LIC-DESKTOP-A','FAKE-LIC-DESKTOP-B','FAKE-LIC-F'])[1 + (g % 6)],
|
||||
(ARRAY['devk-android-google-c','devk-ios-apple-d','devk-ios-apple-e','devk-desktop-windows-f','devk-desktop-windows-g','devk-android-extra-h'])[1 + (g % 6)], 1,
|
||||
(ARRAY['Google','Apple','Apple','Microsoft','Dell','OnePlus'])[1 + (g % 6)],
|
||||
(ARRAY['Pixel 8','iPhone 15 Pro','iPhone 16 Pro Max','Surface Pro 9','XPS 15','OnePlus 12'])[1 + (g % 6)],
|
||||
(ARRAY['Android','iOS','iOS','Windows','Windows','Android'])[1 + (g % 6)],
|
||||
(ARRAY['14','17.6','18.7','11','10','14'])[1 + (g % 6)],
|
||||
(ARRAY['1','2','3','4','5','6'])[1 + (g % 6)],
|
||||
1080, 2400, 393, 873, 2.75,
|
||||
(CASE
|
||||
WHEN g <= 40 THEN 'AGENDA'
|
||||
WHEN g <= 70 THEN 'SITE_DETAIL'
|
||||
WHEN g <= 100 THEN 'RENDEZVOUS_DETAIL'
|
||||
WHEN g <= 130 THEN 'INTERVENTION_SEARCH'
|
||||
WHEN g <= 160 THEN 'INTERVENTION_DETAIL'
|
||||
WHEN g <= 180 THEN 'RAPPORT'
|
||||
WHEN g <= 195 THEN 'EMAIL'
|
||||
WHEN g <= 210 THEN 'TEAM'
|
||||
WHEN g <= 220 THEN 'SETTINGS'
|
||||
WHEN g <= 230 THEN 'DOSSIER_LIST'
|
||||
WHEN g <= 240 THEN 'CLIENT_LIST'
|
||||
ELSE 'SITE_LIST'
|
||||
END),
|
||||
NULL
|
||||
FROM generate_series(1, 250) AS g;
|
||||
|
||||
-- =====================================================================
|
||||
-- 7. FEATUREUSE (usage, §14) — 120 utilisations, fréquences DIFFÉRENTES.
|
||||
-- INTERVENTION_SEARCH_RUN (60) > REPORT_GENERATE (36) > EMAIL_SEND (24).
|
||||
-- EventCode OMK-MOB-UI-002.
|
||||
-- =====================================================================
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment,
|
||||
license_number, device_key, device_key_version, device_manufacturer,
|
||||
device_model, operating_system, operating_system_version, app_build,
|
||||
screen_width, screen_height, logical_width, logical_height, display_scale,
|
||||
screen_code, feature_code)
|
||||
SELECT
|
||||
('62000000-0000-0000-0000-' || lpad(g::text, 12, '0'))::uuid,
|
||||
now() - (g % 50) * interval '4 hours',
|
||||
'P4', 'Info', 'OMK-MOB-UI-002',
|
||||
(CASE WHEN g <= 60 THEN 'InterventionsViewModel'
|
||||
WHEN g <= 96 THEN 'RapportChampsViewModel'
|
||||
ELSE 'NouveauEmailViewModel' END),
|
||||
NULL, ('6a000000-0000-0000-0000-' || lpad((g % 30)::text, 12, '0'))::uuid, NULL,
|
||||
(ARRAY['fake-actor-001','fake-actor-002','fake-actor-003'])[1 + (g % 3)], 1,
|
||||
(ARRAY['1.0.0','1.0.1','1.1.0'])[1 + (g % 3)],
|
||||
(ARRAY['Android','Android','iOS','Desktop'])[1 + (g % 4)],
|
||||
NULL, '[FAKE LOCAL] FeatureUse', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"feature-use"}'::jsonb, now(),
|
||||
(ARRAY['FAKE-CLIENT-A','FAKE-CLIENT-B','FAKE-CLIENT-C'])[1 + (g % 3)],
|
||||
(ARRAY['API-A-01','API-B-01','API-C-DEV-01'])[1 + (g % 3)],
|
||||
(ARRAY['Production','Production','Development'])[1 + (g % 3)],
|
||||
(ARRAY['FAKE-LIC-C','FAKE-LIC-D','FAKE-LIC-E','FAKE-LIC-DESKTOP-A','FAKE-LIC-DESKTOP-B','FAKE-LIC-F'])[1 + (g % 6)],
|
||||
(ARRAY['devk-android-google-c','devk-ios-apple-d','devk-ios-apple-e','devk-desktop-windows-f','devk-desktop-windows-g','devk-android-extra-h'])[1 + (g % 6)], 1,
|
||||
(ARRAY['Google','Apple','Apple','Microsoft','Dell','OnePlus'])[1 + (g % 6)],
|
||||
(ARRAY['Pixel 8','iPhone 15 Pro','iPhone 16 Pro Max','Surface Pro 9','XPS 15','OnePlus 12'])[1 + (g % 6)],
|
||||
(ARRAY['Android','iOS','iOS','Windows','Windows','Android'])[1 + (g % 6)],
|
||||
(ARRAY['14','17.6','18.7','11','10','14'])[1 + (g % 6)],
|
||||
(ARRAY['1','2','3','4','5','6'])[1 + (g % 6)],
|
||||
1080, 2400, 393, 873, 2.75,
|
||||
(CASE WHEN g <= 60 THEN 'INTERVENTION_SEARCH'
|
||||
WHEN g <= 96 THEN 'RAPPORT'
|
||||
ELSE 'EMAIL' END),
|
||||
(CASE WHEN g <= 60 THEN 'INTERVENTION_SEARCH_RUN'
|
||||
WHEN g <= 96 THEN 'REPORT_GENERATE'
|
||||
ELSE 'EMAIL_SEND' END)
|
||||
FROM generate_series(1, 120) AS g;
|
||||
|
||||
-- =====================================================================
|
||||
-- 8. CAS PARC (§9-§11) : licence réattribuée, changement OS, changement version,
|
||||
-- appareils Desktop/Windows, variété de flotte.
|
||||
-- =====================================================================
|
||||
|
||||
-- 8.1 Réattribution de licence FAKE-LIC-REASSIGNED :
|
||||
-- Device A (Android 15, ancien) → Device B (iOS 18, récent).
|
||||
-- MÊME LicenseNumber sur deux DeviceKey = 2 appareils DISTINCTS, jamais fusionnés.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment,
|
||||
license_number, device_key, device_key_version, device_manufacturer,
|
||||
device_model, operating_system, operating_system_version, app_build,
|
||||
screen_width, screen_height, logical_width, logical_height, display_scale,
|
||||
screen_code, feature_code)
|
||||
VALUES
|
||||
('71000000-0000-0000-0000-000000000001', now() - interval '20 days', 'P2', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-001', 1, '1.0.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] reassigned device A (old)', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"license-reassigned"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-REASSIGNED', 'devk-reassigned-android-a', 1, 'Samsung', 'SM-S921B', 'Android', '15', '1',
|
||||
1080, 2400, 360, 800, 3.0, 'AGENDA', NULL),
|
||||
('71000000-0000-0000-0000-000000000002', now() - interval '18 days', 'P2', 'Info', 'OMK-MOB-DIAG-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-001', 1, '1.0.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] reassigned device A (old)', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"license-reassigned"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-REASSIGNED', 'devk-reassigned-android-a', 1, 'Samsung', 'SM-S921B', 'Android', '15', '1',
|
||||
1080, 2400, 360, 800, 3.0, 'SITE_DETAIL', NULL),
|
||||
('71000000-0000-0000-0000-000000000003', now() - interval '2 days', 'P2', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-002', 1, '1.1.0', 'iOS', NULL,
|
||||
'[FAKE LOCAL] reassigned device B (recent)', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"license-reassigned"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-REASSIGNED', 'devk-reassigned-ios-b', 1, 'Apple', 'iPhone 15 Pro', 'iOS', '18', '3',
|
||||
1179, 2556, 393, 852, 3.0, 'RENDEZVOUS_DETAIL', NULL),
|
||||
('71000000-0000-0000-0000-000000000004', now() - interval '1 day', 'P2', 'Info', 'OMK-MOB-DIAG-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-002', 1, '1.1.0', 'iOS', NULL,
|
||||
'[FAKE LOCAL] reassigned device B (recent)', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"license-reassigned"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-REASSIGNED', 'devk-reassigned-ios-b', 1, 'Apple', 'iPhone 15 Pro', 'iOS', '18', '3',
|
||||
1179, 2556, 393, 852, 3.0, 'INTERVENTION_DETAIL', NULL);
|
||||
|
||||
-- 8.2 Changement d'OS d'un même DeviceKey (§10) : devk-android-samsung-a
|
||||
-- Android 15 → Android 16. Vue parc actuelle = Android 16 (dernière observation).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment,
|
||||
license_number, device_key, device_key_version, device_manufacturer,
|
||||
device_model, operating_system, operating_system_version, app_build,
|
||||
screen_width, screen_height, logical_width, logical_height, display_scale,
|
||||
screen_code, feature_code)
|
||||
VALUES
|
||||
('72000000-0000-0000-0000-000000000001', now() - interval '12 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-001', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] os change samsung-a (old Android 15)', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"os-change"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-A', 'devk-android-samsung-a', 1, 'Samsung', 'SM-S928B', 'Android', '15', '1',
|
||||
1080, 2400, 360, 800, 3.0, 'AGENDA', NULL),
|
||||
('72000000-0000-0000-0000-000000000002', now() - interval '3 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-001', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] os change samsung-a (recent Android 16)', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"os-change"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-A', 'devk-android-samsung-a', 1, 'Samsung', 'SM-S928B', 'Android', '16', '2',
|
||||
1080, 2400, 360, 800, 3.0, 'SITE_DETAIL', NULL);
|
||||
|
||||
-- 8.3 Changement de version Omika d'un même DeviceKey (§11) : devk-android-xiaomi-b
|
||||
-- 1.0.0 → 1.1.0. Vue parc actuelle = 1.1.0 (dernière observation).
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment,
|
||||
license_number, device_key, device_key_version, device_manufacturer,
|
||||
device_model, operating_system, operating_system_version, app_build,
|
||||
screen_width, screen_height, logical_width, logical_height, display_scale,
|
||||
screen_code, feature_code)
|
||||
VALUES
|
||||
('73000000-0000-0000-0000-000000000001', now() - interval '15 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-002', 1, '1.0.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] version change xiaomi-b (old 1.0.0)', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"version-change"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production',
|
||||
'FAKE-LIC-B', 'devk-android-xiaomi-b', 1, 'Xiaomi', '23113RKC6G', 'Android', '16', '1',
|
||||
1080, 2400, 393, 873, 2.75, 'AGENDA', NULL),
|
||||
('73000000-0000-0000-0000-000000000002', now() - interval '4 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-002', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] version change xiaomi-b (recent 1.1.0)', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"version-change"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production',
|
||||
'FAKE-LIC-B', 'devk-android-xiaomi-b', 1, 'Xiaomi', '23113RKC6G', 'Android', '16', '3',
|
||||
1080, 2400, 393, 873, 2.75, 'SITE_DETAIL', NULL);
|
||||
|
||||
-- 8.4 Appareils Desktop/Windows + variété de flotte (§8) — 8 observations.
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment,
|
||||
license_number, device_key, device_key_version, device_manufacturer,
|
||||
device_model, operating_system, operating_system_version, app_build,
|
||||
screen_width, screen_height, logical_width, logical_height, display_scale,
|
||||
screen_code, feature_code)
|
||||
VALUES
|
||||
('74000000-0000-0000-0000-000000000001', now() - interval '6 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-001', 1, '1.0.0', 'Desktop', NULL,
|
||||
'[FAKE LOCAL] fleet desktop surface', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"fleet-desktop"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-DESKTOP-A', 'devk-desktop-windows-f', 1, 'Microsoft', 'Surface Pro 9', 'Windows', '11', '1',
|
||||
2880, 1920, 1440, 960, 1.5, 'AGENDA', NULL),
|
||||
('74000000-0000-0000-0000-000000000002', now() - interval '6 days', 'P3', 'Info', 'OMK-MOB-DIAG-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-001', 1, '1.0.0', 'Desktop', NULL,
|
||||
'[FAKE LOCAL] fleet desktop surface diag', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"fleet-desktop"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-DESKTOP-A', 'devk-desktop-windows-f', 1, 'Microsoft', 'Surface Pro 9', 'Windows', '11', '1',
|
||||
2880, 1920, 1440, 960, 1.5, 'SETTINGS', NULL),
|
||||
('74000000-0000-0000-0000-000000000003', now() - interval '8 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-002', 1, '1.1.0', 'Desktop', NULL,
|
||||
'[FAKE LOCAL] fleet desktop dell xps', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"fleet-desktop"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production',
|
||||
'FAKE-LIC-DESKTOP-B', 'devk-desktop-windows-g', 1, 'Dell', 'XPS 15', 'Windows', '10', '3',
|
||||
3840, 2400, 1920, 1200, 2.0, 'TEAM', NULL),
|
||||
('74000000-0000-0000-0000-000000000004', now() - interval '9 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-003', 1, '1.0.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] fleet device google pixel', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"fleet-variety"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-C', 'API-C-DEV-01', 'Development',
|
||||
'FAKE-LIC-C', 'devk-android-google-c', 1, 'Google', 'Pixel 8', 'Android', '14', '2',
|
||||
1080, 2400, 360, 800, 3.0, 'SETTINGS', NULL),
|
||||
('74000000-0000-0000-0000-000000000005', now() - interval '7 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-002', 1, '1.0.0', 'iOS', NULL,
|
||||
'[FAKE LOCAL] fleet device iphone 15 pro', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"fleet-variety"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production',
|
||||
'FAKE-LIC-D', 'devk-ios-apple-d', 1, 'Apple', 'iPhone 15 Pro', 'iOS', '17.6', '1',
|
||||
1179, 2556, 393, 852, 3.0, 'RENDEZVOUS_DETAIL', NULL),
|
||||
('74000000-0000-0000-0000-000000000006', now() - interval '7 days', 'P3', 'Info', 'OMK-MOB-DIAG-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-003', 1, '1.0.0', 'iOS', NULL,
|
||||
'[FAKE LOCAL] fleet device iphone 16 pro max', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"fleet-variety"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-C', 'API-C-DEV-01', 'Development',
|
||||
'FAKE-LIC-E', 'devk-ios-apple-e', 1, 'Apple', 'iPhone 16 Pro Max', 'iOS', '18.7', '2',
|
||||
1206, 2622, 402, 874, 3.0, 'EMAIL', NULL),
|
||||
('74000000-0000-0000-0000-000000000007', now() - interval '10 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-001', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] fleet device samsung again', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"fleet-variety"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-A', 'API-A-01', 'Production',
|
||||
'FAKE-LIC-A', 'devk-android-samsung-a', 1, 'Samsung', 'SM-S928B', 'Android', '16', '2',
|
||||
1080, 2400, 360, 800, 3.0, 'RAPPORT', NULL),
|
||||
('74000000-0000-0000-0000-000000000008', now() - interval '10 days', 'P3', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-002', 1, '1.1.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] fleet device xiaomi again', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"fleet-variety"}'::jsonb, now(),
|
||||
'FAKE-CLIENT-B', 'API-B-01', 'Production',
|
||||
'FAKE-LIC-B', 'devk-android-xiaomi-b', 1, 'Xiaomi', '23113RKC6G', 'Android', '16', '3',
|
||||
1080, 2400, 393, 873, 2.75, 'INTERVENTION_SEARCH', NULL);
|
||||
|
||||
-- =====================================================================
|
||||
-- 9. DONNÉES HISTORIQUES SANS CONTEXTE (§16) :
|
||||
-- device_key / license_number / screen_code / feature_code = NULL.
|
||||
-- Elles restent affichables sans filtre, et exclues par les filtres appareil/
|
||||
-- licence/écran/fonctionnalité. Toujours source='local-seed' + [FAKE LOCAL].
|
||||
-- =====================================================================
|
||||
INSERT INTO diagnostic_event
|
||||
(event_id, timestamp_utc, priority, level, event_code, component,
|
||||
trace_id, session_id, operation_id, actor_key, actor_key_version,
|
||||
app_version, platform, error_code, message, exception_type, stack_trace,
|
||||
duration_ms, source, metadata_json, ingested_at_utc,
|
||||
client_code, server_instance, environment)
|
||||
VALUES
|
||||
('7a000000-0000-0000-0000-000000000001', now() - interval '18 days', 'P2', 'Info', 'OMK-MOB-APP-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-001', 1, '1.0.0', 'Android', NULL,
|
||||
'[FAKE LOCAL] historique sans contexte 1', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"historical-null"}'::jsonb, now(),
|
||||
NULL, NULL, NULL),
|
||||
('7a000000-0000-0000-0000-000000000002', now() - interval '22 days', 'P3', 'Info', 'OMK-API-HTTP-001', 'Omika.Api',
|
||||
NULL, NULL, NULL, NULL, NULL, '1.0.0', 'api', NULL,
|
||||
'[FAKE LOCAL] historique sans contexte 2', NULL, NULL, 120, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"historical-null"}'::jsonb, now(),
|
||||
NULL, NULL, NULL),
|
||||
('7a000000-0000-0000-0000-000000000003', now() - interval '26 days', 'P1', 'Error', 'OMK-API-HTTP-003', 'Omika.Api',
|
||||
NULL, NULL, NULL, NULL, NULL, '1.0.0', 'api', 'TIMEOUT',
|
||||
'[FAKE LOCAL] historique sans contexte 3', 'System.TimeoutException',
|
||||
E'[FAKE LOCAL]\nSystem.TimeoutException: legacy\n at Omika.Api.Tests.FakeService.Execute()', 2000, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"historical-null"}'::jsonb, now(),
|
||||
NULL, NULL, NULL),
|
||||
('7a000000-0000-0000-0000-000000000004', now() - interval '30 days', 'P2', 'Warning', 'OMK-MOB-APP-002', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-002', 1, '1.0.0', 'iOS', NULL,
|
||||
'[FAKE LOCAL] historique sans contexte 4', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"historical-null"}'::jsonb, now(),
|
||||
NULL, NULL, NULL),
|
||||
('7a000000-0000-0000-0000-000000000005', now() - interval '35 days', 'P3', 'Debug', 'OMK-MOB-DIAG-001', 'Omika.App',
|
||||
NULL, NULL, NULL, 'fake-actor-003', 1, '1.0.0', 'Desktop', NULL,
|
||||
'[FAKE LOCAL] historique sans contexte 5', NULL, NULL, NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"historical-null"}'::jsonb, now(),
|
||||
NULL, NULL, NULL),
|
||||
('7a000000-0000-0000-0000-000000000006', now() - interval '40 days', 'P1', 'Error', 'OMK-API-REQ-001', 'Omika.Api',
|
||||
NULL, NULL, NULL, NULL, NULL, '1.0.0', 'api', 'NULL_REF',
|
||||
'[FAKE LOCAL] historique sans contexte 6', 'System.NullReferenceException',
|
||||
E'[FAKE LOCAL]\nSystem.NullReferenceException: legacy\n at Omika.Api.Tests.FakeService.Execute()', NULL, 'local-seed',
|
||||
'{"synthetic":true,"seed":"omika-local-diagnostics-preprod","t21":"historical-null"}'::jsonb, now(),
|
||||
NULL, NULL, NULL);
|
||||
|
||||
-- =====================================================================
|
||||
-- FIN DU SEED T21. Volume attendu (~730 événements local-seed) :
|
||||
-- 270 volume de fond + 57 erreurs + 10 traces + 7 synchronisation +
|
||||
-- 5 sessions + 250 ScreenView + 120 FeatureUse + 16 parc + 6 historiques NULL.
|
||||
-- Requêtes de contrôle :
|
||||
-- SELECT count(*) FROM diagnostic_event WHERE source = 'local-seed';
|
||||
-- SELECT client_code, count(*) FROM diagnostic_event WHERE source='local-seed' GROUP BY 1 ORDER BY 1 NULLS LAST;
|
||||
-- SELECT count(DISTINCT device_key) FROM diagnostic_event WHERE source='local-seed' AND device_key IS NOT NULL;
|
||||
-- SELECT count(DISTINCT license_number) FROM diagnostic_event WHERE source='local-seed' AND license_number IS NOT NULL;
|
||||
-- SELECT screen_code, count(*) FROM diagnostic_event WHERE source='local-seed' AND screen_code IS NOT NULL GROUP BY 1 ORDER BY 2 DESC;
|
||||
-- SELECT feature_code, count(*) FROM diagnostic_event WHERE source='local-seed' AND feature_code IS NOT NULL GROUP BY 1 ORDER BY 2 DESC;
|
||||
-- =====================================================================
|
||||
@@ -0,0 +1,177 @@
|
||||
-- =====================================================================
|
||||
-- Omika Diagnostic Center — SEED RÉFÉRENTIEL COMPLET (généré)
|
||||
-- Généré par scripts\seed-referentiel-complet.ps1
|
||||
-- =====================================================================
|
||||
-- NON DESTRUCTIF : INSERT ... ON CONFLICT DO NOTHING.
|
||||
-- IDEMPOTENT : un libellé modifié par un Admin n'est JAMAIS écrasé.
|
||||
-- Cible : table diagnostics_reference (T24-A, créée par 011_diagnostics_reference.sql).
|
||||
-- Aucun mot de passe, aucune donnée personnelle, aucun secret.
|
||||
-- =====================================================================
|
||||
|
||||
INSERT INTO diagnostics_reference
|
||||
(reference_type, code, label, description, enabled, sort_order, created_at_utc, updated_at_utc)
|
||||
VALUES
|
||||
('SCREEN_CODE', 'AGENDA', 'Agenda', NULL, TRUE, 10, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_SEARCH', 'Recherche d''interventions', NULL, TRUE, 20, now(), now()),
|
||||
('SCREEN_CODE', 'DOSSIER_LIST', 'Dossiers', NULL, TRUE, 30, now(), now()),
|
||||
('SCREEN_CODE', 'TEAM', 'Équipe', NULL, TRUE, 40, now(), now()),
|
||||
('SCREEN_CODE', 'TEAM_DETAIL', 'Détail d''équipe', NULL, TRUE, 50, now(), now()),
|
||||
('SCREEN_CODE', 'CLIENT_LIST', 'Clients', NULL, TRUE, 60, now(), now()),
|
||||
('SCREEN_CODE', 'SITE_LIST', 'Sites', NULL, TRUE, 70, now(), now()),
|
||||
('SCREEN_CODE', 'SETTINGS', 'Paramètres', NULL, TRUE, 80, now(), now()),
|
||||
('SCREEN_CODE', 'RENDEZVOUS_DETAIL', 'Détail de rendez-vous', NULL, TRUE, 90, now(), now()),
|
||||
('SCREEN_CODE', 'AGENDA_TEAM', 'Agenda d''équipe', NULL, TRUE, 100, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_DETAIL', 'Détail d''intervention', NULL, TRUE, 110, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_DOCUMENTS', 'Documents d''intervention', NULL, TRUE, 120, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_NOTES', 'Notes d''intervention', NULL, TRUE, 130, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_NOTES_ADMIN', 'Notes administratives', NULL, TRUE, 140, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_EMAILS', 'Courriels d''intervention', NULL, TRUE, 150, now(), now()),
|
||||
('SCREEN_CODE', 'EMAIL', 'Nouveau courriel', NULL, TRUE, 160, now(), now()),
|
||||
('SCREEN_CODE', 'RAPPORT', 'Rapport', NULL, TRUE, 170, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_RESOURCES', 'Contenus d''intervention', NULL, TRUE, 180, now(), now()),
|
||||
('SCREEN_CODE', 'PHOTO_VIEWER', 'Visionneuse photo', NULL, TRUE, 190, now(), now()),
|
||||
('SCREEN_CODE', 'IMAGE_EDITOR', 'Éditeur d''image', NULL, TRUE, 200, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_CONTACTS', 'Contacts d''intervention', NULL, TRUE, 210, now(), now()),
|
||||
('SCREEN_CODE', 'CONTACT_DETAIL', 'Détail de contact', NULL, TRUE, 220, now(), now()),
|
||||
('SCREEN_CODE', 'DOSSIER_DETAIL', 'Détail de dossier', NULL, TRUE, 230, now(), now()),
|
||||
('SCREEN_CODE', 'CLIENT_DETAIL', 'Détail de client', NULL, TRUE, 240, now(), now()),
|
||||
('SCREEN_CODE', 'SITE_DETAIL', 'Détail de site', NULL, TRUE, 250, now(), now()),
|
||||
('SCREEN_CODE', 'COLLABORATEUR_DETAIL', 'Détail de collaborateur', NULL, TRUE, 260, now(), now()),
|
||||
('SCREEN_CODE', 'INTERVENTION_EDIT', 'Formulaire intervention', NULL, TRUE, 270, now(), now()),
|
||||
('SCREEN_CODE', 'SUIVI_TEMPS_CREATE', 'Saisie de temps', NULL, TRUE, 280, now(), now()),
|
||||
('SCREEN_CODE', 'COMPTE_RENDU_CREATE', 'Compte rendu', NULL, TRUE, 290, now(), now()),
|
||||
('SCREEN_CODE', 'SYNC_QUEUE', 'File de synchronisation', NULL, TRUE, 300, now(), now()),
|
||||
('FEATURE_CODE', 'INTERVENTION_SEARCH_RUN', 'Recherche d''intervention', NULL, TRUE, 10, now(), now()),
|
||||
('FEATURE_CODE', 'REPORT_GENERATE', 'Génération de rapport', NULL, TRUE, 20, now(), now()),
|
||||
('FEATURE_CODE', 'EMAIL_SEND', 'Envoi de courriel', NULL, TRUE, 30, now(), now()),
|
||||
('FEATURE_CODE', 'ADD_COLLABORATEUR', 'Ajouter un collaborateur', NULL, TRUE, 1000, now(), now()),
|
||||
('FEATURE_CODE', 'ADD_DOCUMENT', 'Ajouter un document', NULL, TRUE, 1010, now(), now()),
|
||||
('FEATURE_CODE', 'ADD_NOTE', 'Ajouter une note', NULL, TRUE, 1020, now(), now()),
|
||||
('FEATURE_CODE', 'ADD_PHOTO', 'Ajouter une photo', NULL, TRUE, 1030, now(), now()),
|
||||
('FEATURE_CODE', 'ADD_PHOTO_FROM_CAMERA', 'Prendre une photo', NULL, TRUE, 1040, now(), now()),
|
||||
('FEATURE_CODE', 'ADD_TIME', 'Ajouter un temps', NULL, TRUE, 1050, now(), now()),
|
||||
('FEATURE_CODE', 'AJOUTER_DOCUMENT', 'Ajouter un document', NULL, TRUE, 1060, now(), now()),
|
||||
('FEATURE_CODE', 'AJOUTER_PHOTO', 'Ajouter une photo', NULL, TRUE, 1070, now(), now()),
|
||||
('FEATURE_CODE', 'ANNULER', 'Annuler', NULL, TRUE, 1080, now(), now()),
|
||||
('FEATURE_CODE', 'ANNULER_CONFIRMATION_ANNULATION', 'Annuler la confirmation', NULL, TRUE, 1090, now(), now()),
|
||||
('FEATURE_CODE', 'APPLY_CUSTOM_COLOR', 'Appliquer la couleur personnalisée', NULL, TRUE, 1100, now(), now()),
|
||||
('FEATURE_CODE', 'APPLY_PRIMARY_COLOR', 'Appliquer la couleur principale', NULL, TRUE, 1110, now(), now()),
|
||||
('FEATURE_CODE', 'ARCHIVE', 'Archiver', NULL, TRUE, 1120, now(), now()),
|
||||
('FEATURE_CODE', 'ASSIGN_LICENCE', 'Attribuer une licence', NULL, TRUE, 1130, now(), now()),
|
||||
('FEATURE_CODE', 'BACK', 'Retour', NULL, TRUE, 1140, now(), now()),
|
||||
('FEATURE_CODE', 'BASCULER_OPTION', 'Cocher / décocher l''option', NULL, TRUE, 1150, now(), now()),
|
||||
('FEATURE_CODE', 'BASCULER_SELECTION', 'Sélectionner la pièce jointe', NULL, TRUE, 1160, now(), now()),
|
||||
('FEATURE_CODE', 'CALL', 'Appeler', NULL, TRUE, 1170, now(), now()),
|
||||
('FEATURE_CODE', 'CALL_COLLABORATEUR', 'Appeler le collaborateur', NULL, TRUE, 1180, now(), now()),
|
||||
('FEATURE_CODE', 'CALL_CONTACT', 'Appeler le contact', NULL, TRUE, 1190, now(), now()),
|
||||
('FEATURE_CODE', 'CALL_PRIMARY', 'Appeler (contact principal)', NULL, TRUE, 1200, now(), now()),
|
||||
('FEATURE_CODE', 'CALL_SUIVI_PAR', 'Appeler (suivi par)', NULL, TRUE, 1210, now(), now()),
|
||||
('FEATURE_CODE', 'CANCEL', 'Annuler', NULL, TRUE, 1220, now(), now()),
|
||||
('FEATURE_CODE', 'CANCEL_ARCHIVE', 'Annuler l''archivage', NULL, TRUE, 1230, now(), now()),
|
||||
('FEATURE_CODE', 'CANCEL_SCAN', 'Annuler le scan', NULL, TRUE, 1240, now(), now()),
|
||||
('FEATURE_CODE', 'CLEAR_ALL_DATA', 'Vider toutes les données', NULL, TRUE, 1250, now(), now()),
|
||||
('FEATURE_CODE', 'CLEAR_DOCUMENTS', 'Vider les documents', NULL, TRUE, 1260, now(), now()),
|
||||
('FEATURE_CODE', 'CONFIRM_ARCHIVE', 'Confirmer l''archivage', NULL, TRUE, 1270, now(), now()),
|
||||
('FEATURE_CODE', 'CONFIRMER_ANNULATION', 'Confirmer l''annulation', NULL, TRUE, 1280, now(), now()),
|
||||
('FEATURE_CODE', 'CONTINUER', 'Continuer', NULL, TRUE, 1290, now(), now()),
|
||||
('FEATURE_CODE', 'DELETE_DRAFT', 'Supprimer le brouillon', NULL, TRUE, 1300, now(), now()),
|
||||
('FEATURE_CODE', 'DEMANDER_ANNULATION', 'Demander l''annulation', NULL, TRUE, 1310, now(), now()),
|
||||
('FEATURE_CODE', 'DOWNLOAD', 'Télécharger', NULL, TRUE, 1320, now(), now()),
|
||||
('FEATURE_CODE', 'DOWNLOAD_ALL_DOCUMENTS', 'Télécharger tous les documents', NULL, TRUE, 1330, now(), now()),
|
||||
('FEATURE_CODE', 'DOWNLOAD_ALL_PHOTOS', 'Télécharger toutes les photos', NULL, TRUE, 1340, now(), now()),
|
||||
('FEATURE_CODE', 'EDIT', 'Modifier', NULL, TRUE, 1350, now(), now()),
|
||||
('FEATURE_CODE', 'EDIT_DRAFT', 'Modifier le brouillon', NULL, TRUE, 1360, now(), now()),
|
||||
('FEATURE_CODE', 'EMAIL', 'Envoyer un courriel', NULL, TRUE, 1370, now(), now()),
|
||||
('FEATURE_CODE', 'EMAIL_COLLABORATEUR', 'Écrire au collaborateur', NULL, TRUE, 1380, now(), now()),
|
||||
('FEATURE_CODE', 'EMAIL_CONTACT', 'Écrire au contact', NULL, TRUE, 1390, now(), now()),
|
||||
('FEATURE_CODE', 'EMAIL_PRIMARY', 'Écrire (contact principal)', NULL, TRUE, 1400, now(), now()),
|
||||
('FEATURE_CODE', 'EMAIL_SUIVI_PAR', 'Écrire (suivi par)', NULL, TRUE, 1410, now(), now()),
|
||||
('FEATURE_CODE', 'ENREGISTRER', 'Enregistrer', NULL, TRUE, 1420, now(), now()),
|
||||
('FEATURE_CODE', 'ENROLL', 'Enrôler', NULL, TRUE, 1430, now(), now()),
|
||||
('FEATURE_CODE', 'ENVOYER', 'Envoyer', NULL, TRUE, 1440, now(), now()),
|
||||
('FEATURE_CODE', 'ENVOYER_EMAIL', 'Envoyer par courriel', NULL, TRUE, 1450, now(), now()),
|
||||
('FEATURE_CODE', 'GENERER', 'Générer', NULL, TRUE, 1460, now(), now()),
|
||||
('FEATURE_CODE', 'GO_TO_TODAY', 'Aller à aujourd''hui', NULL, TRUE, 1470, now(), now()),
|
||||
('FEATURE_CODE', 'HIDE_EDITOR', 'Masquer l''éditeur', NULL, TRUE, 1480, now(), now()),
|
||||
('FEATURE_CODE', 'INITIALIZE', 'Initialiser', NULL, TRUE, 1490, now(), now()),
|
||||
('FEATURE_CODE', 'LOAD', 'Charger', NULL, TRUE, 1500, now(), now()),
|
||||
('FEATURE_CODE', 'LOAD_AVEC_DESTINATAIRE', 'Charger avec destinataire', NULL, TRUE, 1510, now(), now()),
|
||||
('FEATURE_CODE', 'LOAD_AVEC_RAPPORT', 'Charger avec rapport', NULL, TRUE, 1520, now(), now()),
|
||||
('FEATURE_CODE', 'LOAD_EDITION', 'Charger en édition', NULL, TRUE, 1530, now(), now()),
|
||||
('FEATURE_CODE', 'LOAD_SCOPES', 'Charger les périmètres', NULL, TRUE, 1540, now(), now()),
|
||||
('FEATURE_CODE', 'LOGOUT', 'Se déconnecter', NULL, TRUE, 1550, now(), now()),
|
||||
('FEATURE_CODE', 'MODIFIER', 'Modifier', NULL, TRUE, 1560, now(), now()),
|
||||
('FEATURE_CODE', 'NAVIGATE', 'Naviguer', NULL, TRUE, 1570, now(), now()),
|
||||
('FEATURE_CODE', 'NEW_INTERVENTION', 'Nouvelle intervention', NULL, TRUE, 1580, now(), now()),
|
||||
('FEATURE_CODE', 'NEXT', 'Suivant', NULL, TRUE, 1590, now(), now()),
|
||||
('FEATURE_CODE', 'NEXT_DAY', 'Jour suivant', NULL, TRUE, 1600, now(), now()),
|
||||
('FEATURE_CODE', 'NEXT_WEEK', 'Semaine suivante', NULL, TRUE, 1610, now(), now()),
|
||||
('FEATURE_CODE', 'NOUVEAU', 'Nouveau', NULL, TRUE, 1620, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN', 'Ouvrir', NULL, TRUE, 1630, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_CLIENT', 'Ouvrir le client', NULL, TRUE, 1640, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_COLLABORATEUR', 'Ouvrir le collaborateur', NULL, TRUE, 1650, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_COMPTE_RENDU', 'Ouvrir le compte rendu', NULL, TRUE, 1660, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_CONTACT', 'Ouvrir le contact', NULL, TRUE, 1670, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_CONTACTS', 'Ouvrir les contacts', NULL, TRUE, 1680, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_DOCUMENTS', 'Ouvrir les documents', NULL, TRUE, 1690, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_DOSSIER', 'Ouvrir le dossier', NULL, TRUE, 1700, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_EMAILS', 'Ouvrir les courriels', NULL, TRUE, 1710, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_EQUIPE', 'Ouvrir l''équipe', NULL, TRUE, 1720, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_FIRST_RENDEZ_VOUS', 'Ouvrir le premier rendez-vous', NULL, TRUE, 1730, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_IMAGES', 'Ouvrir les images', NULL, TRUE, 1740, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_IMAGES_ET_DOCUMENTS', 'Ouvrir images et documents', NULL, TRUE, 1750, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_INTERVENTION', 'Ouvrir l''intervention', NULL, TRUE, 1760, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_ITINERAIRE', 'Ouvrir l''itinéraire', NULL, TRUE, 1770, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_NOTES', 'Ouvrir les notes', NULL, TRUE, 1780, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_NOTES_ADMINISTRATIVES', 'Ouvrir les notes administratives', NULL, TRUE, 1790, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_RAPPORT', 'Ouvrir le rapport', NULL, TRUE, 1800, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_RENDEZ_VOUS', 'Ouvrir le rendez-vous', NULL, TRUE, 1810, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_SITE', 'Ouvrir le site', NULL, TRUE, 1820, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_SYNC_QUEUE', 'Ouvrir la file de synchronisation', NULL, TRUE, 1830, now(), now()),
|
||||
('FEATURE_CODE', 'OPEN_TEAM_AGENDA', 'Ouvrir l''agenda d''équipe', NULL, TRUE, 1840, now(), now()),
|
||||
('FEATURE_CODE', 'OUVRIR_BROUILLON', 'Ouvrir le brouillon', NULL, TRUE, 1850, now(), now()),
|
||||
('FEATURE_CODE', 'OUVRIR_RUBRIQUE', 'Ouvrir une rubrique', NULL, TRUE, 1860, now(), now()),
|
||||
('FEATURE_CODE', 'PREVIOUS', 'Précédent', NULL, TRUE, 1870, now(), now()),
|
||||
('FEATURE_CODE', 'PREVIOUS_DAY', 'Jour précédent', NULL, TRUE, 1880, now(), now()),
|
||||
('FEATURE_CODE', 'PREVIOUS_WEEK', 'Semaine précédente', NULL, TRUE, 1890, now(), now()),
|
||||
('FEATURE_CODE', 'RELEASE_LICENCE', 'Libérer la licence', NULL, TRUE, 1900, now(), now()),
|
||||
('FEATURE_CODE', 'REMOVE_COLLABORATEUR', 'Retirer un collaborateur', NULL, TRUE, 1910, now(), now()),
|
||||
('FEATURE_CODE', 'RESET_APP', 'Réinitialiser l''application', NULL, TRUE, 1920, now(), now()),
|
||||
('FEATURE_CODE', 'RESET_FILTERS', 'Réinitialiser les filtres', NULL, TRUE, 1930, now(), now()),
|
||||
('FEATURE_CODE', 'RESET_SEARCH', 'Réinitialiser la recherche', NULL, TRUE, 1940, now(), now()),
|
||||
('FEATURE_CODE', 'RETOUR', 'Retour', NULL, TRUE, 1950, now(), now()),
|
||||
('FEATURE_CODE', 'RETOUR_RUBRIQUES', 'Retour aux rubriques', NULL, TRUE, 1960, now(), now()),
|
||||
('FEATURE_CODE', 'RETRY', 'Réessayer', NULL, TRUE, 1970, now(), now()),
|
||||
('FEATURE_CODE', 'RUBRIQUE_PRECEDENTE', 'Rubrique précédente', NULL, TRUE, 1980, now(), now()),
|
||||
('FEATURE_CODE', 'RUBRIQUE_SUIVANTE', 'Rubrique suivante', NULL, TRUE, 1990, now(), now()),
|
||||
('FEATURE_CODE', 'SAVE', 'Enregistrer', NULL, TRUE, 2000, now(), now()),
|
||||
('FEATURE_CODE', 'SAVE_EDITOR', 'Enregistrer l''éditeur', NULL, TRUE, 2010, now(), now()),
|
||||
('FEATURE_CODE', 'SAVE_PAUSE_DEJEUNER', 'Enregistrer la pause déjeuner', NULL, TRUE, 2020, now(), now()),
|
||||
('FEATURE_CODE', 'SCAN', 'Scanner', NULL, TRUE, 2030, now(), now()),
|
||||
('FEATURE_CODE', 'SEARCH', 'Rechercher', NULL, TRUE, 2040, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_CALENDAR_DAY', 'Sélectionner un jour', NULL, TRUE, 2050, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_CLIENTS', 'Onglet Clients', NULL, TRUE, 2060, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_CONTACTS', 'Onglet Contacts', NULL, TRUE, 2070, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_DAY', 'Sélectionner le jour', NULL, TRUE, 2080, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_DOSSIERS', 'Onglet Dossiers', NULL, TRUE, 2090, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_INTERVENTIONS', 'Onglet Interventions', NULL, TRUE, 2100, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_RENDEZ_VOUS', 'Onglet Rendez-vous', NULL, TRUE, 2110, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_SITES', 'Onglet Sites', NULL, TRUE, 2120, now(), now()),
|
||||
('FEATURE_CODE', 'SELECT_TAB', 'Changer d''onglet', NULL, TRUE, 2130, now(), now()),
|
||||
('FEATURE_CODE', 'SEND_SUPPORT_EMAIL', 'Écrire au support', NULL, TRUE, 2140, now(), now()),
|
||||
('FEATURE_CODE', 'SET_PRIMARY_COLOR', 'Changer la couleur principale', NULL, TRUE, 2150, now(), now()),
|
||||
('FEATURE_CODE', 'SET_THEME_MODE', 'Changer le thème', NULL, TRUE, 2160, now(), now()),
|
||||
('FEATURE_CODE', 'SHOW_CALENDAR', 'Afficher le calendrier', NULL, TRUE, 2170, now(), now()),
|
||||
('FEATURE_CODE', 'SHOW_DAY', 'Afficher le jour', NULL, TRUE, 2180, now(), now()),
|
||||
('FEATURE_CODE', 'SHOW_PINNED', 'Afficher les épinglés', NULL, TRUE, 2190, now(), now()),
|
||||
('FEATURE_CODE', 'SHOW_RECENT', 'Afficher les récents', NULL, TRUE, 2200, now(), now()),
|
||||
('FEATURE_CODE', 'SHOW_WEEK', 'Afficher la semaine', NULL, TRUE, 2210, now(), now()),
|
||||
('FEATURE_CODE', 'SYNC_NOW', 'Synchroniser maintenant', NULL, TRUE, 2220, now(), now()),
|
||||
('FEATURE_CODE', 'TOGGLE_PINNED', 'Épingler / désépingler', NULL, TRUE, 2230, now(), now()),
|
||||
('FEATURE_CODE', 'VALIDER', 'Valider', NULL, TRUE, 2240, now(), now())
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- =====================================================================
|
||||
-- FIN. Vérification :
|
||||
-- SELECT reference_type, COUNT(*) FROM diagnostics_reference GROUP BY reference_type;
|
||||
-- =====================================================================
|
||||
@@ -0,0 +1,112 @@
|
||||
# ============================================================
|
||||
# Omika Diagnostics — Publication des composants serveur (poste DEV)
|
||||
# ============================================================
|
||||
# Usage (depuis le poste de développement, SDK .NET 10 installé) :
|
||||
# .\scripts\publish-diagnostics.ps1
|
||||
# .\scripts\publish-diagnostics.ps1 -OutputRoot D:\Omika\Deploy
|
||||
# .\scripts\publish-diagnostics.ps1 -IncludeCentralApi # ajoute Omika.Diagnostics.Api
|
||||
# .\scripts\publish-diagnostics.ps1 -SkipDevelopmentJsonCheck
|
||||
#
|
||||
# Produit, pour chaque composant, un dossier "<Nom>-next" contenant la
|
||||
# publication Release win-x64 framework-dependent, et vérifie la présence de
|
||||
# l'exécutable attendu (stop en cas d'absence).
|
||||
#
|
||||
# Les dossiers produits sont ensuite transférés vers SRV-OMIKA puis copiés avec
|
||||
# robocopy /E /XF appsettings.json appsettings.Production.json (voir
|
||||
# docs/MISE_A_JOUR_DIAGNOSTICS_SRV-OMIKA.md).
|
||||
#
|
||||
# Ne fait AUCUNE copie vers le serveur, ne touche à AUCUN secret.
|
||||
# ============================================================
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$OutputRoot = 'C:\Omika\Deploy',
|
||||
[switch]$IncludeCentralApi,
|
||||
[switch]$SkipDevelopmentJsonCheck
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Racine du dépôt = parent du dossier scripts\.
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
$components = @(
|
||||
@{
|
||||
Name = 'OmikaApi'
|
||||
Proj = 'src\Omika.Api\Omika.Api.csproj'
|
||||
Exe = 'Omika.Api.exe'
|
||||
},
|
||||
@{
|
||||
Name = 'OmikaWorker'
|
||||
Proj = 'src\Omika.Worker\Omika.Worker.csproj'
|
||||
Exe = 'Omika.Worker.exe'
|
||||
},
|
||||
@{
|
||||
Name = 'DiagnosticsWorker'
|
||||
Proj = 'src\Omika.Diagnostics.Worker\Omika.Diagnostics.Worker.csproj'
|
||||
Exe = 'Omika.Diagnostics.Worker.exe'
|
||||
},
|
||||
@{
|
||||
Name = 'DiagnosticsWeb'
|
||||
Proj = 'src\Omika.Diagnostics.Web\Omika.Diagnostics.Web.csproj'
|
||||
Exe = 'Omika.Diagnostics.Web.exe'
|
||||
}
|
||||
)
|
||||
|
||||
if ($IncludeCentralApi) {
|
||||
$components += @{
|
||||
Name = 'DiagnosticsApi'
|
||||
Proj = 'src\Omika.Diagnostics.Api\Omika.Diagnostics.Api.csproj'
|
||||
Exe = 'Omika.Diagnostics.Api.exe'
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host '== Publication Omika Diagnostics (Release win-x64, framework-dependent) ==' -ForegroundColor Cyan
|
||||
|
||||
foreach ($c in $components) {
|
||||
$out = Join-Path $OutputRoot ($c.Name + '-next')
|
||||
$proj = Join-Path $RepoRoot $c.Proj
|
||||
|
||||
if (-not (Test-Path -LiteralPath $proj)) {
|
||||
throw "Projet introuvable : $proj"
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host "== $($c.Name) -> $out ==" -ForegroundColor Cyan
|
||||
|
||||
# -r win-x64 --self-contained false : framework-dependent (runtime .NET 10 requis
|
||||
# sur le serveur). Le Worker conserve EnableWindowsService=true (défaut) : ne pas
|
||||
# passer -p:EnableWindowsService=false pour un publish Release+RID.
|
||||
dotnet publish $proj `
|
||||
-c Release `
|
||||
-r win-x64 `
|
||||
--self-contained false `
|
||||
-o $out
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "dotnet publish a échoué (exit $LASTEXITCODE) sur $($c.Name)."
|
||||
}
|
||||
|
||||
$exe = Join-Path $out $c.Exe
|
||||
if (-not (Test-Path -LiteralPath $exe)) {
|
||||
throw "Exécutable attendu absent : $exe"
|
||||
}
|
||||
Write-Host " OK : $exe" -ForegroundColor Green
|
||||
|
||||
if (-not $SkipDevelopmentJsonCheck) {
|
||||
$devJson = Join-Path $out 'appsettings.Development.json'
|
||||
if (Test-Path -LiteralPath $devJson) {
|
||||
Write-Warning " appsettings.Development.json présent dans la publication ($($c.Name)) : ne pas l'activer en Production (ASPNETCORE_ENVIRONMENT doit rester 'Production')."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host 'Publication terminée.' -ForegroundColor Green
|
||||
Write-Host "Dossiers produits (à transférer vers SRV-OMIKA C:\Omika\Deploy\) :"
|
||||
foreach ($c in $components) {
|
||||
Write-Host " $((Join-Path $OutputRoot ($c.Name + '-next')))"
|
||||
}
|
||||
Write-Host ''
|
||||
Write-Host 'Rappel copie serveur (après backup + arrêt) :' -ForegroundColor Yellow
|
||||
Write-Host " robocopy '<dossier>-next' 'C:\Omika\Apps\<composant>' /E /XF appsettings.json appsettings.Production.json"
|
||||
@@ -0,0 +1,461 @@
|
||||
# ============================================================
|
||||
# Omika Diagnostic Center — Seed du référentiel COMPLET
|
||||
# seed-referentiel-complet.ps1
|
||||
# ============================================================
|
||||
# Génère et (optionnellement) applique le référentiel administrable
|
||||
# diagnostics_reference (T24-A) pour :
|
||||
# * les 30 ScreenCode du catalogue applicatif (fenêtres / écrans) ;
|
||||
# * les 3 FeatureCode « action métier » historiques ;
|
||||
# * TOUS les FeatureCode de boutons, dérivés AUTOMATIQUEMENT depuis les
|
||||
# méthodes [RelayCommand] des ViewModels (src\Omika.App\ViewModels).
|
||||
#
|
||||
# PRINCIPE : le code de chaque bouton est dérivé du NOM de la méthode
|
||||
# (même algorithme que DiagnosticFeatureCodes.FromCommandName) puis un
|
||||
# libellé français lui est associé. Le script SCANNE le code source :
|
||||
# re-exécutable après ajout d'un bouton, il reste toujours à jour.
|
||||
#
|
||||
# IDEMPOTENT : INSERT ... ON CONFLICT DO NOTHING. Un libellé déjà modifié
|
||||
# par un Admin (via Diagnostics.Web /Referentiel/Admin) n'est JAMAIS écrasé.
|
||||
# NON DESTRUCTIF : aucun DROP, aucun DELETE, aucune mise à jour.
|
||||
#
|
||||
# Usage :
|
||||
# .\scripts\seed-referentiel-complet.ps1 # génère le .sql uniquement
|
||||
# .\scripts\seed-referentiel-complet.ps1 -Apply # génère + applique via psql
|
||||
# .\scripts\seed-referentiel-complet.ps1 -Apply -Database omika_diagnostics_preprod
|
||||
# .\scripts\seed-referentiel-complet.ps1 -Apply -Server SRV-OMIKA -Username omika_diagnostics_web
|
||||
# .\scripts\seed-referentiel-complet.ps1 -Apply -Force # sans demande de confirmation
|
||||
#
|
||||
# Remarques :
|
||||
# - Nécessite psql (PostgreSQL) uniquement pour -Apply.
|
||||
# - Rôle de connexion : 'postgres' (défaut, superutilisateur, comme 011) ou
|
||||
# 'omika_diagnostics_web' (moindre privilège : INSERT accordé par 011).
|
||||
# 'omika_diagnostics_owner' est NOLOGIN et ne peut PAS servir ici.
|
||||
# - Le mot de passe n'est JAMAIS en clair : saisi en mode sécurisé, ou
|
||||
# fourni via la variable d'environnement PGPASSWORD (jamais en paramètre).
|
||||
# - Les codes de boutons dont le libellé n'est pas encore dans la table
|
||||
# interne $featureLabels sont signalés (libellé de repli « humanisé »).
|
||||
# ============================================================
|
||||
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Database = 'omika_diagnostics',
|
||||
[string]$Server = 'localhost',
|
||||
[int]$Port = 5432,
|
||||
[string]$Username = 'postgres',
|
||||
[string]$PsqlPath = '',
|
||||
[string]$OutputSql = '',
|
||||
[switch]$Apply,
|
||||
[switch]$Force
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
# Racine du dépôt = parent du dossier scripts\.
|
||||
$RepoRoot = Split-Path -Parent $PSScriptRoot
|
||||
$ViewModelsDir = Join-Path $RepoRoot 'src\Omika.App\ViewModels'
|
||||
|
||||
if (-not (Test-Path -LiteralPath $ViewModelsDir)) {
|
||||
throw "Dossier ViewModels introuvable : $ViewModelsDir"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($OutputSql)) {
|
||||
$OutputSql = Join-Path $RepoRoot 'scripts\dev\seed_referentiel_complet.sql'
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 1. Portage EXACT de DiagnosticFeatureCodes.FromCommandName
|
||||
# (retire le suffixe Async, PascalCase -> UPPER_SNAKE_CASE).
|
||||
# ============================================================
|
||||
function ConvertTo-FeatureCode {
|
||||
param([string]$MethodName)
|
||||
|
||||
$v = $MethodName.Trim()
|
||||
if ([string]::IsNullOrWhiteSpace($v)) { return 'ACTION' }
|
||||
if ($v.EndsWith('Async', [System.StringComparison]::Ordinal)) {
|
||||
$v = $v.Substring(0, $v.Length - 5)
|
||||
}
|
||||
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
for ($i = 0; $i -lt $v.Length; $i++) {
|
||||
$c = $v[$i]
|
||||
if ([char]::IsUpper($c)) {
|
||||
$prevLower = ($i -gt 0) -and [char]::IsLower($v[$i - 1])
|
||||
$prevUpper = ($i -gt 0) -and [char]::IsUpper($v[$i - 1])
|
||||
$nextLower = ($i + 1 -lt $v.Length) -and [char]::IsLower($v[$i + 1])
|
||||
if (($prevLower -or ($prevUpper -and $nextLower)) -and $sb.Length -gt 0 -and $sb[$sb.Length - 1] -ne '_') {
|
||||
[void]$sb.Append('_')
|
||||
}
|
||||
[void]$sb.Append([char]::ToUpperInvariant($c))
|
||||
}
|
||||
elseif ($c -eq '_' -or $c -eq '-' -or $c -eq ' ') {
|
||||
if ($sb.Length -gt 0 -and $sb[$sb.Length - 1] -ne '_') {
|
||||
[void]$sb.Append('_')
|
||||
}
|
||||
}
|
||||
else {
|
||||
[void]$sb.Append([char]::ToUpperInvariant($c))
|
||||
}
|
||||
}
|
||||
|
||||
if ($sb.Length -eq 0) { return 'ACTION' }
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 2. Extraction des noms de méthodes [RelayCommand].
|
||||
# ============================================================
|
||||
function Get-RelayCommandMethodNames {
|
||||
param([string]$Directory)
|
||||
|
||||
$names = New-Object System.Collections.Generic.List[string]
|
||||
$sigPattern = '^\s*(?:public|private|internal|protected)?\s*(?:async\s+)?(?:Task(?:<[^>]+>)?|void|bool|[A-Za-z_][A-Za-z0-9_<>,\.\[\]\? ]*)\s+([A-Za-z_][A-Za-z0-9_]*)\s*\('
|
||||
|
||||
foreach ($file in (Get-ChildItem -Path $Directory -Filter *.cs | Sort-Object Name)) {
|
||||
$lines = Get-Content -Path $file.FullName
|
||||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||||
if ($lines[$i] -match '\[RelayCommand') {
|
||||
for ($j = $i + 1; $j -lt [Math]::Min($i + 8, $lines.Count); $j++) {
|
||||
if ($lines[$j] -match $sigPattern) {
|
||||
$names.Add($Matches[1])
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $names
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 3. Catalogue des 30 ScreenCode (labels = DiagnosticScreenCodeCatalog).
|
||||
# ============================================================
|
||||
$screenCodes = @(
|
||||
[pscustomobject]@{ Code = 'AGENDA'; Label = 'Agenda' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_SEARCH'; Label = 'Recherche d''interventions' },
|
||||
[pscustomobject]@{ Code = 'DOSSIER_LIST'; Label = 'Dossiers' },
|
||||
[pscustomobject]@{ Code = 'TEAM'; Label = 'Équipe' },
|
||||
[pscustomobject]@{ Code = 'TEAM_DETAIL'; Label = 'Détail d''équipe' },
|
||||
[pscustomobject]@{ Code = 'CLIENT_LIST'; Label = 'Clients' },
|
||||
[pscustomobject]@{ Code = 'SITE_LIST'; Label = 'Sites' },
|
||||
[pscustomobject]@{ Code = 'SETTINGS'; Label = 'Paramètres' },
|
||||
[pscustomobject]@{ Code = 'RENDEZVOUS_DETAIL'; Label = 'Détail de rendez-vous' },
|
||||
[pscustomobject]@{ Code = 'AGENDA_TEAM'; Label = 'Agenda d''équipe' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_DETAIL'; Label = 'Détail d''intervention' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_DOCUMENTS'; Label = 'Documents d''intervention' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_NOTES'; Label = 'Notes d''intervention' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_NOTES_ADMIN'; Label = 'Notes administratives' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_EMAILS'; Label = 'Courriels d''intervention' },
|
||||
[pscustomobject]@{ Code = 'EMAIL'; Label = 'Nouveau courriel' },
|
||||
[pscustomobject]@{ Code = 'RAPPORT'; Label = 'Rapport' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_RESOURCES'; Label = 'Contenus d''intervention' },
|
||||
[pscustomobject]@{ Code = 'PHOTO_VIEWER'; Label = 'Visionneuse photo' },
|
||||
[pscustomobject]@{ Code = 'IMAGE_EDITOR'; Label = 'Éditeur d''image' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_CONTACTS'; Label = 'Contacts d''intervention' },
|
||||
[pscustomobject]@{ Code = 'CONTACT_DETAIL'; Label = 'Détail de contact' },
|
||||
[pscustomobject]@{ Code = 'DOSSIER_DETAIL'; Label = 'Détail de dossier' },
|
||||
[pscustomobject]@{ Code = 'CLIENT_DETAIL'; Label = 'Détail de client' },
|
||||
[pscustomobject]@{ Code = 'SITE_DETAIL'; Label = 'Détail de site' },
|
||||
[pscustomobject]@{ Code = 'COLLABORATEUR_DETAIL'; Label = 'Détail de collaborateur' },
|
||||
[pscustomobject]@{ Code = 'INTERVENTION_EDIT'; Label = 'Formulaire intervention' },
|
||||
[pscustomobject]@{ Code = 'SUIVI_TEMPS_CREATE'; Label = 'Saisie de temps' },
|
||||
[pscustomobject]@{ Code = 'COMPTE_RENDU_CREATE'; Label = 'Compte rendu' },
|
||||
[pscustomobject]@{ Code = 'SYNC_QUEUE'; Label = 'File de synchronisation' }
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# 4. Libellés français des FeatureCode (boutons + 3 actions métier).
|
||||
# Clé = FeatureCode dérivé ; valeur = libellé métier français.
|
||||
# ============================================================
|
||||
$semanticFeatureOrder = @('INTERVENTION_SEARCH_RUN', 'REPORT_GENERATE', 'EMAIL_SEND')
|
||||
|
||||
$featureLabels = @{
|
||||
# --- Actions métier historiques (semantic success events) ---
|
||||
'INTERVENTION_SEARCH_RUN' = 'Recherche d''intervention'
|
||||
'REPORT_GENERATE' = 'Génération de rapport'
|
||||
'EMAIL_SEND' = 'Envoi de courriel'
|
||||
|
||||
# --- Boutons (dérivés des commandes [RelayCommand]) ---
|
||||
'ADD_COLLABORATEUR' = 'Ajouter un collaborateur'
|
||||
'ADD_DOCUMENT' = 'Ajouter un document'
|
||||
'ADD_NOTE' = 'Ajouter une note'
|
||||
'ADD_PHOTO' = 'Ajouter une photo'
|
||||
'ADD_PHOTO_FROM_CAMERA' = 'Prendre une photo'
|
||||
'ADD_TIME' = 'Ajouter un temps'
|
||||
'AJOUTER_DOCUMENT' = 'Ajouter un document'
|
||||
'AJOUTER_PHOTO' = 'Ajouter une photo'
|
||||
'ANNULER' = 'Annuler'
|
||||
'ANNULER_CONFIRMATION_ANNULATION' = 'Annuler la confirmation'
|
||||
'APPLY_CUSTOM_COLOR' = 'Appliquer la couleur personnalisée'
|
||||
'APPLY_PRIMARY_COLOR' = 'Appliquer la couleur principale'
|
||||
'ARCHIVE' = 'Archiver'
|
||||
'ASSIGN_LICENCE' = 'Attribuer une licence'
|
||||
'BACK' = 'Retour'
|
||||
'BASCULER_OPTION' = 'Cocher / décocher l''option'
|
||||
'BASCULER_SELECTION' = 'Sélectionner la pièce jointe'
|
||||
'CALL' = 'Appeler'
|
||||
'CALL_COLLABORATEUR' = 'Appeler le collaborateur'
|
||||
'CALL_CONTACT' = 'Appeler le contact'
|
||||
'CALL_PRIMARY' = 'Appeler (contact principal)'
|
||||
'CALL_SUIVI_PAR' = 'Appeler (suivi par)'
|
||||
'CANCEL' = 'Annuler'
|
||||
'CANCEL_ARCHIVE' = 'Annuler l''archivage'
|
||||
'CANCEL_SCAN' = 'Annuler le scan'
|
||||
'CLEAR_ALL_DATA' = 'Vider toutes les données'
|
||||
'CLEAR_DOCUMENTS' = 'Vider les documents'
|
||||
'CONFIRM_ARCHIVE' = 'Confirmer l''archivage'
|
||||
'CONFIRMER_ANNULATION' = 'Confirmer l''annulation'
|
||||
'CONTINUER' = 'Continuer'
|
||||
'DELETE_DRAFT' = 'Supprimer le brouillon'
|
||||
'DEMANDER_ANNULATION' = 'Demander l''annulation'
|
||||
'DOWNLOAD' = 'Télécharger'
|
||||
'DOWNLOAD_ALL_DOCUMENTS' = 'Télécharger tous les documents'
|
||||
'DOWNLOAD_ALL_PHOTOS' = 'Télécharger toutes les photos'
|
||||
'EDIT' = 'Modifier'
|
||||
'EDIT_DRAFT' = 'Modifier le brouillon'
|
||||
'EMAIL' = 'Envoyer un courriel'
|
||||
'EMAIL_COLLABORATEUR' = 'Écrire au collaborateur'
|
||||
'EMAIL_CONTACT' = 'Écrire au contact'
|
||||
'EMAIL_PRIMARY' = 'Écrire (contact principal)'
|
||||
'EMAIL_SUIVI_PAR' = 'Écrire (suivi par)'
|
||||
'ENREGISTRER' = 'Enregistrer'
|
||||
'ENROLL' = 'Enrôler'
|
||||
'ENVOYER' = 'Envoyer'
|
||||
'ENVOYER_EMAIL' = 'Envoyer par courriel'
|
||||
'GENERER' = 'Générer'
|
||||
'GO_TO_TODAY' = 'Aller à aujourd''hui'
|
||||
'HIDE_EDITOR' = 'Masquer l''éditeur'
|
||||
'INITIALIZE' = 'Initialiser'
|
||||
'LOAD' = 'Charger'
|
||||
'LOAD_AVEC_DESTINATAIRE' = 'Charger avec destinataire'
|
||||
'LOAD_AVEC_RAPPORT' = 'Charger avec rapport'
|
||||
'LOAD_EDITION' = 'Charger en édition'
|
||||
'LOAD_SCOPES' = 'Charger les périmètres'
|
||||
'LOGOUT' = 'Se déconnecter'
|
||||
'MODIFIER' = 'Modifier'
|
||||
'NAVIGATE' = 'Naviguer'
|
||||
'NEW_INTERVENTION' = 'Nouvelle intervention'
|
||||
'NEXT' = 'Suivant'
|
||||
'NEXT_DAY' = 'Jour suivant'
|
||||
'NEXT_WEEK' = 'Semaine suivante'
|
||||
'NOUVEAU' = 'Nouveau'
|
||||
'OPEN' = 'Ouvrir'
|
||||
'OPEN_CLIENT' = 'Ouvrir le client'
|
||||
'OPEN_COLLABORATEUR' = 'Ouvrir le collaborateur'
|
||||
'OPEN_COMPTE_RENDU' = 'Ouvrir le compte rendu'
|
||||
'OPEN_CONTACT' = 'Ouvrir le contact'
|
||||
'OPEN_CONTACTS' = 'Ouvrir les contacts'
|
||||
'OPEN_DOCUMENTS' = 'Ouvrir les documents'
|
||||
'OPEN_DOSSIER' = 'Ouvrir le dossier'
|
||||
'OPEN_EMAILS' = 'Ouvrir les courriels'
|
||||
'OPEN_EQUIPE' = 'Ouvrir l''équipe'
|
||||
'OPEN_FIRST_RENDEZ_VOUS' = 'Ouvrir le premier rendez-vous'
|
||||
'OPEN_IMAGES' = 'Ouvrir les images'
|
||||
'OPEN_IMAGES_ET_DOCUMENTS' = 'Ouvrir images et documents'
|
||||
'OPEN_INTERVENTION' = 'Ouvrir l''intervention'
|
||||
'OPEN_ITINERAIRE' = 'Ouvrir l''itinéraire'
|
||||
'OPEN_NOTES' = 'Ouvrir les notes'
|
||||
'OPEN_NOTES_ADMINISTRATIVES' = 'Ouvrir les notes administratives'
|
||||
'OPEN_RAPPORT' = 'Ouvrir le rapport'
|
||||
'OPEN_RENDEZ_VOUS' = 'Ouvrir le rendez-vous'
|
||||
'OPEN_SITE' = 'Ouvrir le site'
|
||||
'OPEN_SYNC_QUEUE' = 'Ouvrir la file de synchronisation'
|
||||
'OPEN_TEAM_AGENDA' = 'Ouvrir l''agenda d''équipe'
|
||||
'OUVRIR_BROUILLON' = 'Ouvrir le brouillon'
|
||||
'OUVRIR_RUBRIQUE' = 'Ouvrir une rubrique'
|
||||
'PREVIOUS' = 'Précédent'
|
||||
'PREVIOUS_DAY' = 'Jour précédent'
|
||||
'PREVIOUS_WEEK' = 'Semaine précédente'
|
||||
'RELEASE_LICENCE' = 'Libérer la licence'
|
||||
'REMOVE_COLLABORATEUR' = 'Retirer un collaborateur'
|
||||
'RESET_APP' = 'Réinitialiser l''application'
|
||||
'RESET_FILTERS' = 'Réinitialiser les filtres'
|
||||
'RESET_SEARCH' = 'Réinitialiser la recherche'
|
||||
'RETOUR' = 'Retour'
|
||||
'RETOUR_RUBRIQUES' = 'Retour aux rubriques'
|
||||
'RETRY' = 'Réessayer'
|
||||
'RUBRIQUE_PRECEDENTE' = 'Rubrique précédente'
|
||||
'RUBRIQUE_SUIVANTE' = 'Rubrique suivante'
|
||||
'SAVE' = 'Enregistrer'
|
||||
'SAVE_EDITOR' = 'Enregistrer l''éditeur'
|
||||
'SAVE_PAUSE_DEJEUNER' = 'Enregistrer la pause déjeuner'
|
||||
'SCAN' = 'Scanner'
|
||||
'SEARCH' = 'Rechercher'
|
||||
'SELECT_CALENDAR_DAY' = 'Sélectionner un jour'
|
||||
'SELECT_CLIENTS' = 'Onglet Clients'
|
||||
'SELECT_CONTACTS' = 'Onglet Contacts'
|
||||
'SELECT_DAY' = 'Sélectionner le jour'
|
||||
'SELECT_DOSSIERS' = 'Onglet Dossiers'
|
||||
'SELECT_INTERVENTIONS' = 'Onglet Interventions'
|
||||
'SELECT_RENDEZ_VOUS' = 'Onglet Rendez-vous'
|
||||
'SELECT_SITES' = 'Onglet Sites'
|
||||
'SELECT_TAB' = 'Changer d''onglet'
|
||||
'SEND_SUPPORT_EMAIL' = 'Écrire au support'
|
||||
'SET_PRIMARY_COLOR' = 'Changer la couleur principale'
|
||||
'SET_THEME_MODE' = 'Changer le thème'
|
||||
'SHOW_CALENDAR' = 'Afficher le calendrier'
|
||||
'SHOW_DAY' = 'Afficher le jour'
|
||||
'SHOW_PINNED' = 'Afficher les épinglés'
|
||||
'SHOW_RECENT' = 'Afficher les récents'
|
||||
'SHOW_WEEK' = 'Afficher la semaine'
|
||||
'SYNC_NOW' = 'Synchroniser maintenant'
|
||||
'TOGGLE_PINNED' = 'Épingler / désépingler'
|
||||
'VALIDER' = 'Valider'
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 5. Dérivation des boutons depuis le code source + contrôle de
|
||||
# couverture (un code non mappé => libellé de repli + avertissement).
|
||||
# ============================================================
|
||||
function ConvertTo-HumanizedLabel {
|
||||
param([string]$Code)
|
||||
$words = $Code -split '_'
|
||||
$parts = foreach ($w in $words) {
|
||||
if ($w.Length -eq 0) { continue }
|
||||
if ($w.Length -eq 1) { $w.ToUpperInvariant() }
|
||||
else { $w.Substring(0, 1).ToUpperInvariant() + $w.Substring(1).ToLowerInvariant() }
|
||||
}
|
||||
return ($parts -join ' ')
|
||||
}
|
||||
|
||||
$methodNames = Get-RelayCommandMethodNames -Directory $ViewModelsDir
|
||||
$buttonCodes = $methodNames | ForEach-Object { ConvertTo-FeatureCode $_ } | Sort-Object -Unique
|
||||
|
||||
$unmapped = @()
|
||||
foreach ($code in $buttonCodes) {
|
||||
if (-not $featureLabels.ContainsKey($code)) {
|
||||
$unmapped += $code
|
||||
$featureLabels[$code] = ConvertTo-HumanizedLabel -Code $code
|
||||
}
|
||||
}
|
||||
|
||||
if ($unmapped.Count -gt 0) {
|
||||
Write-Warning "Codes de boutons sans libellé explicite (libellé humanisé appliqué) : $($unmapped -join ', ')"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 6. Génération du SQL (INSERT ... ON CONFLICT DO NOTHING).
|
||||
# ============================================================
|
||||
function ConvertTo-SqlLiteral {
|
||||
param([string]$Value)
|
||||
return "'" + ($Value -replace "'", "''") + "'"
|
||||
}
|
||||
|
||||
$rows = New-Object System.Collections.Generic.List[string]
|
||||
|
||||
# ScreenCodes : sort_order 10..290 (pas de 10).
|
||||
$screenSort = 10
|
||||
foreach ($s in $screenCodes) {
|
||||
$rows.Add(" ('SCREEN_CODE', $(ConvertTo-SqlLiteral $s.Code), $(ConvertTo-SqlLiteral $s.Label), NULL, TRUE, $screenSort, now(), now())")
|
||||
$screenSort += 10
|
||||
}
|
||||
|
||||
# FeatureCode « actions métier » : sort_order 10/20/30 (aligné 011).
|
||||
$semanticSort = 10
|
||||
foreach ($code in $semanticFeatureOrder) {
|
||||
if ($featureLabels.ContainsKey($code)) {
|
||||
$rows.Add(" ('FEATURE_CODE', $(ConvertTo-SqlLiteral $code), $(ConvertTo-SqlLiteral $featureLabels[$code]), NULL, TRUE, $semanticSort, now(), now())")
|
||||
}
|
||||
$semanticSort += 10
|
||||
}
|
||||
|
||||
# FeatureCode « boutons » : tri alphabétique, sort_order 1000, 1010, ...
|
||||
$buttonSort = 1000
|
||||
foreach ($code in ($buttonCodes | Sort-Object)) {
|
||||
$rows.Add(" ('FEATURE_CODE', $(ConvertTo-SqlLiteral $code), $(ConvertTo-SqlLiteral $featureLabels[$code]), NULL, TRUE, $buttonSort, now(), now())")
|
||||
$buttonSort += 10
|
||||
}
|
||||
|
||||
$sql = @"
|
||||
-- =====================================================================
|
||||
-- Omika Diagnostic Center — SEED RÉFÉRENTIEL COMPLET (généré)
|
||||
-- Généré par scripts\seed-referentiel-complet.ps1
|
||||
-- =====================================================================
|
||||
-- NON DESTRUCTIF : INSERT ... ON CONFLICT DO NOTHING.
|
||||
-- IDEMPOTENT : un libellé modifié par un Admin n'est JAMAIS écrasé.
|
||||
-- Cible : table diagnostics_reference (T24-A, créée par 011_diagnostics_reference.sql).
|
||||
-- Aucun mot de passe, aucune donnée personnelle, aucun secret.
|
||||
-- =====================================================================
|
||||
|
||||
INSERT INTO diagnostics_reference
|
||||
(reference_type, code, label, description, enabled, sort_order, created_at_utc, updated_at_utc)
|
||||
VALUES
|
||||
$($rows -join ",`n")
|
||||
ON CONFLICT DO NOTHING;
|
||||
|
||||
-- =====================================================================
|
||||
-- FIN. Vérification :
|
||||
-- SELECT reference_type, COUNT(*) FROM diagnostics_reference GROUP BY reference_type;
|
||||
-- =====================================================================
|
||||
"@
|
||||
|
||||
$null = New-Item -ItemType Directory -Path (Split-Path -Parent $OutputSql) -Force
|
||||
# UTF-8 SANS BOM (psql), indépendant de la version PowerShell.
|
||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
[System.IO.File]::WriteAllText($OutputSql, $sql, $utf8NoBom)
|
||||
|
||||
# ============================================================
|
||||
# 7. Application via psql (optionnel).
|
||||
# ============================================================
|
||||
if ($Apply) {
|
||||
$psql = $PsqlPath
|
||||
if ([string]::IsNullOrWhiteSpace($psql)) {
|
||||
$cmd = Get-Command psql -ErrorAction SilentlyContinue
|
||||
if ($cmd) { $psql = $cmd.Source }
|
||||
elseif (Test-Path -LiteralPath 'C:\Program Files\PostgreSQL\18\bin\psql.exe') {
|
||||
$psql = 'C:\Program Files\PostgreSQL\18\bin\psql.exe'
|
||||
}
|
||||
else {
|
||||
throw "psql introuvable : précisez -PsqlPath ou ajoutez PostgreSQL\bin au PATH."
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $Force) {
|
||||
Write-Host ''
|
||||
Write-Host "Application du référentiel :" -ForegroundColor Yellow
|
||||
Write-Host " Fichier : $OutputSql"
|
||||
Write-Host " Serveur : ${Server}:${Port}"
|
||||
Write-Host " Base : $Database"
|
||||
Write-Host " Utilisateur : $Username"
|
||||
$confirm = Read-Host "Confirmer l'application (taper OUI) ?"
|
||||
if ($confirm -cne 'OUI') {
|
||||
Write-Host 'Annulé par l''utilisateur.' -ForegroundColor Yellow
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($env:PGPASSWORD)) {
|
||||
$sec = Read-Host "Mot de passe PostgreSQL pour $Username" -AsSecureString
|
||||
$env:PGPASSWORD = [Runtime.InteropServices.Marshal]::PtrToStringUni(
|
||||
[Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec))
|
||||
}
|
||||
|
||||
try {
|
||||
& $psql -h $Server -p $Port -U $Username -d $Database -v ON_ERROR_STOP=1 -f $OutputSql
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
throw "psql a échoué (exit $LASTEXITCODE)."
|
||||
}
|
||||
}
|
||||
finally {
|
||||
$env:PGPASSWORD = $null
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# 8. Résumé.
|
||||
# ============================================================
|
||||
Write-Host ''
|
||||
Write-Host '== Référentiel généré ==' -ForegroundColor Cyan
|
||||
Write-Host " Écrans (ScreenCode) : $($screenCodes.Count)"
|
||||
Write-Host " Boutons [RelayCommand] : $($methodNames.Count) méthodes -> $($buttonCodes.Count) codes uniques"
|
||||
Write-Host " Actions métier (histor.) : $($semanticFeatureOrder.Count)"
|
||||
Write-Host " Total lignes SQL : $($rows.Count)"
|
||||
Write-Host " Fichier SQL : $OutputSql"
|
||||
if ($unmapped.Count -gt 0) {
|
||||
Write-Warning "Rappel : $($unmapped.Count) code(s) sans libellé explicite : $($unmapped -join ', ')"
|
||||
}
|
||||
if (-not $Apply) {
|
||||
Write-Host ''
|
||||
Write-Host 'Relancez avec -Apply pour appliquer via psql, ou exécutez :' -ForegroundColor Yellow
|
||||
Write-Host " psql -U $Username -d $Database -v ON_ERROR_STOP=1 -f $OutputSql"
|
||||
}
|
||||
@@ -0,0 +1,151 @@
|
||||
# ============================================================
|
||||
# Omika - Tests de scripts\Completer-Packs-Langues.ps1
|
||||
# ============================================================
|
||||
# AUCUN acces reseau : la recuperation des fichiers est injectee (scriptblock),
|
||||
# ce qui permet de simuler un modele identique, un modele different et une
|
||||
# panne reseau — les trois seuls cas qui decident du sort d'un pack.
|
||||
#
|
||||
# Ce que ces tests protegent :
|
||||
# - le nom du depot deduit du code et du sens (une inversion produirait
|
||||
# opus-mt-fr-es au lieu d'opus-mt-es-fr, donc le vocabulaire d'un AUTRE
|
||||
# modele, c'est-a-dire un charabia silencieux) ;
|
||||
# - le garde-fou : rien n'est ecrit quand le source.spm telecharge differe ;
|
||||
# - le fait que -Verifier n'ecrive jamais.
|
||||
# ============================================================
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$scriptTeste = Join-Path (Split-Path -Parent $PSScriptRoot) 'Completer-Packs-Langues.ps1'
|
||||
|
||||
$tests = 0
|
||||
$fails = 0
|
||||
|
||||
function Assert([bool]$condition, [string]$label) {
|
||||
$script:tests++
|
||||
if ($condition) {
|
||||
Write-Host ("PASS {0}" -f $label) -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host ("FAIL {0}" -f $label) -ForegroundColor Red
|
||||
$script:fails++
|
||||
}
|
||||
}
|
||||
|
||||
# Les fonctions du script sont chargees sans executer le traitement.
|
||||
. $scriptTeste
|
||||
|
||||
$bac = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-completion-test-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
$packs = Join-Path $bac 'packs'
|
||||
|
||||
try {
|
||||
# --- Un pack par cas -----------------------------------------------------
|
||||
foreach ($couple in @(
|
||||
@{ Code = 'es'; Sens = 'versFrancais'; ContenuSpm = 'source-espagnol' },
|
||||
@{ Code = 'pt'; Sens = 'versFrancais'; ContenuSpm = 'source-portugais' },
|
||||
@{ Code = 'it'; Sens = 'depuisFrancais'; ContenuSpm = 'source-italien' })) {
|
||||
$dossier = Join-Path (Join-Path $packs $couple.Code) $couple.Sens
|
||||
$null = New-Item -ItemType Directory -Path $dossier -Force
|
||||
Set-Content -LiteralPath (Join-Path $dossier 'source.spm') -Value $couple.ContenuSpm -NoNewline
|
||||
Set-Content -LiteralPath (Join-Path $dossier 'target.spm') -Value 'cible' -NoNewline
|
||||
Set-Content -LiteralPath (Join-Path $dossier 'encoder_model.onnx') -Value 'faux' -NoNewline
|
||||
}
|
||||
|
||||
# Dossier technique : ce n'est pas une langue, il doit etre ignore.
|
||||
$null = New-Item -ItemType Directory -Path (Join-Path $packs '.hf-cache\hub') -Force
|
||||
|
||||
# --- 1. Nom du depot deduit ---------------------------------------------
|
||||
Assert ((Get-NomModele -Code 'es' -Sens 'versFrancais') -eq 'Helsinki-NLP/opus-mt-es-fr') 'versFrancais -> opus-mt-es-fr'
|
||||
Assert ((Get-NomModele -Code 'it' -Sens 'depuisFrancais') -eq 'Helsinki-NLP/opus-mt-fr-it') 'depuisFrancais -> opus-mt-fr-it'
|
||||
Assert ((Get-NomModele -Code 'ES' -Sens 'versFrancais') -eq 'Helsinki-NLP/opus-mt-es-fr') 'code mis en minuscules'
|
||||
Assert ((Get-NomModele -Code 'pt-br' -Sens 'depuisFrancais') -eq 'Helsinki-NLP/opus-mt-fr-pt-br') 'code compose conserve'
|
||||
|
||||
# --- 2. Inventaire -------------------------------------------------------
|
||||
$presents = @(Get-PacksPresents -Racine $packs)
|
||||
Assert ($presents.Count -eq 3) 'trois couples (code, sens) trouves'
|
||||
Assert (@($presents | Where-Object { $_.Code -eq '.hf-cache' }).Count -eq 0) 'dossier technique ignore'
|
||||
|
||||
$filtres = @(Get-PacksPresents -Racine $packs -CodesFiltres @('pt'))
|
||||
Assert ($filtres.Count -eq 1 -and $filtres[0].Code -eq 'pt') 'filtre par code applique'
|
||||
|
||||
# --- 3. Faux recuperateur : es identique, pt different, it en panne ------
|
||||
$appels = New-Object 'System.Collections.Generic.List[string]'
|
||||
$recuperer = {
|
||||
param($modele, $fichier, $destination)
|
||||
$script:appels.Add(('{0}|{1}' -f $modele, $fichier))
|
||||
if ($modele -like '*fr-it') { return $false } # it : panne reseau
|
||||
|
||||
$contenu = if ($fichier -eq 'source.spm') {
|
||||
# pt : le depot deduit n'est pas celui du pack.
|
||||
if ($modele -like '*pt-fr') { 'source-autre-modele' } else { 'source-espagnol' }
|
||||
}
|
||||
elseif ($fichier -eq 'vocab.json') { '{ "piece": 584 }' }
|
||||
else { '{ "decoder_start_token_id": 74821, "eos_token_id": 0 }' }
|
||||
|
||||
Set-Content -LiteralPath $destination -Value $contenu -NoNewline
|
||||
return $true
|
||||
}
|
||||
|
||||
# --- 4. Verification seule : rien n'est ecrit ----------------------------
|
||||
$bilanVerif = Invoke-Completion -Racine $packs -Recuperer $recuperer -SansEcrire
|
||||
Assert ($bilanVerif.Packs -eq 3) 'verification : trois packs examines'
|
||||
Assert ($bilanVerif.Alignes -eq 1) 'verification : un pack aligne (es)'
|
||||
Assert ($bilanVerif.Ecartes -eq 1) 'verification : un pack ecarte (pt)'
|
||||
Assert ($bilanVerif.Echecs -eq 1) 'verification : un echec (it)'
|
||||
Assert ($bilanVerif.Ecrits -eq 0) 'verification : aucune ecriture'
|
||||
Assert (-not (Test-Path -LiteralPath (Join-Path $packs 'es\versFrancais\vocab.json'))) 'verification : vocab.json absent du pack aligne'
|
||||
|
||||
# --- 5. Ecriture reelle --------------------------------------------------
|
||||
$bilan = Invoke-Completion -Racine $packs -Recuperer $recuperer
|
||||
Assert ($bilan.Ecrits -eq 1) 'ecriture : un pack complete'
|
||||
Assert (Test-Path -LiteralPath (Join-Path $packs 'es\versFrancais\vocab.json')) 'ecriture : vocab.json pose'
|
||||
Assert (Test-Path -LiteralPath (Join-Path $packs 'es\versFrancais\config.json')) 'ecriture : config.json pose'
|
||||
Assert (-not (Test-Path -LiteralPath (Join-Path $packs 'pt\versFrancais\vocab.json'))) 'ecart : aucun fichier ecrit'
|
||||
Assert (-not (Test-Path -LiteralPath (Join-Path $packs 'it\depuisFrancais\vocab.json'))) 'echec : aucun fichier ecrit'
|
||||
|
||||
# --- 6. Le vocabulaire n'est jamais pris pour la configuration -----------
|
||||
$config = Get-Content -LiteralPath (Join-Path $packs 'es\versFrancais\config.json') -Raw
|
||||
Assert ($config -like '*decoder_start_token_id*') 'config.json recopie tel quel'
|
||||
$vocab = Get-Content -LiteralPath (Join-Path $packs 'es\versFrancais\vocab.json') -Raw
|
||||
Assert ($vocab -like '*584*') 'vocab.json recopie tel quel'
|
||||
|
||||
# --- 7. Les bons fichiers ont ete demandes --------------------------------
|
||||
Assert ($appels -contains 'Helsinki-NLP/opus-mt-es-fr|vocab.json') 'vocabulaire demande au bon depot (es)'
|
||||
Assert ($appels -contains 'Helsinki-NLP/opus-mt-fr-it|source.spm') 'source demandee au bon depot (it)'
|
||||
|
||||
# --- 8. Idempotence : relancer ne casse rien -----------------------------
|
||||
$bilan2 = Invoke-Completion -Racine $packs -Recuperer $recuperer
|
||||
Assert ($bilan2.Ecrits -eq 1 -and $bilan2.Packs -eq 3) 'deuxieme passage : meme resultat'
|
||||
|
||||
# --- 9. Un pack familial (commun/) n'est pas un pack a completer ---------
|
||||
# Un modele familial range ses poids dans <code>\commun\ et ne laisse qu'un
|
||||
# modele.json par sens : il n'a AUCUN source.spm par sens, donc rien a
|
||||
# aligner. Le completer fabriquerait sinon un [ECART] trompeur (la paire
|
||||
# bilingue deduite n'existe pas) et ferait croire le pack casse.
|
||||
$dossierFamille = Join-Path (Join-Path $packs 'de') 'commun'
|
||||
$null = New-Item -ItemType Directory -Path $dossierFamille -Force
|
||||
Set-Content -LiteralPath (Join-Path $dossierFamille 'source.spm') -Value 'source-famille' -NoNewline
|
||||
Set-Content -LiteralPath (Join-Path $dossierFamille 'encoder_model.onnx') -Value 'faux' -NoNewline
|
||||
foreach ($sens in @('versFrancais', 'depuisFrancais')) {
|
||||
$dossierSens = Join-Path (Join-Path $packs 'de') $sens
|
||||
$null = New-Item -ItemType Directory -Path $dossierSens -Force
|
||||
Set-Content -LiteralPath (Join-Path $dossierSens 'modele.json') -Value '{ "jetonCible": ">>fra<<" }' -NoNewline
|
||||
}
|
||||
|
||||
$presentsFamille = @(Get-PacksPresents -Racine $packs)
|
||||
Assert ($presentsFamille.Count -eq 3) 'pack familial : aucun couple (code, sens) a completer'
|
||||
$bilanFamille = Invoke-Completion -Racine $packs -Recuperer $recuperer -SansEcrire
|
||||
Assert ($bilanFamille.Packs -eq 3 -and $bilanFamille.Ecartes -eq 1 -and $bilanFamille.Echecs -eq 1) `
|
||||
'pack familial : le bilan ne change pas (aucun ecart invente)'
|
||||
}
|
||||
finally {
|
||||
Remove-Item -LiteralPath $bac -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
if ($fails -eq 0) {
|
||||
Write-Host ("{0} tests, 0 echec" -f $tests) -ForegroundColor Green
|
||||
exit 0
|
||||
}
|
||||
|
||||
Write-Host ("{0} tests, {1} echec(s)" -f $tests, $fails) -ForegroundColor Red
|
||||
exit 1
|
||||
@@ -0,0 +1,538 @@
|
||||
# ============================================================
|
||||
# Omika — Tests de scripts\Exporter-Packs-Langues.ps1
|
||||
# ============================================================
|
||||
# AUCUN acces reseau, AUCUN Python, AUCUN optimum : l'export est simule par un
|
||||
# faux « optimum-cli » place dans un dossier temporaire ajoute au PATH. Ce qui
|
||||
# est verifie ici est donc la logique du script, pas le contenu des modeles.
|
||||
#
|
||||
# Ce que ces tests protegent :
|
||||
# - le nom des modeles demande a HuggingFace (un code de langue mal substitue
|
||||
# produirait « opus-mt-e-fr » au lieu de « opus-mt-es-fr ») ;
|
||||
# - les fichiers REELLEMENT ranges dans le pack (decoder_with_past_model.onnx
|
||||
# ne doit jamais y entrer : il serait telecharge par chaque telephone) ;
|
||||
# - l'idempotence (relancer ne reexporte pas) ;
|
||||
# - -Simulation : le plan, sans rien ecrire.
|
||||
# ============================================================
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
|
||||
$scriptTeste = Join-Path (Split-Path -Parent $PSScriptRoot) 'Exporter-Packs-Langues.ps1'
|
||||
|
||||
$tests = 0
|
||||
$fails = 0
|
||||
|
||||
function Assert([bool]$condition, [string]$label) {
|
||||
$script:tests++
|
||||
if ($condition) {
|
||||
Write-Host ("PASS {0}" -f $label) -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host ("FAIL {0}" -f $label) -ForegroundColor Red
|
||||
$script:fails++
|
||||
}
|
||||
}
|
||||
|
||||
$bac = Join-Path ([System.IO.Path]::GetTempPath()) ('omika-export-' + [guid]::NewGuid().ToString('N').Substring(0, 8))
|
||||
$shim = Join-Path $bac 'shim'
|
||||
$packs = Join-Path $bac 'packs'
|
||||
|
||||
try {
|
||||
$null = New-Item -ItemType Directory -Path $shim -Force
|
||||
|
||||
# --- Faux outils : reproduisent la sortie REELLE d'un export optimum -------
|
||||
# optimum produit encoder_model.onnx, decoder_model.onnx,
|
||||
# decoder_with_past_model.onnx (a NE PAS livrer), source.spm, target.spm et
|
||||
# des fichiers de tokenizer.
|
||||
@'
|
||||
@echo off
|
||||
set OUT=%~5
|
||||
mkdir "%OUT%" 2>nul
|
||||
echo fake-encoder > "%OUT%\encoder_model.onnx"
|
||||
echo fake-decoder > "%OUT%\decoder_model.onnx"
|
||||
echo fake-past > "%OUT%\decoder_with_past_model.onnx"
|
||||
echo fake-source > "%OUT%\source.spm"
|
||||
echo fake-target > "%OUT%\target.spm"
|
||||
echo { "piece": 1 } > "%OUT%\vocab.json"
|
||||
echo { "decoder_start_token_id": 1, "eos_token_id": 0 } > "%OUT%\config.json"
|
||||
echo fake-junk > "%OUT%\tokenizer.json"
|
||||
if not "%OMIKA_TEST_HFHOME_FILE%"=="" echo %HF_HOME%> "%OMIKA_TEST_HFHOME_FILE%"
|
||||
exit /b 0
|
||||
'@ | Set-Content -LiteralPath (Join-Path $shim 'optimum-cli.cmd') -Encoding ASCII
|
||||
|
||||
@'
|
||||
@echo off
|
||||
if "%~1"=="-c" (
|
||||
if not "%OMIKA_TEST_SCRIPTS_PYTHON%"=="" echo %OMIKA_TEST_SCRIPTS_PYTHON%
|
||||
if not "%OMIKA_TEST_SCRIPTS_NTUSER%"=="" echo %OMIKA_TEST_SCRIPTS_NTUSER%
|
||||
exit /b 0
|
||||
)
|
||||
echo Python 3.11.0
|
||||
exit /b 0
|
||||
'@ | Set-Content -LiteralPath (Join-Path $shim 'python.cmd') -Encoding ASCII
|
||||
|
||||
# --- 1. Syntaxe ---------------------------------------------------------
|
||||
Write-Host '=== 1. Syntaxe PowerShell 5.1 ==='
|
||||
$tokens = $null
|
||||
$errors = $null
|
||||
[System.Management.Automation.Language.Parser]::ParseFile($scriptTeste, [ref]$tokens, [ref]$errors) | Out-Null
|
||||
Assert ((@($errors).Count) -eq 0) 'Syntaxe : 0 erreur'
|
||||
|
||||
# --- 2. -Simulation : le plan, et RIEN sur le disque --------------------
|
||||
Write-Host ''
|
||||
Write-Host '=== 2. -Simulation ==='
|
||||
$plan = (& $scriptTeste -Simulation -RacinePacks $packs 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 0) '-Simulation : code retour 0'
|
||||
Assert ($plan -match 'Anglais') '-Simulation : la table des langues est listee'
|
||||
Assert ($plan -match 'opus-mt-uk-fr' -and $plan -match 'opus-mt-ru-fr') '-Simulation : ukrainien et russe presents'
|
||||
Assert ($plan -match 'Modeles a exporter\s+:\s+102') '-Simulation : 102 modeles (2 par langue, 51 langues) annonces'
|
||||
Assert ($plan -match 'opus-mt-ar-fr' -and $plan -match 'opus-mt-fr-ar') '-Simulation : l''arabe est dans la table'
|
||||
Assert ($plan -match 'opus-mt-tc-big') '-Simulation : la famille de repli « tc-big » est annoncee'
|
||||
Assert ($plan -match 'Repli familial pour 6 langue') '-Simulation : les 6 langues a modele familial sont annoncees'
|
||||
Assert ($plan -match 'opus-mt-tc-big-itc-itc') '-Simulation : le modele de famille est nomme'
|
||||
Assert (-not (Test-Path -LiteralPath $packs)) '-Simulation : aucun dossier de sortie cree'
|
||||
|
||||
$zone = (& $scriptTeste -Simulation -Zone Est -RacinePacks $packs 6>&1 | Out-String)
|
||||
Assert ($zone -match 'Langues selectionnees : 3') '-Zone Est : 3 langues (ru, uk, be)'
|
||||
Assert ($zone -notmatch 'Espagnol') '-Zone Est : les autres zones sont exclues'
|
||||
|
||||
$codes = (& $scriptTeste -Simulation -Codes es,zzz -RacinePacks $packs 6>&1 | Out-String)
|
||||
Assert ($codes -match 'code inconnu, ignore : zzz') '-Codes : un code inconnu est signale'
|
||||
Assert ($codes -match 'Langues selectionnees : 1') '-Codes : seule la langue connue est retenue'
|
||||
|
||||
# -Codes "es,pt" (une seule chaine, entre guillemets ou via une variable) :
|
||||
# piege reel — sans decoupage, la chaine entiere etait prise pour un code
|
||||
# unique et le script s'arretait sur « Aucune langue selectionnee ».
|
||||
$chaine = (& $scriptTeste -Simulation -Codes 'es,pt' -RacinePacks $packs 6>&1 | Out-String)
|
||||
Assert ($chaine -match 'Langues selectionnees : 2') '-Codes : la liste entre guillemets est bien decoupee'
|
||||
Assert ($chaine -notmatch 'code inconnu') '-Codes : aucune alerte sur une liste entre guillemets'
|
||||
|
||||
# --- 3. Export reel (simule) --------------------------------------------
|
||||
Write-Host ''
|
||||
Write-Host '=== 3. Export reel avec un faux optimum-cli ==='
|
||||
$env:PATH = $shim + ';' + $env:PATH
|
||||
|
||||
# Cache HuggingFace : le script doit le placer a cote des packs (et non sur C:
|
||||
# dans %USERPROFILE%), et le vider apres un export reussi — sinon chaque
|
||||
# modele occupe deux fois sa taille.
|
||||
$traceHfHome = Join-Path $bac 'hfhome.txt'
|
||||
$env:OMIKA_TEST_HFHOME_FILE = $traceHfHome
|
||||
$cacheFaux = Join-Path (Join-Path $packs '.hf-cache') 'hub\models--Helsinki-NLP--opus-mt-es-fr'
|
||||
$null = New-Item -ItemType Directory -Path $cacheFaux -Force
|
||||
[System.IO.File]::WriteAllBytes((Join-Path $cacheFaux 'blob-pytorch'), (New-Object byte[] 2048))
|
||||
|
||||
$env:HF_HOME = ''
|
||||
$sortie = (& $scriptTeste -SansVerifierHuggingFace -Codes es,pt -RacinePacks $packs `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 0) 'Export : code retour 0'
|
||||
|
||||
Assert (Test-Path -LiteralPath $traceHfHome) 'Cache HuggingFace : HF_HOME est transmis a l''export'
|
||||
$hfTransmis = (Get-Content -LiteralPath $traceHfHome -Raw).Trim()
|
||||
Assert ($hfTransmis -eq (Join-Path $packs '.hf-cache')) `
|
||||
('Cache HuggingFace : place a cote des packs ({0})' -f $hfTransmis)
|
||||
Assert (-not (Test-Path -LiteralPath $cacheFaux)) 'Cache HuggingFace : la copie du modele est supprimee apres export'
|
||||
Assert ($sortie -match 'Cache HuggingFace libere') 'Cache HuggingFace : l''espace libere est annonce'
|
||||
|
||||
# Le nom du modele : le bug « opus-mt-e-fr » venait du deroulement PowerShell
|
||||
# d'un tableau a un seul element ('es'[0] = 'e').
|
||||
Assert ($sortie -match 'Helsinki-NLP/opus-mt-es-fr') 'Export : modele « opus-mt-es-fr » demande'
|
||||
Assert ($sortie -match 'Helsinki-NLP/opus-mt-fr-es') 'Export : modele « opus-mt-fr-es » demande'
|
||||
Assert ($sortie -notmatch 'opus-mt-e-fr') 'Export : aucun code de langue tronque en un caractere'
|
||||
|
||||
foreach ($sens in @('versFrancais', 'depuisFrancais')) {
|
||||
$dossier = Join-Path (Join-Path $packs 'es') $sens
|
||||
Assert (Test-Path -LiteralPath (Join-Path $dossier 'encoder_model.onnx')) ("es/{0} : modele principal present" -f $sens)
|
||||
Assert (Test-Path -LiteralPath (Join-Path $dossier 'decoder_model.onnx')) ("es/{0} : decodeur separe present" -f $sens)
|
||||
Assert (Test-Path -LiteralPath (Join-Path $dossier 'source.spm')) ("es/{0} : source.spm present" -f $sens)
|
||||
Assert (Test-Path -LiteralPath (Join-Path $dossier 'target.spm')) ("es/{0} : target.spm present" -f $sens)
|
||||
|
||||
# Le moteur a besoin de ces deux referentiels : sans eux il envoie au
|
||||
# modele les identifiants du .spm, qui ne sont pas les siens (mesure :
|
||||
# ▁Ho = 5293 cote .spm contre 584 cote modele) — traduction fausse et
|
||||
# silencieuse. Les oublier ici reproduirait exactement ce defaut.
|
||||
Assert (Test-Path -LiteralPath (Join-Path $dossier 'vocab.json')) ("es/{0} : vocab.json present" -f $sens)
|
||||
Assert (Test-Path -LiteralPath (Join-Path $dossier 'config.json')) ("es/{0} : config.json present" -f $sens)
|
||||
}
|
||||
|
||||
Assert ((@(Get-ChildItem -LiteralPath $packs -Recurse -Filter '*with_past*')).Count -eq 0) `
|
||||
'Export : decoder_with_past_model.onnx JAMAIS livre'
|
||||
Assert ((@(Get-ChildItem -LiteralPath $packs -Recurse -Filter 'tokenizer.json')).Count -eq 0) `
|
||||
'Export : fichiers de tokenizer non livres'
|
||||
Assert ((@(Get-ChildItem -LiteralPath $packs -Directory -Filter '.tmp-*')).Count -eq 0) `
|
||||
'Export : dossiers temporaires nettoyes'
|
||||
|
||||
# --- 4. Idempotence et -Forcer ------------------------------------------
|
||||
Write-Host ''
|
||||
Write-Host '=== 4. Idempotence ==='
|
||||
$sortie2 = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packs `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
|
||||
Assert ($sortie2 -match 'deja present') 'Idempotence : un pack deja present est ignore'
|
||||
Assert ($sortie2 -notmatch 'export : Helsinki') 'Idempotence : aucun export relance'
|
||||
|
||||
$sortie3 = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packs -Forcer `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
|
||||
Assert ($sortie3 -match 'export : Helsinki') '-Forcer : l''export est relance'
|
||||
|
||||
# -GarderCacheHuggingFace : on conserve la copie telechargee (utile pour
|
||||
# enchainer plusieurs -Forcer sans retelecharger 300 Mo a chaque fois).
|
||||
$cacheConserve = Join-Path (Join-Path $packs '.hf-cache') 'hub\models--Helsinki-NLP--opus-mt-fr-es'
|
||||
$null = New-Item -ItemType Directory -Path $cacheConserve -Force
|
||||
[System.IO.File]::WriteAllBytes((Join-Path $cacheConserve 'blob-pytorch'), (New-Object byte[] 1024))
|
||||
$null = & $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packs -Forcer -GarderCacheHuggingFace `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' 6>&1
|
||||
Assert (Test-Path -LiteralPath $cacheConserve) '-GarderCacheHuggingFace : le cache est conserve'
|
||||
|
||||
# --- 4bis. Langue a moitie faite : ne pas repayer le sens deja la --------
|
||||
# Panne reelle : -InclureDirectionsPartielles sert a completer une langue qui
|
||||
# n'a qu'un sens. Comme le test d'idempotence porte sur la LANGUE ENTIERE, la
|
||||
# version precedente reexportait le sens deja present : 11 langues a moitie
|
||||
# faites = 11 telechargements de 500 Mo inutiles.
|
||||
Write-Host ''
|
||||
Write-Host '=== 4bis. Sens deja present dans une langue incomplete ==='
|
||||
$packsPartiel = Join-Path $bac 'packs-partiel'
|
||||
$sensDejaLa = Join-Path $packsPartiel 'es\versFrancais'
|
||||
$null = New-Item -ItemType Directory -Path $sensDejaLa -Force
|
||||
foreach ($nom in @('encoder_model.onnx', 'decoder_model.onnx', 'source.spm', 'target.spm')) {
|
||||
[System.IO.File]::WriteAllText((Join-Path $sensDejaLa $nom), 'MARQUEUR-INTACT')
|
||||
}
|
||||
|
||||
$sortiePartiel = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packsPartiel `
|
||||
-InclureDirectionsPartielles -OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 0) 'Partiel : code retour 0'
|
||||
Assert ($sortiePartiel -match 'versFrancais : deja present, ignore') `
|
||||
'Partiel : le sens deja present est ignore, pas reexporte'
|
||||
Assert ((Get-Content -LiteralPath (Join-Path $sensDejaLa 'encoder_model.onnx') -Raw).Trim() -eq 'MARQUEUR-INTACT') `
|
||||
'Partiel : le sens deja present n''est pas ecrase'
|
||||
Assert (Test-Path -LiteralPath (Join-Path $packsPartiel 'es\depuisFrancais\encoder_model.onnx')) `
|
||||
'Partiel : le sens manquant est bien construit'
|
||||
|
||||
# --- 5. Rapport ---------------------------------------------------------
|
||||
Write-Host ''
|
||||
Write-Host '=== 5. Rapport ==='
|
||||
$rapport = Join-Path $packs 'RAPPORT-LANGUES.md'
|
||||
Assert (Test-Path -LiteralPath $rapport) 'Rapport : RAPPORT-LANGUES.md ecrit'
|
||||
$contenu = Get-Content -LiteralPath $rapport -Raw -Encoding UTF8
|
||||
Assert ($contenu -match '\| es \| Espagnol') 'Rapport : la langue exportee y figure'
|
||||
Assert ($contenu -match 'Paires indisponibles') 'Rapport : section des paires indisponibles presente'
|
||||
Assert ($contenu -match 'New-OmikaLanguesManifeste') 'Rapport : rappelle la generation du manifeste'
|
||||
|
||||
# --- 6. optimum-cli hors PATH (Python du Microsoft Store) ---------------
|
||||
# Cas reel : « pip install optimum » reussit, mais pip range le programme
|
||||
# dans un dossier utilisateur qui n'est PAS dans le PATH. Toutes les fenetres
|
||||
# repondent alors « optimum-cli introuvable » et le script s'arrete sans rien
|
||||
# faire — l'utilisateur croit que le script ne fonctionne pas. Il doit donc
|
||||
# aller le chercher la ou pip l'ecrit.
|
||||
Write-Host ''
|
||||
Write-Host '=== 6. optimum-cli hors PATH ==='
|
||||
$scriptsPython = Join-Path $bac 'scripts-python'
|
||||
$null = New-Item -ItemType Directory -Path $scriptsPython -Force
|
||||
Copy-Item -LiteralPath (Join-Path $shim 'optimum-cli.cmd') `
|
||||
-Destination (Join-Path $scriptsPython 'optimum-cli.cmd') -Force
|
||||
|
||||
$pathSansShim = (@($env:PATH -split ';' | Where-Object { $_ -and $_ -ne $shim }) -join ';')
|
||||
$env:PATH = $pathSansShim
|
||||
$env:OMIKA_TEST_SCRIPTS_PYTHON = $scriptsPython
|
||||
$pythonHorsPath = Join-Path $shim 'python.cmd'
|
||||
$packsHorsPath = Join-Path $bac 'packs-horspath'
|
||||
|
||||
$sortieHorsPath = (& $scriptTeste -SansVerifierHuggingFace -Codes es `
|
||||
-RacinePacks $packsHorsPath -Python $pythonHorsPath 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 0) 'Hors PATH : le script aboutit quand meme'
|
||||
Assert ($sortieHorsPath -match 'trouve hors PATH') 'Hors PATH : la recherche est annoncee'
|
||||
Assert (Test-Path -LiteralPath (Join-Path $packsHorsPath 'es\versFrancais\encoder_model.onnx')) `
|
||||
'Hors PATH : le pack est construit malgre tout'
|
||||
|
||||
# Environnement virtuel (venv) : c'est la solution quand l'installation bute
|
||||
# sur la limite de 260 caracteres de Windows. Le Python du venv annonce
|
||||
# D'ABORD son propre dossier de scripts, PUIS le dossier utilisateur. L'outil
|
||||
# peut n'etre que dans le second : le script doit balayer les deux, sinon il
|
||||
# conclut « introuvable » alors que l'installation est bonne.
|
||||
$venvVide = Join-Path $bac 'venv-vide'
|
||||
$null = New-Item -ItemType Directory -Path $venvVide -Force
|
||||
$env:OMIKA_TEST_SCRIPTS_PYTHON = $venvVide
|
||||
$env:OMIKA_TEST_SCRIPTS_NTUSER = $scriptsPython
|
||||
$packsVenv = Join-Path $bac 'packs-venv'
|
||||
$sortieVenv = (& $scriptTeste -SansVerifierHuggingFace -Codes pt `
|
||||
-RacinePacks $packsVenv -Python $pythonHorsPath 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 0) 'Venv : l''outil est trouve dans le SECOND dossier annonce'
|
||||
Assert (Test-Path -LiteralPath (Join-Path $packsVenv 'pt\versFrancais\encoder_model.onnx')) `
|
||||
'Venv : le pack est construit'
|
||||
|
||||
# Outil reellement absent : le message doit dire QUOI installer ET OU le
|
||||
# chercher, sinon l'utilisateur reinstalle en boucle sans comprendre.
|
||||
$scriptsVides = Join-Path $bac 'scripts-vides'
|
||||
$null = New-Item -ItemType Directory -Path $scriptsVides -Force
|
||||
$env:OMIKA_TEST_SCRIPTS_PYTHON = $scriptsVides
|
||||
$env:OMIKA_TEST_SCRIPTS_NTUSER = $scriptsVides
|
||||
$absent = (& $scriptTeste -Codes es -RacinePacks (Join-Path $bac 'packs-absent') `
|
||||
-Python $pythonHorsPath 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 1) 'Outil absent : code retour 1'
|
||||
Assert ($absent -match 'pip install') 'Outil absent : la commande d''installation est indiquee'
|
||||
Assert ($absent -match 'pas dans le PATH') 'Outil absent : le piege du PATH est explique'
|
||||
Assert ($absent -notmatch 'Test-Path') 'Outil absent : aucune erreur interne affichee'
|
||||
|
||||
$env:PATH = $shim + ';' + $pathSansShim
|
||||
|
||||
# --- 7. -SansExporter : l'inventaire des paires -------------------------
|
||||
# Repond a « quelles langues sont manquantes ? » SANS telecharger 30 Go. Il
|
||||
# ne doit donc exiger NI Python NI optimum : la question se pose AVANT
|
||||
# d'installer les outils.
|
||||
Write-Host ''
|
||||
Write-Host '=== 7. -SansExporter (inventaire) ==='
|
||||
$contradiction = (& $scriptTeste -SansExporter -SansVerifierHuggingFace -Codes es `
|
||||
-RacinePacks $packs 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 1) '-SansExporter : la combinaison contradictoire est refusee'
|
||||
Assert ($contradiction -match 'a besoin de la verification') '-SansExporter : la raison est expliquee'
|
||||
|
||||
# Langue deja presente : aucun reseau, aucun export, rapport ecrit.
|
||||
$null = (& $scriptTeste -SansExporter -Codes es -RacinePacks $packs 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 0) '-SansExporter : aboutit sans Python ni optimum'
|
||||
$rapportInventaire = Get-Content -LiteralPath (Join-Path $packs 'RAPPORT-LANGUES.md') -Raw -Encoding UTF8
|
||||
Assert ($rapportInventaire -match 'inventaire') '-SansExporter : le rapport annonce le mode inventaire'
|
||||
Assert ($rapportInventaire -match 'Paires disponibles') '-SansExporter : la section est renommee'
|
||||
|
||||
# --- 8. Interprétation des réponses HuggingFace --------------------------
|
||||
# Mesure sur l'export des 50 langues : HuggingFace répond 401 (JAMAIS 404)
|
||||
# pour un dépôt absent. Un « 401 » seul étant ambigu, c'est le CORPS de la
|
||||
# réponse qui tranche — une erreur de lecture ferait écrire au rapport que
|
||||
# des langues livrables n'existent pas. Fonction pure : testable sans réseau.
|
||||
Write-Host ''
|
||||
Write-Host '=== 8. Verdict HuggingFace ==='
|
||||
$texteScript = Get-Content -LiteralPath $scriptTeste -Raw -Encoding UTF8
|
||||
$definition = [regex]::Match($texteScript, '(?s)function Get-VerdictEchecHuggingFace \{.*?\r?\n\}').Value
|
||||
Assert ($definition.Length -gt 0) 'Verdict : la fonction est extractible du script'
|
||||
|
||||
if ($definition.Length -gt 0) {
|
||||
. ([scriptblock]::Create($definition))
|
||||
|
||||
$vAbsent = Get-VerdictEchecHuggingFace -Code 401 -Corps '{"error":"Repository not found"}' -MessageErreur 'x'
|
||||
Assert (-not $vAbsent.Existe -and -not $vAbsent.Injoignable) 'Verdict : 401 « Repository not found » = paire inexistante'
|
||||
Assert ($vAbsent.Detail -match 'depot introuvable') 'Verdict : le motif exact est inscrit au rapport'
|
||||
|
||||
$v404 = Get-VerdictEchecHuggingFace -Code 404 -Corps '' -MessageErreur 'x'
|
||||
Assert (-not $v404.Existe -and -not $v404.Injoignable) 'Verdict : 404 = paire inexistante'
|
||||
|
||||
# Mesure du 16/10, meme client, meme instant : un depot EXISTANT repond
|
||||
# 200 (opus-mt-es-fr) et un depot absent repond 401, sur l'API COMME sur
|
||||
# la page publique. Un 401 inexplique est donc traite comme une absence,
|
||||
# mais le rapport dit sur quoi il se fonde — le temoin 200.
|
||||
$vDoute = Get-VerdictEchecHuggingFace -Code 401 -Corps '{"error":"Invalid credentials"}' -MessageErreur 'x'
|
||||
Assert (-not $vDoute.Existe -and -not $vDoute.Injoignable) `
|
||||
'Verdict : 401 sans « not found » = paire non publiee'
|
||||
Assert ($vDoute.Detail -match 'repond 200') 'Verdict : le rapport cite le temoin qui justifie le 401'
|
||||
|
||||
$vQuota = Get-VerdictEchecHuggingFace -Code 429 -Corps '' -MessageErreur 'x'
|
||||
Assert ($vQuota.Injoignable) 'Verdict : 429 (quota) n''est PAS une absence'
|
||||
|
||||
$vServeur = Get-VerdictEchecHuggingFace -Code 503 -Corps '' -MessageErreur 'x'
|
||||
Assert ($vServeur.Injoignable) 'Verdict : 503 n''est PAS une absence'
|
||||
|
||||
$vReseau = Get-VerdictEchecHuggingFace -Code 0 -Corps '' -MessageErreur 'connexion fermee'
|
||||
Assert ($vReseau.Injoignable -and $vReseau.Detail -match 'connexion') 'Verdict : panne reseau = « on ne sait pas »'
|
||||
}
|
||||
|
||||
# --- 9. Un avertissement sur stderr ne doit JAMAIS tuer un export --------
|
||||
# Panne reelle : optimum-cli ecrit un UserWarning de huggingface_hub sur
|
||||
# stderr. Lance avec « 2>&1 | Tee-Object » (naturel pour garder une trace sur
|
||||
# plusieurs heures), le script transformait cette ligne en erreur FATALE
|
||||
# ($ErrorActionPreference = 'Stop') et tuait les 11 exports juste apres le
|
||||
# config.json : 0 octet telecharge, et un rapport annoncant 11 echecs qui
|
||||
# n'en etaient pas. Seul le code de sortie doit juger un export.
|
||||
Write-Host ''
|
||||
Write-Host '=== 9. Avertissement stderr pendant l''export ==='
|
||||
@'
|
||||
@echo off
|
||||
set OUT=%~5
|
||||
mkdir "%OUT%" 2>nul
|
||||
echo UserWarning: the cache-system uses symlinks >&2
|
||||
echo fake-encoder > "%OUT%\encoder_model.onnx"
|
||||
echo fake-decoder > "%OUT%\decoder_model.onnx"
|
||||
echo fake-past > "%OUT%\decoder_with_past_model.onnx"
|
||||
echo fake-source > "%OUT%\source.spm"
|
||||
echo fake-target > "%OUT%\target.spm"
|
||||
echo { "piece": 1 } > "%OUT%\vocab.json"
|
||||
echo { "decoder_start_token_id": 1, "eos_token_id": 0 } > "%OUT%\config.json"
|
||||
exit /b 0
|
||||
'@ | Set-Content -LiteralPath (Join-Path $shim 'optimum-cli.cmd') -Encoding ASCII
|
||||
|
||||
$packsBruyants = Join-Path $bac 'packs-stderr'
|
||||
# *>&1 : TOUS les flux, erreur comprise — c'est exactement la redirection qui
|
||||
# rendait la panne possible (« 2>&1 | Tee-Object » pour garder une trace).
|
||||
$sortieBruyante = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packsBruyants `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' *>&1 | Out-String)
|
||||
$sensBruyant = Join-Path $packsBruyants 'es\versFrancais'
|
||||
Assert ($LASTEXITCODE -eq 0) 'stderr : l''export aboutit malgre l''avertissement (code 0)'
|
||||
Assert (Test-Path -LiteralPath (Join-Path $sensBruyant 'encoder_model.onnx')) `
|
||||
'stderr : le pack est ecrit malgre l''avertissement'
|
||||
# 6 fichiers : encoder + decoder + source.spm + target.spm + vocab.json +
|
||||
# config.json. Les deux derniers sont les referentiels du modele (identifiants
|
||||
# et conventions du decodeur), pas des poids.
|
||||
Assert ($sortieBruyante -match 'versFrancais : 6 fichier') `
|
||||
'stderr : le sens est compte comme construit, pas comme un echec'
|
||||
|
||||
# --- 10. Le catalogue : la preuve d'existence ---------------------------
|
||||
# Troisieme et derniere evolution, apres mesures reelles : un depot absent
|
||||
# repond 401 sur l'API, la page publique s'est mise a repondre 401 elle
|
||||
# aussi, alors que le meme client obtenait 200 sur un depot existant au meme
|
||||
# instant. Plus aucun code d'erreur ne discrimine. On lit donc LA LISTE
|
||||
# publique des depots (une requete, toujours 200) : l'existence devient une
|
||||
# egalite de chaine, et « absent » devient une preuve.
|
||||
Write-Host ''
|
||||
Write-Host '=== 10. Catalogue Helsinki-NLP ==='
|
||||
$definitionCatalogue = [regex]::Match($texteScript, '(?s)function Get-CatalogueHelsinkiNLP \{.*?\r?\n\}').Value
|
||||
Assert ($definitionCatalogue.Length -gt 0) 'Catalogue : la fonction est extractible du script'
|
||||
Assert ($definitionCatalogue -match 'api/models\?author=') 'Catalogue : lit la liste publique des depots'
|
||||
Assert ($definitionCatalogue -match 'Headers\[' -and $definitionCatalogue -match 'next') `
|
||||
'Catalogue : suit la pagination annoncee par l''entete Link'
|
||||
# Garde-fou mesure : une reponse a 2 entrees a fait declarer 66 modeles
|
||||
# absents, dont 11 presents sur le disque. Un catalogue trop court est rejete.
|
||||
Assert ($definitionCatalogue -match 'Count -lt 100') `
|
||||
'Catalogue : une reponse tronquee est refusee (jamais « absent » sur 2 entrees)'
|
||||
|
||||
$definitionSonde = [regex]::Match($texteScript, '(?s)function Test-ModeleHuggingFace \{.*?\r?\n\}').Value
|
||||
Assert ($definitionSonde.Length -gt 0) 'Catalogue : la sonde est extractible du script'
|
||||
|
||||
if ($definitionSonde.Length -gt 0) {
|
||||
. ([scriptblock]::Create($definitionSonde))
|
||||
|
||||
$tableDepots = New-Object 'System.Collections.Generic.HashSet[string]' ([System.StringComparer]::OrdinalIgnoreCase)
|
||||
$null = $tableDepots.Add('Helsinki-NLP/opus-mt-es-fr')
|
||||
|
||||
$vPresent = Test-ModeleHuggingFace -Modele 'Helsinki-NLP/opus-mt-es-fr' -Catalogue $tableDepots
|
||||
Assert ($vPresent.Existe -and -not $vPresent.Injoignable) 'Catalogue : un depot present est reconnu'
|
||||
|
||||
$vAbsent = Test-ModeleHuggingFace -Modele 'Helsinki-NLP/opus-mt-fr-nl' -Catalogue $tableDepots
|
||||
Assert (-not $vAbsent.Existe -and -not $vAbsent.Injoignable) `
|
||||
'Catalogue : un depot absent est une PREUVE, pas un doute'
|
||||
Assert ($vAbsent.Detail -match 'catalogue') 'Catalogue : le rapport cite la source du verdict'
|
||||
|
||||
$vCasse = Test-ModeleHuggingFace -Modele 'helsinki-nlp/OPUS-MT-ES-FR' -Catalogue $tableDepots
|
||||
Assert ($vCasse.Existe) 'Catalogue : la comparaison ignore la casse'
|
||||
}
|
||||
|
||||
# --- 11. Repli de famille a l'export -------------------------------------
|
||||
# Certaines langues (georgien, armenien, azari…) ne sont couvertes que par
|
||||
# « opus-mt-tc-big ». En mode verifie le catalogue tranche ; sans
|
||||
# verification, c'est l'export qui doit essayer la petite famille PUIS la
|
||||
# « tc-big », et garder la premiere qui aboutit. Avant, il n'essayait que la
|
||||
# petite : ces langues etaient declarées en echec sans avoir ete tentees.
|
||||
Write-Host ''
|
||||
Write-Host '=== 11. Repli opus-mt -> tc-big a l''export ==='
|
||||
@'
|
||||
@echo off
|
||||
set MODELE=%~4
|
||||
set OUT=%~5
|
||||
echo %MODELE% | findstr /C:"tc-big" >nul
|
||||
if errorlevel 1 (
|
||||
echo Error: 401 Repository not found 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
mkdir "%OUT%" 2>nul
|
||||
echo fake-encoder > "%OUT%\encoder_model.onnx"
|
||||
echo fake-decoder > "%OUT%\decoder_model.onnx"
|
||||
echo fake-source > "%OUT%\source.spm"
|
||||
echo fake-target > "%OUT%\target.spm"
|
||||
echo { "piece": 1 } > "%OUT%\vocab.json"
|
||||
echo { "decoder_start_token_id": 1, "eos_token_id": 0 } > "%OUT%\config.json"
|
||||
exit /b 0
|
||||
'@ | Set-Content -LiteralPath (Join-Path $shim 'optimum-cli.cmd') -Encoding ASCII
|
||||
|
||||
$packsTcBig = Join-Path $bac 'packs-tcbig'
|
||||
$sortieTcBig = (& $scriptTeste -SansVerifierHuggingFace -Codes es -RacinePacks $packsTcBig `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 0) 'tc-big : le run aboutit'
|
||||
Assert ($sortieTcBig -match 'opus-mt-es-fr indisponible') 'tc-big : la famille standard est essayee d''abord'
|
||||
Assert ($sortieTcBig -match 'opus-mt-tc-big-es-fr') 'tc-big : la famille « tc-big » est essayee ensuite'
|
||||
Assert (Test-Path -LiteralPath (Join-Path $packsTcBig 'es\versFrancais\encoder_model.onnx')) `
|
||||
'tc-big : le pack est construit avec le modele de repli'
|
||||
|
||||
# --- 12. Modele familial : un export, deux sens, poids partages ----------
|
||||
# pt et gl n'ont AUCUNE paire bilingue ; it n'en a qu'une. Le modele de
|
||||
# famille (opus-mt-tc-big-itc-itc) les couvre dans les deux sens via un jeton
|
||||
# de cible. Ses 1,15 Go doivent etre ecrits UNE seule fois (commun/) : les
|
||||
# dupliquer par sens doublerait ce que telecharge chaque telephone, ce que
|
||||
# l'utilisateur refuse explicitement.
|
||||
Write-Host ''
|
||||
Write-Host '=== 12. Modele familial (commun/ + modele.json par sens) ==='
|
||||
@'
|
||||
@echo off
|
||||
set MODELE=%~4
|
||||
set OUT=%~5
|
||||
echo %MODELE% | findstr /C:"tc-big-itc-itc" >nul
|
||||
if errorlevel 1 (
|
||||
echo Error: 401 Repository not found 1>&2
|
||||
exit /b 1
|
||||
)
|
||||
mkdir "%OUT%" 2>nul
|
||||
echo fake-encoder > "%OUT%\encoder_model.onnx"
|
||||
echo fake-decoder > "%OUT%\decoder_model.onnx"
|
||||
echo fake-past > "%OUT%\decoder_with_past_model.onnx"
|
||||
echo fake-source > "%OUT%\source.spm"
|
||||
echo fake-target > "%OUT%\target.spm"
|
||||
echo { "piece": 1 } > "%OUT%\vocab.json"
|
||||
echo { "decoder_start_token_id": 1, "eos_token_id": 0 } > "%OUT%\config.json"
|
||||
exit /b 0
|
||||
'@ | Set-Content -LiteralPath (Join-Path $shim 'optimum-cli.cmd') -Encoding ASCII
|
||||
|
||||
$env:HF_HOME = ''
|
||||
$packsFamille = Join-Path $bac 'packs-famille'
|
||||
$sortieFamille = (& $scriptTeste -SansVerifierHuggingFace -Codes pt -RacinePacks $packsFamille `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
|
||||
Assert ($LASTEXITCODE -eq 0) 'Familial : le run aboutit (code 0)'
|
||||
Assert ($sortieFamille -match 'opus-mt-tc-big-itc-itc') 'Familial : le modele de famille est employe'
|
||||
Assert ($sortieFamille -match 'aucune paire bilingue exploitable') 'Familial : le repli est annonce'
|
||||
|
||||
$commun = Join-Path $packsFamille 'pt\commun'
|
||||
foreach ($nom in @('encoder_model.onnx', 'decoder_model.onnx', 'source.spm', 'target.spm', 'vocab.json', 'config.json')) {
|
||||
Assert (Test-Path -LiteralPath (Join-Path $commun $nom)) ("Familial : {0} present dans commun/" -f $nom)
|
||||
}
|
||||
Assert ((@(Get-ChildItem -LiteralPath $packsFamille -Recurse -Filter '*with_past*')).Count -eq 0) `
|
||||
'Familial : decoder_with_past_model.onnx jamais livre'
|
||||
|
||||
foreach ($sens in @('versFrancais', 'depuisFrancais')) {
|
||||
$dossierSens = Join-Path $packsFamille ('pt\' + $sens)
|
||||
Assert (Test-Path -LiteralPath (Join-Path $dossierSens 'modele.json')) ("Familial : pt/{0}/modele.json present" -f $sens)
|
||||
Assert ((@(Get-ChildItem -LiteralPath $dossierSens -Filter '*.onnx')).Count -eq 0) `
|
||||
("Familial : pt/{0} ne duplique AUCUN poids" -f $sens)
|
||||
}
|
||||
|
||||
$metaVers = Get-Content -LiteralPath (Join-Path $packsFamille 'pt\versFrancais\modele.json') -Raw | ConvertFrom-Json
|
||||
$metaDepuis = Get-Content -LiteralPath (Join-Path $packsFamille 'pt\depuisFrancais\modele.json') -Raw | ConvertFrom-Json
|
||||
Assert ($metaVers.jetonCible -eq '>>fra<<') 'Familial : jeton de l''aller = >>fra<<'
|
||||
Assert ($metaDepuis.jetonCible -eq '>>por<<') 'Familial : jeton du retour = >>por<<'
|
||||
Assert ($metaVers.modeleSource -eq 'Helsinki-NLP/opus-mt-tc-big-itc-itc') 'Familial : le depot d''origine est inscrit'
|
||||
Assert ($metaVers.famille -eq 'itc') 'Familial : la famille est inscrite'
|
||||
Assert ($metaVers.version -eq 1) 'Familial : version de metadonnees = 1'
|
||||
|
||||
# Le jeton doit rester LISIBLE : ConvertTo-Json l'ecrirait \u003e\u003efra\u003c\u003c,
|
||||
# et c'est exactement la valeur qu'un humain doit pouvoir relire au diagnostic.
|
||||
$brut = Get-Content -LiteralPath (Join-Path $packsFamille 'pt\versFrancais\modele.json') -Raw
|
||||
Assert ($brut -match '"jetonCible": ">>fra<<"') 'Familial : le jeton est ecrit en clair (pas en \u003e)'
|
||||
|
||||
# Reexport : 1,15 Go ne doivent pas etre repayes a chaque relance.
|
||||
$sortieFamille2 = (& $scriptTeste -SansVerifierHuggingFace -Codes pt -RacinePacks $packsFamille `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' 6>&1 | Out-String)
|
||||
Assert ($sortieFamille2 -match 'familial\), ignore') 'Familial : un pack familial deja present est ignore'
|
||||
Assert ($sortieFamille2 -notmatch 'export : Helsinki') 'Familial : aucun export relance'
|
||||
|
||||
# Une langue NON couverte (uk) ne doit pas se voir inventer un modele familial.
|
||||
$packsSansFamille = Join-Path $bac 'packs-sans-famille'
|
||||
$null = & $scriptTeste -SansVerifierHuggingFace -Codes uk -RacinePacks $packsSansFamille `
|
||||
-OptimumCli 'optimum-cli' -Python 'python' 6>&1
|
||||
Assert ($LASTEXITCODE -eq 1) 'Familial : une langue non couverte echoue toujours (code 1)'
|
||||
Assert (-not (Test-Path -LiteralPath (Join-Path $packsSansFamille 'uk\commun'))) `
|
||||
'Familial : aucune commun/ inventee pour une langue non couverte'
|
||||
|
||||
# Quand les deux paires bilingues existent (es), le familial ne doit PAS etre
|
||||
# employe : il est deux fois plus lourd que les deux bilingues reunis.
|
||||
Assert (-not (Test-Path -LiteralPath (Join-Path $packs 'es\commun'))) `
|
||||
'Familial : pas de commun/ quand les deux paires bilingues existent'
|
||||
}
|
||||
finally {
|
||||
if (Test-Path -LiteralPath $bac) {
|
||||
Remove-Item -LiteralPath $bac -Recurse -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host ("=== RESULTAT : {0} tests, {1} echec(s) ===" -f $tests, $fails)
|
||||
if ($fails -gt 0) { exit 1 } else { exit 0 }
|
||||
@@ -0,0 +1,53 @@
|
||||
# ============================================================
|
||||
# Omika — Validation Phase 5 (poste développeur, 100 % Windows)
|
||||
# ============================================================
|
||||
# N'exécute QUE les projets nécessaires — jamais de build solution
|
||||
# complet (évite NETSDK1139 android/ios, hors périmètre §7).
|
||||
#
|
||||
# Usage :
|
||||
# .\scripts\validate-phase5-dev.ps1 # build parallèle standard
|
||||
# .\scripts\validate-phase5-dev.ps1 -SingleNode # force -m:1 (CI / sandbox sans nœuds MSBuild parallèles)
|
||||
#
|
||||
# NB : AVALONIA_TELEMETRY_OPTOUT=1 désactive la télémétrie build Avalonia
|
||||
# (requis dans certains environnements restreints ; sans effet sinon).
|
||||
|
||||
param(
|
||||
[switch]$SingleNode
|
||||
)
|
||||
|
||||
$ErrorActionPreference = 'Stop'
|
||||
$env:AVALONIA_TELEMETRY_OPTOUT = '1'
|
||||
|
||||
$extra = @()
|
||||
if ($SingleNode) { $extra = @('-m:1') }
|
||||
|
||||
$root = Split-Path -Parent $PSScriptRoot
|
||||
|
||||
# Gate 1 + Gate 2 (§8) : adapter OpenTelemetry, API, Worker
|
||||
$steps = @(
|
||||
@('build', "$root\src\Omika.Observability.External\Omika.Observability.External.csproj", '-c', 'Debug'),
|
||||
@('build', "$root\src\Omika.Api\Omika.Api.csproj", '-c', 'Debug'),
|
||||
@('build', "$root\src\Omika.Worker\Omika.Worker.csproj", '-c', 'Debug'),
|
||||
# Gate 3 (§63) : tests Observability verts
|
||||
@('test', "$root\tests\Omika.Tests\Omika.Tests.csproj", '-c', 'Debug', '--filter', 'FullyQualifiedName~Observability'),
|
||||
# Diagnostic Center (Phase 5, §50)
|
||||
@('build', "$root\src\Omika.Diagnostics.Core\Omika.Diagnostics.Core.csproj", '-c', 'Debug'),
|
||||
@('build', "$root\src\Omika.Diagnostics.Data\Omika.Diagnostics.Data.csproj", '-c', 'Debug'),
|
||||
@('build', "$root\src\Omika.Diagnostics.Worker\Omika.Diagnostics.Worker.csproj", '-c', 'Debug'),
|
||||
@('build', "$root\src\Omika.Diagnostics.Web\Omika.Diagnostics.Web.csproj", '-c', 'Debug'),
|
||||
# Tests Diagnostic Center SANS PostgreSQL réel (§51)
|
||||
@('test', "$root\tests\Omika.Diagnostics.Tests\Omika.Diagnostics.Tests.csproj", '-c', 'Debug')
|
||||
)
|
||||
|
||||
foreach ($step in $steps) {
|
||||
$cmdArgs = $step + $extra
|
||||
Write-Host "> dotnet $($cmdArgs -join ' ')" -ForegroundColor Cyan
|
||||
& dotnet $cmdArgs
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Error "ÉCHEC : dotnet $($cmdArgs -join ' ') (exit $LASTEXITCODE)"
|
||||
exit $LASTEXITCODE
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ''
|
||||
Write-Host 'PHASE 5 — VALIDATION OK' -ForegroundColor Green
|
||||
Reference in New Issue
Block a user