Premiere commit - Omika
This commit is contained in:
@@ -0,0 +1,17 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net10.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>false</IsPackable>
|
||||
<RootNamespace>Omika.Verif.Langues</RootNamespace>
|
||||
<AssemblyName>Omika.Verif.Langues</AssemblyName>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Omika.App.Local\Omika.App.Local.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,210 @@
|
||||
using Omika.App.Local.Abstractions;
|
||||
using Omika.App.Local.Models;
|
||||
using Omika.App.Local.Services;
|
||||
using Omika.Core.Contracts.Mobile.Languages;
|
||||
|
||||
namespace Omika.Verif.Langues;
|
||||
|
||||
/// <summary>
|
||||
/// Vérification de la traduction hors ligne sur les VRAIS modèles du catalogue.
|
||||
///
|
||||
/// <para>
|
||||
/// Ce petit exécutable joue le rôle du téléphone : il charge les packs depuis un
|
||||
/// répertoire local et traduit quelques phrases connues dans les deux sens. Il
|
||||
/// sert à prouver, avant de livrer un APK, que le pipeline ONNX produit bien du
|
||||
/// texte — un test unitaire ne peut pas le faire sans embarquer 250 Mo de poids.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Usage : <c>Omika.Verif.Langues.exe [racine_packs]</c>
|
||||
/// (par défaut <c>D:\Avalonia\Omika\packs</c>).
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public static class Program
|
||||
{
|
||||
private static readonly (string Code, SensTraduction Sens, string Texte, string Attendu)[] Essais =
|
||||
[
|
||||
("es", SensTraduction.VersFrancais, "el techo estaba mojado", "Le plafond était mouillé."),
|
||||
("en", SensTraduction.VersFrancais, "The ceiling was wet.", "Le plafond était mouillé."),
|
||||
("de", SensTraduction.VersFrancais, "Die Decke war nass.", ""),
|
||||
("pt", SensTraduction.VersFrancais, "O telhado estava molhado.", "Le toit était humide."),
|
||||
("it", SensTraduction.VersFrancais, "Il soffitto era bagnato.", "Le plafond était humide."),
|
||||
("es", SensTraduction.DepuisFrancais, "Le plafond était mouillé.", "El techo estaba mojado."),
|
||||
("pt", SensTraduction.DepuisFrancais, "Le plafond était mouillé.", "O teto estava molhado."),
|
||||
];
|
||||
|
||||
public static async Task<int> Main(string[] args)
|
||||
{
|
||||
string racine = args.Length > 0 ? args[0] : @"D:\Avalonia\Omika\packs";
|
||||
|
||||
var depot = new DepotDisque(racine);
|
||||
using var service = new OnnxTranslationService(depot, OnnxRuntimeInferenceSessionFactory.Instance);
|
||||
|
||||
Console.WriteLine($"Moteur : {OnnxRuntimeInferenceSessionFactory.Instance.NomMoteur}");
|
||||
Console.WriteLine($"Packs : {racine}");
|
||||
Console.WriteLine();
|
||||
|
||||
int echecs = 0;
|
||||
foreach ((string code, SensTraduction sens, string texte, string attendu) in Essais)
|
||||
{
|
||||
var montre = System.Diagnostics.Stopwatch.StartNew();
|
||||
try
|
||||
{
|
||||
string resultat = await service.TraduireAsync(texte, code, sens);
|
||||
montre.Stop();
|
||||
|
||||
bool boucle = DetecterBoucle(resultat);
|
||||
bool conforme = attendu.Length == 0
|
||||
|| string.Equals(resultat.Trim(), attendu, StringComparison.OrdinalIgnoreCase);
|
||||
if (boucle || !conforme)
|
||||
{
|
||||
echecs++;
|
||||
}
|
||||
|
||||
string verdict = boucle ? "BOUCLE" : conforme ? "OK" : "ECART";
|
||||
Console.WriteLine($"[{verdict,-6}] {code} {sens,-14} {montre.ElapsedMilliseconds,6} ms « {texte} » -> « {resultat} »");
|
||||
if (!conforme && attendu.Length > 0)
|
||||
{
|
||||
Console.WriteLine($" attendu : « {attendu} »");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
echecs++;
|
||||
montre.Stop();
|
||||
Console.WriteLine($"[ERREUR] {code} {sens} : {ex.GetType().Name} — {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine("--- Deuxième passe (modèles déjà chargés : latence réelle perçue) ---");
|
||||
foreach ((string code, SensTraduction sens, string texte, _) in Essais)
|
||||
{
|
||||
var montre = System.Diagnostics.Stopwatch.StartNew();
|
||||
string resultat = await service.TraduireAsync(texte, code, sens);
|
||||
montre.Stop();
|
||||
Console.WriteLine($" {code} {sens,-14} {montre.ElapsedMilliseconds,6} ms « {resultat} »");
|
||||
}
|
||||
|
||||
Console.WriteLine();
|
||||
Console.WriteLine(echecs == 0
|
||||
? "RÉSULTAT : tous les essais sont conformes."
|
||||
: $"RÉSULTAT : {echecs} essai(s) en échec.");
|
||||
return echecs == 0 ? 0 : 1;
|
||||
}
|
||||
|
||||
/// <summary>Vrai si le texte contient un même mot répété 5 fois d'affilée.</summary>
|
||||
private static bool DetecterBoucle(string texte)
|
||||
{
|
||||
string[] mots = texte.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||
int suite = 1;
|
||||
for (int i = 1; i < mots.Length; i++)
|
||||
{
|
||||
suite = string.Equals(mots[i], mots[i - 1], StringComparison.OrdinalIgnoreCase) ? suite + 1 : 1;
|
||||
if (suite >= 5)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Dépôt en lecture seule sur un répertoire de packs : c'est exactement la
|
||||
/// disposition produite par <c>scripts\Exporter-Packs-Langues.ps1</c>.
|
||||
/// </summary>
|
||||
internal sealed class DepotDisque(string racine) : ILanguagePackStore
|
||||
{
|
||||
private static readonly string[] SensPossibles =
|
||||
[
|
||||
LanguageModelDirections.VersFrancais,
|
||||
LanguageModelDirections.DepuisFrancais,
|
||||
LanguageModelDirections.Commun
|
||||
];
|
||||
|
||||
public string Racine { get; } = racine;
|
||||
|
||||
public string CheminFichier(string code, string sens, string nomFichier)
|
||||
=> Path.Combine(Racine, code, sens, nomFichier);
|
||||
|
||||
public Task<PackLangueInstalle?> GetPackInstalleAsync(
|
||||
string code, CancellationToken cancellationToken = default)
|
||||
{
|
||||
string dossier = Path.Combine(Racine, code);
|
||||
if (!Directory.Exists(dossier))
|
||||
{
|
||||
return Task.FromResult<PackLangueInstalle?>(null);
|
||||
}
|
||||
|
||||
var fichiers = new List<FichierModeleInstalle>();
|
||||
foreach (string sens in SensPossibles)
|
||||
{
|
||||
string cheminSens = Path.Combine(dossier, sens);
|
||||
if (!Directory.Exists(cheminSens))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (string fichier in Directory.EnumerateFiles(cheminSens))
|
||||
{
|
||||
fichiers.Add(new FichierModeleInstalle
|
||||
{
|
||||
Sens = sens,
|
||||
NomFichier = Path.GetFileName(fichier),
|
||||
TailleOctets = new FileInfo(fichier).Length,
|
||||
Sha256 = string.Empty
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<PackLangueInstalle?>(new PackLangueInstalle
|
||||
{
|
||||
Code = code,
|
||||
Libelle = code,
|
||||
Version = "1.0.0",
|
||||
InstalleLeUtc = DateTimeOffset.UtcNow,
|
||||
Fichiers = fichiers
|
||||
});
|
||||
}
|
||||
|
||||
public Task<IReadOnlyList<PackLangueInstalle>> ListerPacksInstallesAsync(
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
var packs = new List<PackLangueInstalle>();
|
||||
foreach (string dossier in Directory.EnumerateDirectories(Racine))
|
||||
{
|
||||
PackLangueInstalle? pack = GetPackInstalleAsync(Path.GetFileName(dossier), cancellationToken)
|
||||
.GetAwaiter().GetResult();
|
||||
if (pack is not null)
|
||||
{
|
||||
packs.Add(pack);
|
||||
}
|
||||
}
|
||||
|
||||
return Task.FromResult<IReadOnlyList<PackLangueInstalle>>(packs);
|
||||
}
|
||||
|
||||
public Task<LanguageManifestResponse?> GetIndexAsync(CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult<LanguageManifestResponse?>(null);
|
||||
|
||||
public Task EnregistrerIndexAsync(LanguageManifestResponse manifeste, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task EnregistrerPackAsync(PackLangueInstalle pack, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public Task SupprimerPackAsync(string code, CancellationToken cancellationToken = default)
|
||||
=> Task.CompletedTask;
|
||||
|
||||
public bool FichierPresent(string code, FichierModeleInstalle fichier)
|
||||
=> File.Exists(CheminFichier(code, fichier.Sens, fichier.NomFichier));
|
||||
|
||||
public Task<bool> VerifierFichierAsync(
|
||||
string code, FichierModeleInstalle fichier, CancellationToken cancellationToken = default)
|
||||
=> Task.FromResult(FichierPresent(code, fichier));
|
||||
|
||||
public string PreparerFichierPartiel(string code, string sens, string nomFichier)
|
||||
=> CheminFichier(code, sens, nomFichier + ".partiel");
|
||||
}
|
||||
Reference in New Issue
Block a user