package trim import ( "context" "encoding/json" "fmt" "os" "sort" "gitea.maximumdirect.net/eric/seriatim/internal/config" "gitea.maximumdirect.net/eric/seriatim/internal/jsonfile" "gitea.maximumdirect.net/eric/seriatim/internal/report" ) type auditReport struct { Operation string `json:"operation"` InputFile string `json:"input_file"` OutputFile string `json:"output_file"` InputSchema string `json:"input_schema"` OutputSchema string `json:"output_schema"` Mode string `json:"mode"` Selector string `json:"selector"` SelectedIDs []int `json:"selected_ids"` AllowEmpty bool `json:"allow_empty"` InputSegmentCount int `json:"input_segment_count"` RetainedSegmentCount int `json:"retained_segment_count"` RemovedSegmentCount int `json:"removed_segment_count"` RemovedInputIDs []int `json:"removed_input_ids"` OldToNewIDMapping []idMapping `json:"old_to_new_id_mapping"` OverlapGroupsRecomputed bool `json:"overlap_groups_recomputed"` } type idMapping struct { OldID int `json:"old_id"` NewID int `json:"new_id"` } // Run executes artifact-level trim orchestration. func Run(ctx context.Context, cfg config.TrimConfig) error { if err := ctx.Err(); err != nil { return err } selector, err := ParseSelector(cfg.Selector) if err != nil { return fmt.Errorf("invalid selector %q: %w", cfg.Selector, err) } data, err := os.ReadFile(cfg.InputFile) if err != nil { return fmt.Errorf("read --input-file %q: %w", cfg.InputFile, err) } artifact, err := ParseArtifactJSON(data) if err != nil { return fmt.Errorf("--input-file %q: %w", cfg.InputFile, err) } inputSegmentCount := artifact.SegmentCount() inputSchema := artifact.Schema mode := ModeKeep if cfg.Mode == "remove" { mode = ModeRemove } trimmed, err := ApplyArtifact(artifact, Options{ Mode: mode, Selector: selector, AllowEmpty: cfg.AllowEmpty, }) if err != nil { return err } outputSchema := artifact.Schema if cfg.OutputSchema != "" { outputSchema = cfg.OutputSchema } outputArtifact, err := ConvertArtifact(trimmed.Artifact, outputSchema) if err != nil { return err } if err := ValidateArtifact(outputArtifact); err != nil { return fmt.Errorf("validate trimmed output: %w", err) } if err := jsonfile.Write(cfg.OutputFile, outputArtifact.Value()); err != nil { return err } if cfg.ReportFile == "" { return nil } audit := auditReport{ Operation: "trim", InputFile: cfg.InputFile, OutputFile: cfg.OutputFile, InputSchema: inputSchema, OutputSchema: outputArtifact.Schema, Mode: cfg.Mode, Selector: cfg.Selector, SelectedIDs: selector.IDs(), AllowEmpty: cfg.AllowEmpty, InputSegmentCount: inputSegmentCount, RetainedSegmentCount: len(trimmed.OldToNewID), RemovedSegmentCount: len(trimmed.RemovedIDs), RemovedInputIDs: append([]int(nil), trimmed.RemovedIDs...), OldToNewIDMapping: orderedIDMapping(trimmed.OldToNewID), OverlapGroupsRecomputed: trimmed.OverlapGroupsRecomputed, } auditJSON, err := json.Marshal(audit) if err != nil { return fmt.Errorf("marshal trim audit report: %w", err) } rpt := report.Report{ Metadata: report.Metadata{ Application: outputArtifact.Application(), Version: outputArtifact.Version(), InputReader: "trim-artifact", InputFiles: []string{cfg.InputFile}, OutputModules: []string{"json"}, }, Events: []report.Event{ report.Info("trim", "trim", fmt.Sprintf("trimmed %d input segment(s) into %d output segment(s) with mode=%s", inputSegmentCount, outputArtifact.SegmentCount(), cfg.Mode)), report.Info("trim", "trim-audit", string(auditJSON)), report.Info("trim", "validate-output", fmt.Sprintf("validated %d output segment(s)", outputArtifact.SegmentCount())), report.Info("output", "json", "wrote transcript JSON"), }, } if err := report.WriteJSON(cfg.ReportFile, rpt); err != nil { return err } return nil } func orderedIDMapping(mapping map[int]int) []idMapping { keys := make([]int, 0, len(mapping)) for oldID := range mapping { keys = append(keys, oldID) } sort.Ints(keys) pairs := make([]idMapping, 0, len(keys)) for _, oldID := range keys { pairs = append(pairs, idMapping{ OldID: oldID, NewID: mapping[oldID], }) } return pairs }