A free, open-source tool that processes CSV files, runs statistical analysis, and generates journalist-style narrative stories using AI.
CSV Storyteller is an automated intelligence tool that transforms raw spreadsheets into readable, compelling narratives. Built for analysts, journalists, marketers, and founders, the application takes any uploaded CSV file, processes key statistical metrics directly in the browser, and hands those insights to an AI engine to draft a polished story.
The platform operates across three specific analysis modes: Marketing, Startup, and Finance. Each mode applies a different analytical lens and tonal perspective to the data, ensuring the resulting narrative matches the context of the domain.
Every generated output strictly follows a professional journalistic structure: a catchy headline, a summarizing lede, a detailed body story thoroughly grounded in the data, and a memorable kicker.
Analysis Engine
- Automatic numeric column detection
- Statistical computation: mean, median, min, max, std deviation, trend
- Anomaly detection via z-score (flags > 2.0)
- Peak and largest-drop identification
- Data strength classification (strong / moderate / weak)
AI Narrative
- Mode-specific journalistic prompting (Marketing / Startup / Finance)
- Anti-hallucination constraints (every claim must cite a data point)
- No-speculation enforcement
- Graceful fallback for low-signal data
Product
- No account required
- Raw CSV never stored — only computed summary stats sent to backend
- Copy to clipboard + download as .txt
- MIT Licensed — self-hostable
| Layer | Technology | Purpose |
|---|---|---|
| Frontend | HTML, CSS, Vanilla JavaScript | Zero-build, fast-loading client interface |
| CSV Parsing | PapaParse | Secure, browser-side data extraction |
| Backend | Node.js, Express.js | API routing, AI orchestration, and security layer |
| AI | Google Gemini API | Natural language narrative generation |
| Database | Turso (libSQL) | Edge database storing generated stories and metadata |
| Security | Helmet.js, express-rate-limit | HTTP header protection and endpoint throttling |
| Validation | Zod | Strict schema requirements for incoming JSON payloads |
csv-storyteller/
├── frontend/
│ ├── index.html # Main application interface and form
│ ├── about.html # Project background and methodology
│ ├── pricing.html # Free software declaration page
│ ├── styles.css # Global stylesheet and CSS variables
│ └── script.js # Client-side validation, rendering, and fetch logic
├── src/
│ ├── server.js # Express application entry point
│ ├── config.js # Environment variable validation and export
│ ├── routes/
│ │ └── analyze.js # Core HTTP endpoint logic for story generation
│ ├── analysis/
│ │ ├── index.js # Central exporter for analysis algorithms
│ │ ├── marketing.js # Marketing metrics analyzer
│ │ ├── startup.js # Startup metrics analyzer
│ │ └── finance.js # Finance metrics analyzer
│ ├── prompts/
│ │ └── index.js # Mode-specific AI instructions and constraints
│ ├── db/
│ │ ├── index.js # Turso database connection handler
│ │ └── migrations.js # Schema creation and sync logic
│ ├── middleware/
│ │ ├── rateLimiter.js # Global and endpoint-specific memory limiters
│ │ ├── validate.js # Zod schema interceptor and payload evaluator
│ │ └── errorHandler.js # Dedicated error masking and HTTP response mapping
│ └── utils/
│ ├── stats.js # Reusable mathematical calculation algorithms
│ └── logger.js # Environment-aware, sanitized console logging
├── .env.example # Template for required environment variables
├── .gitignore # Excluded paths and secret files
├── package.json # Backend dependencies and run scripts
└── README.md # Project documentation and guide
- Node.js v18+
- A Gemini API key (via Google AI Studio)
- A Turso database account (via turso.tech)
- Git
- Clone the repository
git clone https://github.com/cold-cofffeee/CSV-Storyteller.git- Install backend dependencies
cd CSV-Storyteller
npm install- Configure environment variables
cp .env.example .env-
Populate your variables inside
.env:GEMINI_API_KEY(Your Google AI authentication token)TURSO_DATABASE_URL(Your Turso instance URL)TURSO_AUTH_TOKEN(Your Turso access token)PORT(Defaults to 3000)ALLOWED_ORIGIN(Defaults to http://localhost:5500 for local development)NODE_ENV(Defaults to development)
-
Run the database migration to create the stories table
npm run migrate- Start the backend server
npm run dev- Serve the frontend application
Use the VSCode "Live Server" extension on the
frontend/folder, or run:
npx serve frontendThe app will be running at http://localhost:5500 (frontend) and http://localhost:3000 (backend).
| Variable | Required | Description |
|---|---|---|
GEMINI_API_KEY |
Yes | Token for Google's Gemini models |
TURSO_DATABASE_URL |
Yes | Endpoint format: libsql://your-db-name.turso.io |
TURSO_AUTH_TOKEN |
Yes | Valid access token generated for your Turso DB |
PORT |
No | Server listen port. Defaults to 3000 |
ALLOWED_ORIGIN |
Yes | Exact domain allowed for CORS requests (e.g., frontend host) |
NODE_ENV |
Yes | development or production to control logging/stacktraces |
Note: Never commit your .env file. It is already in .gitignore.
- Navigate to netlify.com/drop.
- Drag and drop your
/frontenddirectory directly into the browser. - Set the backend URL in
script.jsbefore deploying, or use a_redirectsproxy file to route traffic seamlessly.
- Connect your GitHub repository to Render as a Web Service.
- Build command:
npm install - Start command:
node src/server.js - Use the Render dashboard to input your environment variables (Gemini Key, Turso URLs, etc.).
- Copy the backend service URL safely generated by Render, and paste it securely into your Netlify frontend.
Tip: Set
NODE_ENV=productionin Render's environment variables. This disables stack traces in error responses.
Never expose .env files in production environments; always provision sensitive keys securely via your host's dedicated environment variables manager dashboard.
Method: POST
Headers:
Content-Type: application/json
Request Body:
{
"mode": "marketing",
"data": [
{ "month": "Jan", "sales": 400 },
{ "month": "Feb", "sales": 600 }
],
"headers": ["month", "sales"],
"rowCount": 2
}Success Response (200 OK):
{
"headline": "A Surprising Shift in Q1 Momentum",
"lede": "Early signals indicate unexpected sales behavior emerging from the data.",
"story": "The dataset reflects robust progression across key indicators...",
"kicker": "Only time will tell if this trajectory holds true."
}Error Responses:
| Status | Error Code | Meaning |
|---|---|---|
| 400 | Bad Request | The JSON payload failed Zod schema validation (e.g., missing headers). |
| 422 | Unprocessable Entity | The payload structure was correct, but business logic blocked it (e.g., too many rows). |
| 429 | Too Many Requests | The global or route-specific rate limits were exceeded. |
| 500 | Internal Server Error | Gemini generation failed, DB operations failed, or an unknown exception occurred. |
We are constantly looking to expand the analytics engine and welcome contributions from everyone!
How to contribute:
- Fork the repository
- Create a feature branch:
git checkout -b feature/your-feature-name - Commit your changes:
git commit -m 'Add: your feature description' - Push to the branch:
git push origin feature/your-feature-name - Open a Pull Request
What we'd love help with:
- New analysis modes (e.g., HR, E-commerce, Healthcare)
- Additional language support
- A richer stats engine (seasonality detection, correlation)
- UI improvements and accessibility
- Test coverage
For major changes, please open an issue first to discuss what you'd like to change.
This project is licensed under the MIT License. See the LICENSE file for details.
Built and maintained by @cold-cofffeee
If you find this useful, consider starring the repo ★
CSV Storyteller — Free, forever. MIT Licensed.


