2026-05-08 14:53:59 +00:00
package cli
import (
"encoding/json"
"fmt"
"os"
2026-05-08 14:56:24 +00:00
"sort"
2026-05-08 14:53:59 +00:00
"github.com/spf13/cobra"
"gitea.maximumdirect.net/eric/seriatim/internal/config"
"gitea.maximumdirect.net/eric/seriatim/internal/report"
triminternal "gitea.maximumdirect.net/eric/seriatim/internal/trim"
)
2026-05-08 14:56:24 +00:00
type trimAuditReport 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 [ ] trimIDMapping ` json:"old_to_new_id_mapping" `
OverlapGroupsRecomputed bool ` json:"overlap_groups_recomputed" `
}
type trimIDMapping struct {
OldID int ` json:"old_id" `
NewID int ` json:"new_id" `
}
2026-05-08 14:53:59 +00:00
func newTrimCommand ( ) * cobra . Command {
var opts config . TrimOptions
cmd := & cobra . Command {
Use : "trim" ,
Short : "Trim an existing seriatim transcript artifact by segment ID" ,
RunE : func ( cmd * cobra . Command , args [ ] string ) error {
trimOpts := opts
if ! cmd . Flags ( ) . Changed ( "output-schema" ) {
trimOpts . OutputSchema = ""
}
cfg , err := config . NewTrimConfig ( trimOpts )
if err != nil {
return err
}
selector , err := triminternal . 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 := triminternal . ParseArtifactJSON ( data )
if err != nil {
return fmt . Errorf ( "--input-file %q: %w" , cfg . InputFile , err )
}
2026-05-08 14:56:24 +00:00
inputSegmentCount := artifact . SegmentCount ( )
inputSchema := artifact . Schema
2026-05-08 14:53:59 +00:00
mode := triminternal . ModeKeep
if cfg . Mode == "remove" {
mode = triminternal . ModeRemove
}
trimmed , err := triminternal . ApplyArtifact ( artifact , triminternal . Options {
Mode : mode ,
Selector : selector ,
AllowEmpty : cfg . AllowEmpty ,
} )
if err != nil {
return err
}
outputSchema := artifact . Schema
if cfg . OutputSchema != "" {
outputSchema = cfg . OutputSchema
}
outputArtifact , err := triminternal . ConvertArtifact ( trimmed . Artifact , outputSchema )
if err != nil {
return err
}
if err := triminternal . ValidateArtifact ( outputArtifact ) ; err != nil {
return fmt . Errorf ( "validate trimmed output: %w" , err )
}
if err := writeOutputJSON ( cfg . OutputFile , outputArtifact . Value ( ) ) ; err != nil {
return err
}
if cfg . ReportFile != "" {
2026-05-08 14:56:24 +00:00
audit := trimAuditReport {
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 )
}
2026-05-08 14:53:59 +00:00
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 {
2026-05-08 14:56:24 +00:00
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 ) ) ,
2026-05-08 14:53:59 +00:00
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
} ,
}
flags := cmd . Flags ( )
flags . StringVar ( & opts . InputFile , "input-file" , "" , "input seriatim transcript artifact JSON file" )
flags . StringVar ( & opts . OutputFile , "output-file" , "" , "output transcript JSON file" )
flags . StringVar ( & opts . ReportFile , "report-file" , "" , "optional report JSON file" )
flags . StringVar ( & opts . Keep , "keep" , "" , "segment ID selector to keep (for example: 1-10,15)" )
flags . StringVar ( & opts . Remove , "remove" , "" , "segment ID selector to remove (for example: 1-10,15)" )
flags . StringVar ( & opts . OutputSchema , "output-schema" , "" , "optional output JSON schema override: seriatim-minimal, seriatim-intermediate, or seriatim-full" )
flags . BoolVar ( & opts . AllowEmpty , "allow-empty" , false , "allow trimming to an empty transcript" )
return cmd
}
func writeOutputJSON ( path string , value any ) error {
file , err := os . Create ( path )
if err != nil {
return err
}
defer file . Close ( )
enc := json . NewEncoder ( file )
enc . SetIndent ( "" , " " )
return enc . Encode ( value )
}
2026-05-08 14:56:24 +00:00
func orderedIDMapping ( mapping map [ int ] int ) [ ] trimIDMapping {
keys := make ( [ ] int , 0 , len ( mapping ) )
for oldID := range mapping {
keys = append ( keys , oldID )
}
sort . Ints ( keys )
pairs := make ( [ ] trimIDMapping , 0 , len ( keys ) )
for _ , oldID := range keys {
pairs = append ( pairs , trimIDMapping {
OldID : oldID ,
NewID : mapping [ oldID ] ,
} )
}
return pairs
}