package processreport import ( "time" "gitea.maximumdirect.net/eric/audita/internal/core/chunking" "gitea.maximumdirect.net/eric/audita/internal/core/diagnostics" "gitea.maximumdirect.net/eric/audita/internal/core/normalization" "gitea.maximumdirect.net/eric/audita/internal/core/reporting" "gitea.maximumdirect.net/eric/audita/internal/framework/runner" stagewarnings "gitea.maximumdirect.net/eric/audita/internal/framework/warnings" ) // BuildInput contains already-computed process execution facts for report assembly. type BuildInput struct { Status string TranscriptPath string GlossaryPath string OutputPath string Modules []string OutputSchema string ConfigVersion *int StartedAt time.Time CompletedAt time.Time ErrorMessage string ErrorPhase string RunDirectoryPath string NormalizationSummary *normalization.NormalizationSummary ChunkingSummary *chunking.Summary RunOutput *runner.RunOutput } // Build creates the public process report without owning command parsing or config loading. func Build(input BuildInput) reporting.ProcessReport { report := reporting.ProcessReport{ ReportMetadata: reporting.ReportMetadata{ ReportSchemaName: reporting.DefaultProcessReportSchemaName, ReportSchemaVersion: reporting.DefaultProcessReportSchemaVersion, OutputSchema: input.OutputSchema, ConfigVersion: input.ConfigVersion, }, Phase: "default_pipeline", Status: input.Status, Operation: "process", TranscriptPath: input.TranscriptPath, GlossaryPath: input.GlossaryPath, OutputPath: input.OutputPath, Modules: append([]string(nil), input.Modules...), StartedAt: input.StartedAt, CompletedAt: &input.CompletedAt, ErrorPhase: input.ErrorPhase, } if input.RunDirectoryPath != "" { runSucceeded := input.Status == "success" metadata := diagnostics.BuildDiagnosticsMetadata(input.RunDirectoryPath, runSucceeded) report.Diagnostics = &metadata } if input.ErrorMessage != "" { report.ErrorMessage = input.ErrorMessage } if input.NormalizationSummary != nil { report.InputSegmentCount = &input.NormalizationSummary.InputSegmentCount report.NormalizedSegmentCount = &input.NormalizationSummary.OutputSegmentCount report.NormalizationMerges = &input.NormalizationSummary.MergesPerformed report.NormalizationIDReassignments = &input.NormalizationSummary.IDsReassigned report.NormalizationSkipped.DifferentSpeakers = &input.NormalizationSummary.SkippedMerges.DifferentSpeakers report.NormalizationSkipped.GapTooLarge = &input.NormalizationSummary.SkippedMerges.GapTooLarge report.NormalizationSkipped.DurationExceeded = &input.NormalizationSummary.SkippedMerges.DurationExceeded report.NormalizationSkipped.TokenLimitExceeded = &input.NormalizationSummary.SkippedMerges.TokenLimitExceeded } if input.ChunkingSummary != nil { report.Chunking = &reporting.ChunkingSummary{ ChunkCount: input.ChunkingSummary.ChunkCount, MinEstimatedTokens: input.ChunkingSummary.MinEstimatedTokens, MaxEstimatedTokens: input.ChunkingSummary.MaxEstimatedTokens, TotalEstimatedTokens: input.ChunkingSummary.TotalEstimatedTokens, TargetSections: input.ChunkingSummary.TargetSections, MaxSectionTokens: input.ChunkingSummary.MaxSectionTokens, MinSectionTokens: input.ChunkingSummary.MinSectionTokens, } } report.ModulesSummary, report.ModuleResults = buildModuleReporting(input.RunOutput) return report } func buildModuleReporting(runOutput *runner.RunOutput) (*reporting.ModulesSummary, []reporting.ModuleReport) { if runOutput == nil || len(runOutput.ModuleResults) == 0 { return nil, nil } moduleReports := make([]reporting.ModuleReport, 0, len(runOutput.ModuleResults)) summary := &reporting.ModulesSummary{ModuleCount: len(runOutput.ModuleResults)} for _, r := range runOutput.ModuleResults { startedAt := r.StartedAt completedAt := r.CompletedAt moduleReports = append(moduleReports, reporting.ModuleReport{ ModuleKey: r.ModuleKey, ModuleInstance: r.ModuleInstance, ReplacementPolicy: string(r.ReplacementPolicy), Status: r.Status, ProposalCount: r.ProposalCount, Warnings: append([]stagewarnings.StageWarning(nil), r.Warnings...), ValidatorDecisions: mapValidatorDecisions(r.ValidatorDecisions), ValidatorRejected: mapValidatorRejected(r.ValidatorRejected), AppliedChanges: r.AppliedChanges, SkippedChanges: r.SkippedChanges, ErrorMessage: r.ErrorMessage, StartedAt: &startedAt, CompletedAt: &completedAt, }) summary.TotalAppliedChanges += len(r.AppliedChanges) summary.TotalSkippedChanges += len(r.SkippedChanges) + len(r.ValidatorRejected) if r.Status == runner.ModuleStatusFailed && summary.FailedModuleInstance == "" { summary.FailedModuleInstance = r.ModuleInstance } } return summary, moduleReports } func mapValidatorDecisions(in []runner.ValidatorDecisionRecord) []reporting.ValidatorDecisionReport { if len(in) == 0 { return nil } out := make([]reporting.ValidatorDecisionReport, len(in)) for i, d := range in { out[i] = reporting.ValidatorDecisionReport{ ValidatorName: d.ValidatorName, ProposalIndex: d.ProposalIndex, Approved: d.Approved, ReasonCode: d.ReasonCode, Message: d.Message, DiagnosticArtifactPath: d.DiagnosticArtifactPath, } } return out } func mapValidatorRejected(in []runner.ValidatorRejectedChange) []reporting.ValidatorRejectedReport { if len(in) == 0 { return nil } out := make([]reporting.ValidatorRejectedReport, len(in)) for i, d := range in { out[i] = reporting.ValidatorRejectedReport{ ValidatorName: d.ValidatorName, ProposalIndex: d.ProposalIndex, ModuleKey: d.ModuleKey, ModuleInstance: d.ModuleInstance, TargetSegmentID: d.TargetSegmentID, OriginalText: d.OriginalText, CorrectedText: d.CorrectedText, ReasonCode: d.ReasonCode, Message: d.Message, } } return out }