Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 224 additions & 22 deletions Controllers/TransformationExecutionController.cs

Large diffs are not rendered by default.

32 changes: 32 additions & 0 deletions Models/Transformation/PathwayDiagnostic.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
namespace LayoutParserApi.Models.Transformation
{
/// <summary>
/// Diagnóstico estruturado por pathway de <c>POST /api/transformation-execution/execute-candidates</c>
/// (Issue LayoutParserReact #86). Campo ADITIVO — não substitui <see cref="TransformationExecutionCandidatesResponse.Warnings"/>,
/// que continua populado exatamente como hoje por compatibilidade.
///
/// <para>Ver desenho completo em
/// docs/architecture/diagnostico-issue-86-diagnostico-estruturado-execute-candidates.md §4.
/// Este arquivo só define a estrutura; a população dos valores por pathway (sysmiddle/tcl-xsl/
/// ai-fallback) é feita por quem já monta <c>warnings</c>/<c>failureKinds</c> hoje.</para>
/// </summary>
public class PathwayDiagnostic
{
/// <summary>"sysmiddle" | "tcl-xsl" | "ai-fallback".</summary>
public string Pathway { get; set; } = "";

/// <summary>"candidate_generated" | "not_applicable" | "failed" (§4.2 do desenho).
/// String, não enum exposto — permite adicionar valores sem quebrar o contrato.</summary>
public string Status { get; set; } = "";

/// <summary>Taxonomia estável: "no_mapper" | "map_not_found" | "xsl_not_found" |
/// "configuration_error" | "runner_unavailable" | "execution_error" | "not_applicable"
/// (§4.3 do desenho). String, não enum exposto, pelo mesmo motivo de <see cref="Status"/>.</summary>
public string Code { get; set; } = "";

/// <summary>Mensagem legível para o front. SEMPRE passada por
/// <see cref="LayoutParserApi.Services.Transformation.LowCode.LowCodeErrorSanitizer"/> antes de
/// chegar aqui — nunca caminho de disco/detalhe interno cru (§5 do desenho).</summary>
public string Message { get; set; } = "";
}
}
10 changes: 10 additions & 0 deletions Models/Transformation/TransformationCandidate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,15 @@ public class TransformationExecutionCandidatesResponse
public List<TransformationCandidate> Candidates { get; set; } = new();
public string? RecommendedCandidateId { get; set; }
public List<string> Warnings { get; set; } = new();

/// <summary>Diagnóstico estruturado por pathway (Issue LayoutParserReact #86) — ADITIVO,
/// não substitui <see cref="Warnings"/>. Vazio hoje: a população dos valores por pathway
/// (sysmiddle/tcl-xsl/ai-fallback) é feita em cima desta estrutura, ver
/// <see cref="PathwayDiagnostic"/>.</summary>
public List<PathwayDiagnostic> PathwayDiagnostics { get; set; } = new();

/// <summary>CorrelationId da request (<see cref="LayoutParserApi.Services.Logging.CorrelationContext.CurrentId"/>),
/// permite ao suporte cruzar com o log estruturado completo (não sanitizado) desta chamada.</summary>
public string? CorrelationId { get; set; }
}
}
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,48 @@ Without a `groundTruthXml` (State A, "generate from scratch"), the convergence c
| `failed` | Yes — **new in this contract** | Candidates exist, but **none** succeeded — structural failure of the set. Previously this came back as `completed` with every candidate `success=false`, forcing the front to scan the array to infer failure. |
| `not_applicable` / `error` | Yes (only in `/api/parse/upload`'s synchronous response) | `not_applicable`: pathway not eligible (no mapper, non-positional type, empty input). `error`: structural failure processing transformations (e.g. database down) — does not fail the main parse. |

### Diagnóstico estruturado de `execute-candidates` (Issue LayoutParserReact #86) / Structured diagnostics for `execute-candidates`

**🇧🇷** `POST /api/transformationexecution/execute-candidates` ganhou dois campos **aditivos** na resposta (não quebram clientes existentes que ignoram campos desconhecidos): [`pathwayDiagnostics`](Models/Transformation/PathwayDiagnostic.cs) e `correlationId`. Design completo: [`docs/architecture/diagnostico-issue-86-diagnostico-estruturado-execute-candidates.md`](docs/architecture/diagnostico-issue-86-diagnostico-estruturado-execute-candidates.md).

```jsonc
{
"success": true,
"candidates": [],
"recommendedCandidateId": null,
"warnings": ["..."],
"pathwayDiagnostics": [
{ "pathway": "sysmiddle", "status": "not_applicable", "code": "no_mapper", "message": "..." },
{ "pathway": "tcl-xsl", "status": "failed", "code": "map_not_found", "message": "..." }
],
"correlationId": "..."
}
```

**Semântica principal:** `candidates: []` nunca fica sem causa quando a API sabe o motivo — cada pathway avaliado (`sysmiddle`, `tcl-xsl`, e `ai-fallback` quando o fallback automático de IA é disparado) entra em `pathwayDiagnostics` com um veredito, mesmo quando não produz candidato. `warnings` continua populado exatamente como antes, por compatibilidade — `pathwayDiagnostics` é estruturado, não substitui.

| Campo | Valores | Significado |
|-------|---------|-------------|
| `pathway` | `sysmiddle` \| `tcl-xsl` \| `ai-fallback` | Qual dos pathways gerou este diagnóstico. |
| `status` | `candidate_generated` \| `not_applicable` \| `failed` | `candidate_generated`: o pathway produziu ao menos um candidato. `not_applicable`: o pathway não é elegível para este layout/entrada (não é falha). `failed`: o pathway era elegível mas não conseguiu produzir candidato. |
| `code` | `no_mapper` \| `map_not_found` \| `xsl_not_found` \| `configuration_error` \| `runner_unavailable` \| `timeout` \| `not_applicable` \| `execution_error` | Taxonomia estável (string, não enum — permite adicionar valores sem quebrar o contrato). |
| `message` | texto livre | Mensagem legível para exibição no front. |

**Regra de sanitização:** toda `message` em `pathwayDiagnostics` passa por [`LowCodeErrorSanitizer`](Services/Transformation/LowCode/LowCodeErrorSanitizer.cs) antes de chegar ao payload HTTP — **nunca** contém caminho físico de disco nem detalhe interno cru. O detalhe completo (não sanitizado) só existe no log estruturado, correlacionável via `correlationId`.

**🇺🇸** `POST /api/transformationexecution/execute-candidates` gained two **additive** response fields (safe for existing clients that ignore unknown fields): [`pathwayDiagnostics`](Models/Transformation/PathwayDiagnostic.cs) and `correlationId`. Full design: [`docs/architecture/diagnostico-issue-86-diagnostico-estruturado-execute-candidates.md`](docs/architecture/diagnostico-issue-86-diagnostico-estruturado-execute-candidates.md).

**Core semantics:** `candidates: []` is never left without a cause when the API knows the reason — every pathway evaluated (`sysmiddle`, `tcl-xsl`, and `ai-fallback` when the automatic AI fallback fires) gets an entry in `pathwayDiagnostics` with a verdict, even when it produces no candidate. `warnings` remains populated exactly as before for backward compatibility — `pathwayDiagnostics` is structured, it doesn't replace it.

| Field | Values | Meaning |
|-------|--------|---------|
| `pathway` | `sysmiddle` \| `tcl-xsl` \| `ai-fallback` | Which pathway produced this diagnostic. |
| `status` | `candidate_generated` \| `not_applicable` \| `failed` | `candidate_generated`: the pathway produced at least one candidate. `not_applicable`: the pathway isn't eligible for this layout/input (not a failure). `failed`: the pathway was eligible but couldn't produce a candidate. |
| `code` | `no_mapper` \| `map_not_found` \| `xsl_not_found` \| `configuration_error` \| `runner_unavailable` \| `timeout` \| `not_applicable` \| `execution_error` | Stable taxonomy (string, not an exposed enum — new values can be added without breaking the contract). |
| `message` | free text | Human-readable message for front-end display. |

**Sanitization rule:** every `message` in `pathwayDiagnostics` goes through [`LowCodeErrorSanitizer`](Services/Transformation/LowCode/LowCodeErrorSanitizer.cs) before reaching the HTTP payload — it **never** contains a physical disk path or raw internal detail. The full (unsanitized) detail only exists in the structured log, correlatable via `correlationId`.

---

## 8. Configuração / Configuration
Expand Down
9 changes: 9 additions & 0 deletions Services/XmlAnalysis/Models/TransformationPipelineResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ public class TransformationPipelineResult
public string XslPath { get; set; }
public List<string> Errors { get; set; } = new();
public List<string> Warnings { get; set; } = new();

/// <summary>
/// Código estável da causa de falha (Issue LayoutParserReact #86, pathwayDiagnostics).
/// "map_not_found" | "xsl_not_found" | null (sucesso ou erro interno não classificado —
/// nesse caso o chamador cai no "execution_error" genérico). Populado no ponto de origem
/// (<see cref="TransformationPipelineService"/>), nunca inferido depois por regex sobre
/// <see cref="Errors"/> — mesma disciplina já usada para <c>FailureKind</c> no controller.
/// </summary>
public string ErrorCode { get; set; }
public Dictionary<string, string> StepResults { get; set; } = new();
public Dictionary<int, string> SegmentMappings { get; set; } = new();
}
Expand Down
3 changes: 3 additions & 0 deletions Services/XmlAnalysis/TransformationPipelineService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ public async Task<TransformationPipelineResult> TransformXmlToXmlAsync(string xm
if (string.IsNullOrEmpty(xslPath) || !File.Exists(xslPath))
{
result.Success = false;
result.ErrorCode = "xsl_not_found";
result.Errors.Add($"Arquivo XSL não encontrado para transformação {sourceDocumentType} → {targetDocumentType}");
return result;
}
Expand Down Expand Up @@ -151,6 +152,7 @@ private async Task<string> TransformTxtToIntermediateXmlAsync(string txtContent,
var mapContent = await LoadMappingFileAsync(layoutName);
if (mapContent == null)
{
result.ErrorCode = "map_not_found";
result.Errors.Add($"Arquivo MAP não encontrado para layout: {layoutName}");
return null;
}
Expand Down Expand Up @@ -309,6 +311,7 @@ private async Task<string> TransformIntermediateToFinalXmlAsync(string intermedi
var xslPath = FindXslFile("Intermediate", targetDocumentType, layoutName);
if (string.IsNullOrEmpty(xslPath) || !File.Exists(xslPath))
{
result.ErrorCode = "xsl_not_found";
result.Errors.Add($"Arquivo XSL não encontrado para transformação Intermediate → {targetDocumentType}");
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion security-code-scan-baseline.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
{ "code": "SCS0018", "file": "Services/Transformation/TransformationValidatorService.cs", "line": 232 },
{ "code": "SCS0018", "file": "Services/Validation/DocumentMLValidationService.cs", "line": 178 },
{ "code": "SCS0018", "file": "Services/Validation/DocumentMLValidationService.cs", "line": 206 },
{ "code": "SCS0018", "file": "Services/XmlAnalysis/TransformationPipelineService.cs", "line": 390 },
{ "code": "SCS0018", "file": "Services/XmlAnalysis/TransformationPipelineService.cs", "line": 393 },
{ "code": "SCS0018", "file": "Services/XmlAnalysis/XsdValidationService.cs", "line": 221 },
{ "code": "SCS0018", "file": "Services/XmlAnalysis/XsdValidationService.cs", "line": 235 },
{ "code": "SCS0018", "file": "Services/XmlAnalysis/XsdValidationService.cs", "line": 340 }
Expand Down
Loading
Loading