Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Automate survey deployment, data collection, and pipeline management
.claude/skills/brycewang-stanford-data-collection-automation/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -8% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 97% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 45% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 36% | 0% |
| case-16 | ✗→✓ | ▲ Improved | 70% | 0% |
A skill for automating research data collection, survey deployment, and data pipeline management. Covers survey platform APIs, automated data retrieval, quality checks, ETL pipelines, and scheduling for longitudinal studies.
pythonimport os import json import urllib.request import time def export_qualtrics_responses(survey_id: str, file_format: str = "csv") -> str: """ Export survey responses from Qualtrics via API. Args: survey_id: The Qualtrics survey ID (SV_...) file_format: Export format (csv, json, spss) """ api_token = os.environ["QUALTRICS_API_TOKEN"] data_center = os.environ["QUALTRICS_DATACENTER"] base_url = f"https://{data_center}.qualtrics.com/API/v3" headers = { "X-API-TOKEN": api_token, "Content-Type": "application/json" } # Step 1: Start export export_data = json.dumps({ "format": file_format, "compress": False }).encode("utf-8") req = urllib.request.Request( f"{base_url}/surveys/{survey_id}/export-responses", data=export_data, headers=headers ) response = json.loads(urllib.request.urlopen(req).read()) progress_id = response["result"]["progressId"] # Step 2: Poll for completion status = "inProgress" while status == "inProgress": time.sleep(2) req = urllib.request.Request( f"{base_url}/surveys/{survey_id}/export-responses/{progress_id}", headers=headers ) check = json.loads(urllib.request.urlopen(req).read()) status = check["result"]["status"] file_id = check["result"]["fileId"] # Step 3: Download file req = urllib.request.Request( f"{base_url}/surveys/{survey_id}/export-responses/{file_id}/file", headers=headers ) file_data = urllib.request.urlopen(req).read() output_path = f"responses_{survey_id}.{file_format}" with open(output_path, "wb") as f: f.write(file_data) return output_path
pythondef export_redcap_records(api_url: str, fields: list[str] = None) -> list: """ Export records from a REDCap project. Args: api_url: REDCap API endpoint URL fields: List of field names to export (None = all fields) """ api_token = os.environ["REDCAP_API_TOKEN"] data = { "token": api_token, "content": "record", "format": "json", "type": "flat" } if fields: data["fields"] = ",".join(fields) encoded = urllib.parse.urlencode(data).encode("utf-8") req = urllib.request.Request(api_url, data=encoded) response = urllib.request.urlopen(req) return json.loads(response.read())
pythonimport pandas as pd from datetime import datetime def validate_survey_data(df: pd.DataFrame, rules: dict) -> dict: """ Run automated data quality checks on collected data. Args: df: DataFrame of survey responses rules: Dict of column -> validation rule pairs """ issues = [] # Check for duplicates dupes = df.duplicated(subset=["respondent_id"]).sum() if dupes > 0: issues.append(f"Found {dupes} duplicate respondent IDs") # Check completion rates completion = df.notna().mean() low_completion = completion[completion < 0.5] for col in low_completion.index: issues.append(f"Column '{col}' has {low_completion[col]:.0%} completion") # Check value ranges for col, rule in rules.items(): if col not in df.columns: continue if "min" in rule: violations = (df[col] < rule["min"]).sum() if violations > 0: issues.append(f"{violations} values below minimum in '{col}'") if "max" in rule: violations = (df[col] > rule["max"]).sum() if violations > 0: issues.append(f"{violations} values above maximum in '{col}'") # Check for speeding (unusually fast completion) if "duration_seconds" in df.columns: median_time = df["duration_seconds"].median() speeders = (df["duration_seconds"] < median_time * 0.3).sum() if speeders > 0: issues.append(f"{speeders} respondents completed in <30% of median time") return { "n_records": len(df), "n_issues": len(issues), "issues": issues, "timestamp": datetime.now().isoformat() }
pythondef research_etl_pipeline(sources: list[dict], output_dir: str) -> dict: """ Extract, transform, and load research data from multiple sources. Args: sources: List of data source configurations output_dir: Directory to save processed data """ results = {} for source in sources: name = source["name"] # Extract if source["type"] == "qualtrics": raw_path = export_qualtrics_responses(source["survey_id"]) df = pd.read_csv(raw_path) elif source["type"] == "redcap": records = export_redcap_records(source["api_url"]) df = pd.DataFrame(records) elif source["type"] == "csv_url": df = pd.read_csv(source["url"]) else: continue # Transform df = df.dropna(how="all") df.columns = [c.strip().lower().replace(" ", "_") for c in df.columns] # Load timestamp = datetime.now().strftime("%Y%m%d") output_path = f"{output_dir}/{name}_{timestamp}.csv" df.to_csv(output_path, index=False) results[name] = { "records": len(df), "columns": len(df.columns), "output": output_path } return results
bash# Run data collection pipeline daily at 6 AM # crontab -e 0 6 * * * cd /path/to/project && python collect_data.py >> logs/collection.log 2>&1
For longitudinal studies, automate monitoring of:
- Response rates per wave (alert if below threshold)
- Data quality metrics (completion, speeding, straight-lining)
- API quota usage (stay within rate limits)
- Storage usage and backup status
- Participant dropout patternsAlways ensure automated data collection complies with your IRB/ethics board approval. Store API tokens securely using environment variables, never in code. Implement data encryption at rest. Log all data access for audit trails. Respect rate limits on external APIs. Include automated checks for consent status before processing participant data.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-04 | pass→pass | 15,417 | 18,035 | +17% | 1 | 1 | 0% | 2,788 | 5,157 | +85% | 0 | 0 | — |
case-01 | fail→pass | 23,927 | 23,560 | -2% | 1 | 1 | 0% | 5,739 | 5,297 | -8% | 0 | 0 | — |
case-02 | fail→pass | 21,746 | 32,351 | +49% | 1 | 1 | 0% | 4,566 | 8,990 | +97% | 0 | 0 | — |
case-03 | fail→fail | 13,917 | 19,232 | +38% | 1 | 1 | 0% | 3,016 | 5,211 | +73% | 0 | 0 | — |
case-05 | pass→pass | 16,231 | 25,496 | +57% | 1 | 1 | 0% | 2,984 | 5,997 | +101% | 0 | 0 | — |
case-06 | pass→pass | 15,951 | 14,153 | -11% | 1 | 1 | 0% | 3,209 | 4,563 | +42% | 0 | 0 | — |
case-07 | fail→pass | 16,280 | 8,897 | -45% | 1 | 1 | 0% | 2,642 | 3,840 | +45% | 0 | 0 | — |
case-08 | pass→fail | 4,695 | 2,944 | -37% | 1 | 1 | 0% | 719 | 2,251 | +213% | 0 | 0 | — |
case-09 | pass→pass | 7,406 | 8,351 | +13% | 1 | 1 | 0% | 1,505 | 3,113 | +107% | 0 | 0 | — |
case-10 | fail→pass | 10,083 | 7,024 | -30% | 1 | 1 | 0% | 2,113 | 2,875 | +36% | 0 | 0 | — |
case-11 | pass→pass | 10,185 | 6,516 | -36% | 1 | 1 | 0% | 1,743 | 2,755 | +58% | 0 | 0 | — |
case-12 | pass→pass | 14,618 | 6,365 | -56% | 1 | 1 | 0% | 2,215 | 2,830 | +28% | 0 | 0 | — |
case-13 | pass→pass | 18,497 | 18,063 | -2% | 1 | 1 | 0% | 2,785 | 4,504 | +62% | 0 | 0 | — |
case-14 | fail→fail | 18,570 | 18,782 | +1% | 1 | 1 | 0% | 2,859 | 5,303 | +85% | 0 | 0 | — |
case-15 | pass→pass | 9,658 | 3,988 | -59% | 1 | 1 | 0% | 1,881 | 2,517 | +34% | 0 | 0 | — |
case-16 | fail→pass | 15,627 | 11,708 | -25% | 1 | 1 | 0% | 2,535 | 4,304 | +70% | 0 | 0 | — |
case-17 | fail→fail | 6,000 | 5,408 | -10% | 1 | 1 | 0% | 1,196 | 2,871 | +140% | 0 | 0 | — |
case-18 | pass→pass | 6,622 | 4,584 | -31% | 1 | 1 | 0% | 1,181 | 2,691 | +128% | 0 | 0 | — |
case-19 | pass→pass | 14,613 | 9,501 | -35% | 1 | 1 | 0% | 2,515 | 3,546 | +41% | 0 | 0 | — |
case-20 | pass→pass | 14,917 | 8,270 | -45% | 1 | 1 | 0% | 2,398 | 3,552 | +48% | 0 | 0 | — |
case-21 | pass→pass | 9,191 | 6,004 | -35% | 1 | 1 | 0% | 1,341 | 2,684 | +100% | 0 | 0 | — |
case-22 | fail→pass | 14,228 | 2,819 | -80% | 1 | 1 | 0% | 2,519 | 2,382 | -5% | 0 | 0 | — |
case-23 | fail→fail | 15,852 | 11,542 | -27% | 1 | 1 | 0% | 2,460 | 3,679 | +50% | 0 | 0 | — |
case-24 | pass→pass | 13,011 | 13,930 | +7% | 1 | 1 | 0% | 2,283 | 4,047 | +77% | 0 | 0 | — |
case-25 | pass→pass | 10,938 | 9,233 | -16% | 1 | 1 | 0% | 1,711 | 3,761 | +120% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +20 percentage points is the difference between those two pass rates over the 25 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.