Choose how to run this agent
Download Agent
Choose how you want to use this agent:
Use Security Settings Key (Recommended)
Use the API key you've already saved in Security Settings. Quick and convenient!
- No need to re-enter API key
- Works offline after download
- Centralized key management
No API key found in Security Settings. Add one now
Enter API Key Manually
Enter your API key now for this specific agent download.
- Use different key for this agent
- One-time use (not saved)
- Works offline after download
Configure Agent Encryption
Description
Based on the Student Marks CSV Analyst template.
What this agent can do
My Student Marks CSV Analyst is built from the Student Marks CSV Analyst template. Runs fully on your own device: llama.cpp compiled to WebAssembly, GPU-accelerated through WebGPU, with no API key and no server. After the one-time model download it works offline. Can run on OpenAI models with your own API key, encrypted in your browser. Can run on Anthropic Claude models with your own API key, encrypted in your browser.
Source Code
import csv
import io
import json
import math
import statistics
from datetime import date
def _kind(values: list[str], row_count: int) -> str:
if not values:
return 'empty'
try:
numbers = [float(value) for value in values]
if all(math.isfinite(number) for number in numbers):
return 'numeric'
except ValueError:
pass
try:
for value in values:
date.fromisoformat(value)
return 'date'
except ValueError:
pass
distinct = len(set(values))
if distinct < row_count and distinct <= min(12, max(2, row_count // 2)):
return 'categorical'
return 'text'
def analyze_csv(csv_text: str) -> str:
"""Calculate CSV column types, statistics, missing values, and three grounded insights.
Args:
csv_text: Comma-separated dataset with a header row and up to 1000 data rows.
"""
if len(csv_text) > 100000:
raise ValueError('CSV exceeds the 100,000-character limit.')
try:
records = list(csv.reader(io.StringIO(csv_text.lstrip('\ufeff'), newline=''), strict=True))
except csv.Error as exc:
raise ValueError('CSV syntax error: ' + str(exc)) from exc
records = [row for row in records if any(cell.strip() for cell in row)]
if len(records) < 2:
raise ValueError('Provide a header row and at least one data row.')
names = [cell.strip() for cell in records[0]]
if not names or len(names) > 30 or any(not name for name in names):
raise ValueError('Use 1–30 columns, each with a nonempty header.')
if len(set(names)) != len(names):
raise ValueError('CSV column headers must be unique.')
rows = [[cell.strip() for cell in row] for row in records[1:]]
if len(rows) > 1000:
raise ValueError('Use no more than 1,000 data rows.')
if any(len(row) != len(names) for row in rows):
raise ValueError('Every data row must have the same number of cells as the header.')
columns = []
for index, name in enumerate(names):
all_values = [row[index] for row in rows]
present = [value for value in all_values if value != '']
missing = len(rows) - len(present)
column = {'name': name, 'type': _kind(present, len(rows)),
'missing': missing, 'missing_pct': round(100 * missing / len(rows), 2),
'unique': len(set(present))}
if column['type'] == 'numeric':
numbers = [float(value) for value in present]
low, high = min(numbers), max(numbers)
column['stats'] = {'count': len(numbers), 'mean': round(statistics.mean(numbers), 2),
'median': round(statistics.median(numbers), 2),
'min': round(low, 2), 'max': round(high, 2),
'range': round(high - low, 2)}
columns.append(column)
missing_total = sum(col['missing'] for col in columns)
if missing_total:
most_missing = max(columns, key=lambda col: col['missing'])
first = (f"{missing_total} of {len(rows) * len(names)} cells are missing "
f"({100 * missing_total / (len(rows) * len(names)):.1f}%). "
f"{most_missing['name']} has the most: {most_missing['missing']} "
f"({most_missing['missing_pct']:.1f}% of rows).")
else:
first = f"No missing values: all {len(rows) * len(names)} cells are filled."
numeric = [col for col in columns if col['type'] == 'numeric']
if len(numeric) >= 2:
top = max(numeric, key=lambda col: col['stats']['mean'])
bottom = min(numeric, key=lambda col: col['stats']['mean'])
second = (f"Among numeric columns, {top['name']} has the highest observed mean "
f"({top['stats']['mean']:.2f}); {bottom['name']} has the lowest "
f"({bottom['stats']['mean']:.2f}). Compare means only if their units match.")
elif numeric:
col = numeric[0]
second = (f"{col['name']} averages {col['stats']['mean']:.2f} across "
f"{col['stats']['count']} nonempty values (median {col['stats']['median']:.2f}).")
else:
second = f"This dataset has {len(rows)} rows and no fully numeric columns for numeric summaries."
if numeric:
widest = max(numeric, key=lambda col: col['stats']['range'])
third = (f"{widest['name']} has the widest observed numeric range: "
f"{widest['stats']['min']:.2f} to {widest['stats']['max']:.2f} "
f"(difference {widest['stats']['range']:.2f}).")
else:
varied = max(columns, key=lambda col: col['unique'])
third = (f"{varied['name']} has {varied['unique']} distinct nonempty values, "
f"the highest distinct-value count among the columns.")
result = {'row_count': len(rows), 'column_count': len(names),
'missing_cells': missing_total, 'columns': columns,
'insights': [first, second, third], 'preview': rows[:8]}
return json.dumps(result, ensure_ascii=False, allow_nan=False)