Back to catalog

csv-processing

Skill from retex73/music-app

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

# CSV Processing Skill

## Purpose
Master CSV data loading and parsing using PapaParse library for the music-app's tune catalogs.

## PapaParse Integration

### Basic CSV Loading
```javascript
import Papa from 'papaparse';

let tunesData = [];

export const initializeTunesData = async () => {
  if (tunesData.length > 0) return tunesData;

  const response = await fetch('/data/tunes_data.csv');
  const csvText = await response.text();

  return new Promise((resolve, reject) => {
    Papa.parse(csvText, {
      header: true,
      skipEmptyLines: true,
      complete: (results) => {
        tunesData = results.data;
        resolve(tunesData);
      },
      error: (error) => {
        console.error('CSV parsing error:', error);
        reject(error);
      }
    });
  });
};

export const getAllTunes = () => tunesData;
```

### PapaParse Configuration
```javascript
Papa.parse(csvText, {
  header: true,           // First row as keys
  skipEmptyLines: true,   // Ignore blank lines
  dynamicTyping: true,    // Auto-convert numbers
  transformHeader: (h) => h.trim(), // Clean headers
  transform: (value) => value.trim(), // Clean values
  complete: (results) => {
    // results.data - parsed data array
    // results.errors - any parse errors
    // results.meta - metadata
  }
});
```

## Data Structures

### Hatao Tunes CSV
```csv
Set No.,Tune No.,Tune Title,Learning Video,Genre,Added,Rhythm,Key,Mode,Part
1,1,The Butterfly,https://youtube.com/...,Irish Traditional,2023-01-15,Slip Jig,Em,Minor,A
```

### The Session Tunes CSV
```csv
tune_id,setting_id,name,type,meter,mode,abc,date,username
1,12345,The Butterfly,slip jig,9/8,Emin,"X:1\nT:The Butterfly...",2023-01-15,user123
```

## Service Layer Pattern

### Tune Service
```javascript
// src/services/tunesService.js
import Papa from 'papaparse';

let tunesData = [];
let fuseInstance = null;

export const initializeTunesData = async () => {
  if (tunesData.length > 0) {
    return tunesData;
  }

  try {
    const response = await fetch('/data/tunes_data.csv');
    const csvText = await response.text();

    return new Promise((resolve, reject) => {
      Papa.parse(csvText, {
        header: true,
        skipEmptyLines: true,
        complete: (results) => {
          // Validate data
          if (!results.data || results.data.length === 0) {
            reject(new Error('No data in CSV'));
            return;
          }

          tunesData = results.data;

          // Initialize search index
          fuseInstance = new Fuse(tunesData, {
            keys: ['Tune Title', 'Genre', 'Rhythm', 'Key'],
            threshold: 0.3
          });

          resolve(tunesData);
        },
        error: (error) => {
          console.error('CSV parse error:', error);
          reject(error);
        }
      });
    });
  } catch (error) {
    console.error('Error loading CSV:', error);
    throw error;
  }
};

export const getAllTunes = () => tunesData;

export const getTuneById = (id) => {
  return tunesData.find(tune => tune['Tune No.'] === id);
};
```

## Best Practices

✅ **Do:**
- Cache parsed data in module variable
- Validate CSV structure after parsing
- Handle parse errors gracefully
- Use header: true for object output
- Initialize data before use

❌ **Don't:**
- Parse CSV on every component render
- Forget error handling
- Assume CSV is well-formed
- Load large CSVs without progress feedback
- Parse synchronously (blocks UI)

## Data Validation

```javascript
function validateTuneData(data) {
  if (!Array.isArray(data) || data.length === 0) {
    throw new Error('Invalid CSV data: empty or not an array');
  }

  const requiredFields = ['Tune Title', 'Tune No.'];
  const firstRow = data[0];

  for (const field of requiredFields) {
    if (!(field in firstRow)) {
      throw new Error(`Missing required field: ${field}`);
    }
  }

  return true;
}

// Usage
Papa.parse(csvText, {
  complete: (results) => {
    try {
      validateTuneData(results.data);
      tunesData = results.data;
    } catch (error) {
      console.error('Validation failed:', error);
    }
  }
});
```

## Performance Optimization

### Lazy Loading
```javascript
// Load CSV only when needed
export const getTunes = async () => {
  if (tunesData.length === 0) {
    await initializeTunesData();
  }
  return tunesData;
};
```

### Web Worker (for large CSVs)
```javascript
// csv-worker.js
import Papa from 'papaparse';

self.onmessage = (e) => {
  const csvText = e.data;

  Papa.parse(csvText, {
    header: true,
    complete: (results) => {
      self.postMessage({ data: results.data });
    },
    error: (error) => {
      self.postMessage({ error: error.message });
    }
  });
};

// Usage
const worker = new Worker('csv-worker.js');
worker.postMessage(csvText);
worker.onmessage = (e) => {
  if (e.data.error) {
    console.error(e.data.error);
  } else {
    setTunes(e.data.data);
  }
};
```

## Common Issues

**Issue:** CSV not loading
**Solution:** Check file path (/data/ in public folder), verify fetch works

**Issue:** Headers not parsed correctly
**Solution:** Ensure `header: true`, check CSV has header row

**Issue:** Data types wrong (strings instead of numbers)
**Solution:** Use `dynamicTyping: true` option

**Issue:** Blank rows in data
**Solution:** Use `skipEmptyLines: true`

## Project Usage

- **Hatao Tunes**: `/data/tunes_data.csv` → `tunesService.js`
- **Session Tunes**: `/data/session_tunes_data.csv` → `sessionService.js`
- Both use same PapaParse pattern
- CSV files in `/public/data/` for static hosting