Claude
Skills
Sign in
Back

check-completeness

Included with Lifetime
$97 forever

Analyze collection completeness against canonical discography and generate prioritized gap report

General

What this skill does


# check-completeness

Analyze collection completeness by comparing actual inventory against canonical discography, identify gaps, score coverage, and generate prioritized acquisition plans.

## Purpose

Answer collection completeness questions:
- "How complete is my [artist] collection?"
- "What am I missing?"
- "What should I acquire next?"
- "What legendary performances am I missing?"
- "Where should I upgrade quality?"

Produces comprehensive gap reports with actionable acquisition strategies prioritized by importance, availability, and cost.

## Parameters

### Required

**`<artist>`** - Artist name (quoted if contains spaces)
- Matched against collection directory structure
- Used to fetch canonical discography from MusicBrainz
- Example: `"Pink Floyd"` or `"The Beatles"`

### Optional

**`--collection <path>`** - Collection root directory
- Default: Current working directory
- Must contain artist subdirectories
- Example: `/mnt/media/Music`

**`--gaps-only`** - Show only missing items, skip completeness scoring
- Faster execution when you just need gap list
- Useful for quick "what's missing?" queries

**`--priority <level>`** - Filter gaps by priority level
- Values: `high`, `medium`, `low`, `all` (default: `all`)
- Example: `--priority high` shows only critical gaps

**`--output <file>`** - Write report to file instead of stdout
- Format determined by extension: `.yaml`, `.json`, `.md`
- Default: stdout (terminal display)
- Example: `--output gaps-report.yaml`

**`--canonical-source <source>`** - Canonical discography source
- Values: `musicbrainz` (default), `discogs`, `allmusic`
- Falls back to secondary sources if primary fails
- Example: `--canonical-source discogs`

**`--include-legendary`** - Include legendary/bootleg content in analysis
- Default: Official releases only
- Adds section for high-priority unofficial recordings
- Requires legendary catalog file or web search

**`--quality-threshold <level>`** - Minimum quality for "complete" status
- Values: `lossless`, `320kbps`, `256kbps`, `any` (default: `any`)
- Example: `--quality-threshold lossless` treats 320kbps as needing upgrade

**`--create-gap-notes`** - Generate GAP-NOTE.md files for missing items
- Creates placeholder files in artist directory structure
- Each contains acquisition strategy and priority
- Enables parallel gap-fill workflow

**`--incremental`** - Incremental update mode
- Loads previous state file if exists
- Only re-fetches canonical if >7 days old
- Faster for routine monitoring

**`--cost-estimate`** - Include cost estimates in report
- Estimates based on typical pricing for format/availability
- Adds budget planning section to output
- Example range: "$15-25 for used CD"

## Workflow

### 1. Load Collection Inventory

```bash
# Use existing scan results if available
SCAN_FILE=".media-curator/scans/${ARTIST_SLUG}/scan-latest.json"

if [[ -f "$SCAN_FILE" ]]; then
  INVENTORY=$(cat "$SCAN_FILE")
else
  # Trigger fresh scan
  /scan-collection "$ARTIST" --format json --output "$SCAN_FILE"
  INVENTORY=$(cat "$SCAN_FILE")
fi
```

Inventory provides:
- All albums/releases present in collection
- Track counts and file formats
- Quality levels (lossless/lossy bitrates)
- File sizes and directory structure

### 2. Fetch Canonical Discography

```bash
# Check for cached canonical data
CANONICAL_FILE=".media-curator/completeness/${ARTIST_SLUG}/canonical.yaml"
CACHE_AGE_DAYS=$((( $(date +%s) - $(stat -c %Y "$CANONICAL_FILE" 2>/dev/null || echo 0) ) / 86400))

if [[ $CACHE_AGE_DAYS -gt 7 ]] || [[ ! -f "$CANONICAL_FILE" ]]; then
  # Fetch fresh canonical discography
  # MusicBrainz API example:
  MBID=$(musicbrainz-lookup "$ARTIST" --type artist --format json | jq -r '.artists[0].id')
  RELEASES=$(musicbrainz-releases "$MBID" --format json)

  # Parse into canonical.yaml structure
  echo "$RELEASES" | parse-canonical > "$CANONICAL_FILE"
fi
```

Canonical discography includes:
- Studio albums with track counts and years
- EPs and singles with A/B sides
- Official live albums
- Compilations (filtered by novelty)
- Official box sets

**MusicBrainz advantages:**
- Comprehensive and accurate
- Includes release dates and track counts
- Distinguishes editions (original, remaster, deluxe)
- Free API with rate limiting

**Discogs alternative:**
- More complete for rare/indie releases
- Includes pressing details and variants
- Better for vinyl collectors
- Requires API key

### 3. Build Extended Content Catalog

If `--include-legendary` flag set:

```bash
# Load legendary performances catalog
LEGENDARY_FILE=".media-curator/completeness/${ARTIST_SLUG}/legendary.yaml"

if [[ ! -f "$LEGENDARY_FILE" ]]; then
  # Web search for "artist legendary performances bootleg"
  # Or load from community-maintained catalog
  # Or use private tracker tags (if integrated)

  fetch-legendary-catalog "$ARTIST" > "$LEGENDARY_FILE"
fi
```

Extended catalog includes:
- Notable covers (tribute albums, sessions)
- Soundtrack contributions
- Collaboration tracks (appears on other artists' albums)
- Legendary live performances (soundboard, historical significance)
- Unreleased demos and outtakes

### 4. Compare Inventory to Canonical

```yaml
# Comparison logic pseudocode
for release in canonical_releases:
  match = find_in_inventory(release.title, release.year)

  if match:
    if match.track_count == release.track_count:
      release.status = "complete"
    else:
      release.status = "partial"
      release.missing_tracks = release.track_count - match.track_count

    release.quality = match.quality
    release.source = match.source
  else:
    release.status = "missing"
```

**Fuzzy matching rules:**
- Tolerate minor title differences ("The Album" vs "Album, The")
- Match by year if title ambiguous
- Prefer exact track count match over year match
- Flag multiple matches as potential duplicates

### 5. Score Completeness

```python
# Weighted scoring formula
weights = {
  'studio_albums': 0.40,
  'eps': 0.15,
  'singles': 0.15,
  'live_albums': 0.10,
  'notable_covers': 0.10,
  'legendary': 0.10
}

component_scores = {}
for category, weight in weights.items():
  total = len(canonical[category])
  complete = sum(1 for item in canonical[category] if item.status == 'complete')
  partial = sum(0.5 for item in canonical[category] if item.status == 'partial')

  component_scores[category] = ((complete + partial) / total * 100) if total > 0 else 100

overall_score = sum(component_scores[cat] * weights[cat] for cat in weights)

# Quality bonus
lossless_pct = (lossless_count / total_count) * 100
if lossless_pct > 80:
  overall_score += 5
```

### 6. Identify and Prioritize Gaps

```yaml
# Gap prioritization logic
for release in canonical_releases:
  if release.status == "missing" or release.status == "partial":
    priority = calculate_priority(release)
    availability = check_availability(release)
    cost = estimate_cost(release, availability)

    gaps.append({
      'item': release.title,
      'type': release.type,
      'year': release.year,
      'priority': priority,
      'availability': availability,
      'estimated_cost': cost,
      'reason': priority_reason(release)
    })

gaps.sort(key=lambda x: (priority_order[x['priority']], x['year']))
```

**Priority calculation:**

```python
def calculate_priority(release):
  score = 0

  # Type importance
  if release.type == 'studio_album':
    score += 40
  elif release.type in ['ep', 'single']:
    score += 15
  elif release.type == 'live_album':
    score += 10

  # Critical acclaim (if available)
  if release.rating and release.rating > 8.0:
    score += 20

  # Classic period (artist-specific)
  if release.year in artist_classic_years:
    score += 15

  # Completes a set
  if is_part_of_trilogy(release) and have_other_parts(release):
    score += 10

  # Historical significance
  if release.legendary or release.historically_significant:
    score += 25

  # Assign tier
  if score >= 60:
    return "HIGH"
  elif score >= 30:
    return "MEDIUM"
  e

Related in General