package tools
import (
"sort"
"strings"
"skraak/utils"
)
type CallsSummariseInput struct {
Folder string `json:"folder"`
Brief bool `json:"brief"`
Filter string `json:"filter,omitempty"`
}
type CallsSummariseOutput struct {
Segments []SegmentSummary `json:"segments"`
Folder string `json:"folder"`
DataFilesRead int `json:"data_files_read"`
DataFilesSkipped []string `json:"data_files_skipped"`
TotalSegments int `json:"total_segments"`
Filters map[string]FilterStats `json:"filters"`
ReviewStatus ReviewStatus `json:"review_status"`
Operators []string `json:"operators"`
Reviewers []string `json:"reviewers"`
Error *string `json:"error,omitempty"`
}
type SegmentSummary struct {
File string `json:"file"`
StartTime float64 `json:"start_time"`
EndTime float64 `json:"end_time"`
Labels []LabelSummary `json:"labels"`
}
type LabelSummary struct {
Filter string `json:"filter"`
Certainty int `json:"certainty"`
Species string `json:"species"`
CallType string `json:"calltype,omitempty"`
Comment string `json:"comment,omitempty"`
Bookmark bool `json:"bookmark,omitempty"`
}
type FilterStats struct {
Segments int `json:"segments"`
Species map[string]int `json:"species"`
Calltypes map[string]map[string]int `json:"calltypes,omitempty"` }
type ReviewStatus struct {
Unreviewed int `json:"unreviewed"` Confirmed int `json:"confirmed"` DontKnow int `json:"dont_know"` WithCallType int `json:"with_calltype"`
WithComments int `json:"with_comments"`
Bookmarked int `json:"bookmarked"`
}
func CallsSummarise(input CallsSummariseInput) (CallsSummariseOutput, error) {
var output CallsSummariseOutput
filePaths, err := utils.FindDataFiles(input.Folder)
if err != nil {
errMsg := err.Error()
output.Error = &errMsg
return output, err
}
output.Segments = make([]SegmentSummary, 0)
output.Folder = input.Folder
output.Filters = make(map[string]FilterStats)
output.Operators = make([]string, 0)
output.Reviewers = make([]string, 0)
output.DataFilesSkipped = make([]string, 0)
if len(filePaths) == 0 {
return output, nil
}
operatorSet := make(map[string]bool)
reviewerSet := make(map[string]bool)
summariseFiles(filePaths, input, &output, operatorSet, reviewerSet)
if input.Brief {
for _, fs := range output.Filters {
output.TotalSegments += fs.Segments
}
} else {
output.TotalSegments = len(output.Segments)
}
finaliseSummary(&output, operatorSet, reviewerSet, input.Brief)
return output, nil
}
func summariseFiles(filePaths []string, input CallsSummariseInput, output *CallsSummariseOutput, operatorSet, reviewerSet map[string]bool) {
for _, path := range filePaths {
df, err := utils.ParseDataFile(path)
if err != nil {
output.DataFilesSkipped = append(output.DataFilesSkipped, path)
continue
}
output.DataFilesRead++
trackMeta(df.Meta, operatorSet, reviewerSet)
var relPath string
if !input.Brief {
relPath = extractRelativePath(input.Folder, path)
}
for _, seg := range df.Segments {
filteredLabels := filterLabels(seg.Labels, input.Filter)
if input.Filter != "" && len(filteredLabels) == 0 {
continue
}
updateStatsFromLabels(filteredLabels, output)
if !input.Brief {
output.Segments = append(output.Segments, SegmentSummary{
File: relPath,
StartTime: seg.StartTime,
EndTime: seg.EndTime,
Labels: buildLabelSummaries(filteredLabels),
})
}
}
}
}
func trackMeta(meta *utils.DataMeta, operatorSet, reviewerSet map[string]bool) {
if meta == nil {
return
}
if meta.Operator != "" {
operatorSet[meta.Operator] = true
}
if meta.Reviewer != "" {
reviewerSet[meta.Reviewer] = true
}
}
func filterLabels(labels []*utils.Label, filter string) []*utils.Label {
if filter == "" {
return labels
}
var filtered []*utils.Label
for _, l := range labels {
if l.Filter == filter {
filtered = append(filtered, l)
}
}
return filtered
}
func buildLabelSummaries(labels []*utils.Label) []LabelSummary {
var summaries []LabelSummary
for _, l := range labels {
ls := LabelSummary{
Filter: l.Filter,
Certainty: l.Certainty,
Species: l.Species,
}
if l.CallType != "" {
ls.CallType = l.CallType
}
if l.Comment != "" {
ls.Comment = l.Comment
}
if l.Bookmark {
ls.Bookmark = true
}
summaries = append(summaries, ls)
}
return summaries
}
func updateStatsFromLabels(labels []*utils.Label, output *CallsSummariseOutput) {
for _, l := range labels {
updateFilterStats(l, output)
updateReviewStatus(l, output)
}
}
func updateFilterStats(l *utils.Label, output *CallsSummariseOutput) {
fs, exists := output.Filters[l.Filter]
if !exists {
fs = FilterStats{
Segments: 0,
Species: make(map[string]int),
Calltypes: make(map[string]map[string]int),
}
}
fs.Segments++
fs.Species[l.Species]++
if l.CallType != "" {
if fs.Calltypes[l.Species] == nil {
fs.Calltypes[l.Species] = make(map[string]int)
}
fs.Calltypes[l.Species][l.CallType]++
}
output.Filters[l.Filter] = fs
}
func updateReviewStatus(l *utils.Label, output *CallsSummariseOutput) {
switch l.Certainty {
case 100:
output.ReviewStatus.Confirmed++
case 0:
output.ReviewStatus.DontKnow++
default:
output.ReviewStatus.Unreviewed++
}
if l.CallType != "" {
output.ReviewStatus.WithCallType++
}
if l.Comment != "" {
output.ReviewStatus.WithComments++
}
if l.Bookmark {
output.ReviewStatus.Bookmarked++
}
}
func finaliseSummary(output *CallsSummariseOutput, operatorSet, reviewerSet map[string]bool, brief bool) {
for filter, fs := range output.Filters {
if len(fs.Calltypes) == 0 {
fs.Calltypes = nil
output.Filters[filter] = fs
}
}
for op := range operatorSet {
output.Operators = append(output.Operators, op)
}
for r := range reviewerSet {
output.Reviewers = append(output.Reviewers, r)
}
sort.Strings(output.Operators)
sort.Strings(output.Reviewers)
if !brief {
sort.Slice(output.Segments, func(i, j int) bool {
if output.Segments[i].File != output.Segments[j].File {
return output.Segments[i].File < output.Segments[j].File
}
return output.Segments[i].StartTime < output.Segments[j].StartTime
})
}
}
func extractRelativePath(folder, dataPath string) string {
filename := dataPath
if idx := strings.LastIndex(dataPath, "/"); idx >= 0 {
filename = dataPath[idx+1:]
}
return strings.TrimSuffix(filename, ".data")
}