---
name: skraak-propagate-call-classification
description: Auto-classify segments in one filter based on verified classifications from another filter. Use when one ML filter has been reviewed and you want to propagate those classifications to another filter on overlapping segments.
---
# Deduplicate Call Specification
Propagate verified classifications from one filter to another for overlapping segments. This is useful when you have reviewed classifications from one ML model and want to apply those learnings to another model's segments.
## When to Use
- You have reviewed/classified segments for one filter (e.g., `opensoundscape-kiwi-1.2`)
- You want to auto-classify overlapping segments in another filter (e.g., `opensoundscape-kiwi-1.5`)
- Both filters have segments on the same audio files
## Prerequisites
- Folder with `.data` files containing segments from multiple filters
- One filter already reviewed (certainty >= 80 for confirmed calls, certainty = 0 for confirmed noise)
- `./skraak` binary built and available
## Workflow
### Step 1: Identify candidate segments
Run summarise and use jq to find overlapping segments where:
- Source filter has verified classification (certainty >= 80 or = 0)
- Target filter has unverified classification (certainty = 70)
- Segments overlap in time
- Species/calltype differ between filters
```bash
./skraak calls summarise --folder "<folder_path>" > /tmp/summary.json
cat /tmp/summary.json | jq '
.segments |
group_by(.file) |
map({
file: .[0].file,
segments: [.[] | {start: .start_time, end: .end_time, labels: .labels}]
}) |
map({
file: .file,
auto_classify: [
.segments[] as $s1 |
.segments[] as $s2 |
select(
($s1.labels[0].filter == "<source_filter>") and
($s2.labels[0].filter == "<target_filter>") and
# Time overlap
($s1.start | floor) <= ($s2.end | ceil) and
($s2.start | floor) <= ($s1.end | ceil) and
# Source is verified
(($s1.labels[0].certainty >= 80) or ($s1.labels[0].certainty == 0)) and
# Target is unverified
($s2.labels[0].certainty == 70) and
# They differ
(($s2.labels[0].species != $s1.labels[0].species) or ($s2.labels[0].calltype != $s1.labels[0].calltype))
) |
{
file: .file,
source_start: $s1.start,
source_end: $s1.end,
source_species: $s1.labels[0].species,
source_calltype: $s1.labels[0].calltype,
source_certainty: $s1.labels[0].certainty,
target_start: $s2.start,
target_end: $s2.end,
target_species: $s2.labels[0].species,
target_calltype: $s2.labels[0].calltype,
action: (if $s1.labels[0].certainty == 0 then "Noise" else $s1.labels[0].species + "+" + ($s1.labels[0].calltype // "N/A") end)
}
]
}) |
map(select(.auto_classify | length > 0)) |
[.[] | .auto_classify[]]
'
```
### Step 2: Present summary to user
Group results by action type:
| Action | Count | Description |
|--------|-------|-------------|
| GSK+Male from "Don't Know" | N | Target had "Don't Know", source verified GSK Male |
| GSK+Female from "Don't Know" | N | Target had "Don't Know", source verified GSK Female |
| GSK+Duet from "Don't Know" | N | Target had "Don't Know", source verified GSK Duet |
| Calltype correction | N | Both had GSK but different calltypes |
| Noise from "Don't Know" | N | Source confirmed as noise (certainty=0) |
| Weka from "Don't Know" | N | Source identified as Weka |
| Other species | N | Other verified species |
Ask user to confirm before applying changes.
### Step 3: Apply approved changes
Use `skraak calls modify` to update target filter segments:
```bash
# For species+calltype changes
./skraak calls modify --file "<folder>/<file>.wav.data" --reviewer Claude \
--filter <target_filter> --segment <start>-<end> \
--species <Species>+<Calltype> --certainty 80
# For noise
./skraak calls modify --file "<folder>/<file>.wav.data" --reviewer Claude \
--filter <target_filter> --segment <start>-<end> \
--species Noise --certainty 80
# For species without calltype (Weka, etc.)
./skraak calls modify --file "<folder>/<file>.wav.data" --reviewer Claude \
--filter <target_filter> --segment <start>-<end> \
--species <Species> --certainty 80
```
### Step 4: Verify results
```bash
./skraak calls summarise --folder "<folder_path>" --brief
```
Check that both filters now have similar species counts and the "Don't Know" count decreased in the target filter.
## Classification Rules
- **Always use certainty=80** for auto-classified segments (distinguishes from ML=70, human=100)
- **Reviewer**: always "Claude" (indicates LLM-assisted classification)
- **Only modify** segments where target has certainty=70 (unverified)
- **Never modify** segments where target already has certainty >= 80
- **Handle noise carefully**: source certainty=0 means confirmed noise, propagate as Noise species
## Segment Matching
Segments overlap when:
```
floor(source.start) <= ceil(target.end) AND floor(target.start) <= ceil(source.end)
```
The `--segment` parameter uses integer seconds, so floor/ceil handles the matching:
- Source: 362.5-390 matches target: 360-390 via `--segment 362-390`
- Source: 297.5-322.5 matches target: 295-323 via `--segment 297-323`
## Example Output
```
Auto-Classification Candidates for opensoundscape-kiwi-1.5
Based on verified opensoundscape-kiwi-1.2 classifications:
### GSK from "Don't Know" (14 segments)
| File | Segment | Action |
|------|---------|--------|
| ridgeend_20260303_204507 | 297-323 | → GSK+Female |
| ridgeend_20260303_211507 | 292-320 | → GSK+Female |
...
### Calltype Corrections (4 segments)
| File | Segment | Current | Correct |
|------|---------|---------|---------|
| ridgeend_20260304_043007 | 360-388 | GSK+Duet | → GSK+Male |
...
Summary: 27 segments ready for auto-classification
Apply these changes? (y/n)
```
## Notes
- This is a one-way propagation: source filter → target filter
- Run `skraak-check-call-classification` skill first to verify the source filter
- The target filter may have unique segments not in source - these remain unverified
- Species that sound similar (e.g., GSK and LSK) may need human review - flag these for user attention