Architecture, file structure, the manage.py menu, and the API reference were each documented in two or three of README.md, SPEC.md, and CLAUDE.md — the same drift trap the SQL just had. SPEC.md, examples/GETTING_STARTED.md, and ui/README.md move into docs/. PERSPECTIVE.md and docs/perspective-pivot.md merge into docs/perspective.md, version rationale first, then the API reference. README.md becomes an entry point that links out, and CLAUDE.md keeps only working rules and non-obvious behaviour, pointing at docs/spec.md for the rest. examples/ keeps just the sample CSV the tutorial loads. Corrections found while consolidating: - the spec's API table was missing 20 routes — every override endpoint, most of /api/stacks, the mapping remap routes, /health. Rebuilt from the route files - the tutorial used port 3000 (default is 3020) and never mentioned Basic auth, so every curl in it would have 401'd - the tutorial and the spec each hand-listed the SQL deploy order; both now point at manage.py, which is where the order actually lives - CLAUDE.md described deduplication as an MD5 hash (it is a plain JSONB object), claimed 5 tables and 4 functions, and told you to run a setup.sh that has not existed for some time Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.3 KiB
Getting Started with Dataflow
This guide walks through a complete example using bank transaction data.
Prerequisites
PostgreSQL running, Node.js 18+, and Python 3.
Step 1: Configure and Deploy
cd /opt/dataflow
npm install
python3 manage.py
Choose option 1. It writes .env, creates the database and user if they don't exist,
then deploys database/schema.sql and the SQL function files in dependency order.
Step 2: Start the API Server
npm start
The server starts on the port set by API_PORT in .env (3020 by default).
Every /api route requires HTTP Basic auth using the credentials set by manage.py
option 9. The examples below omit it for readability — add -u username:password to each
curl, or export it once:
alias dfcurl='curl -u username:password'
GET /health is the one route that needs no auth.
Test it:
curl http://localhost:3020/health
# Should return: {"status":"ok","timestamp":"..."}
Step 3: Create a Data Source
A source defines where data comes from and how to deduplicate it.
curl -X POST http://localhost:3020/api/sources \
-H "Content-Type: application/json" \
-d '{
"name": "bank_transactions",
"constraint_fields": ["date", "description", "amount"]
}'
What this does: Records with the same date + description + amount will be considered duplicates.
Step 4: Create Transformation Rules
Rules extract meaningful data using regex patterns.
Rule 1: Extract merchant name (first part of description)
curl -X POST http://localhost:3020/api/rules \
-H "Content-Type: application/json" \
-d '{
"source_name": "bank_transactions",
"name": "extract_merchant",
"field": "description",
"pattern": "^([A-Z][A-Z ]+)",
"output_field": "merchant",
"sequence": 1
}'
Rule 2: Extract location (city + state pattern)
curl -X POST http://localhost:3020/api/rules \
-H "Content-Type: application/json" \
-d '{
"source_name": "bank_transactions",
"name": "extract_location",
"field": "description",
"pattern": "([A-Z]+) OH",
"output_field": "location",
"sequence": 2
}'
Step 5: Import Data
Import the example CSV file:
curl -X POST http://localhost:3020/api/sources/bank_transactions/import \
-F "file=@examples/bank_transactions.csv"
Response:
{
"success": true,
"imported": 14,
"duplicates": 0,
"log_id": 1
}
Step 6: View Imported Records
curl http://localhost:3020/api/records/source/bank_transactions?limit=5
You'll see the raw imported data. Note that transformed is null - we haven't applied transformations yet!
Step 7: Apply Transformations
curl -X POST http://localhost:3020/api/sources/bank_transactions/transform
Response:
{
"success": true,
"transformed": 14
}
Now check the records again:
curl http://localhost:3020/api/records/source/bank_transactions?limit=2
You'll see the transformed field now contains the original data plus extracted fields like merchant and location.
Step 8: View Extracted Values That Need Mapping
curl http://localhost:3020/api/mappings/source/bank_transactions/unmapped
Response shows extracted merchant names that aren't mapped yet:
[
{"rule_name": "extract_merchant", "extracted_value": "GOOGLE", "record_count": 2},
{"rule_name": "extract_merchant", "extracted_value": "TARGET", "record_count": 2},
{"rule_name": "extract_merchant", "extracted_value": "WALMART", "record_count": 1},
...
]
Step 9: Create Value Mappings
Map extracted values to clean, standardized output:
curl -X POST http://localhost:3020/api/mappings \
-H "Content-Type: application/json" \
-d '{
"source_name": "bank_transactions",
"rule_name": "extract_merchant",
"input_value": "GOOGLE",
"output": {
"vendor": "Google",
"category": "Technology"
}
}'
curl -X POST http://localhost:3020/api/mappings \
-H "Content-Type: application/json" \
-d '{
"source_name": "bank_transactions",
"rule_name": "extract_merchant",
"input_value": "TARGET",
"output": {
"vendor": "Target",
"category": "Retail"
}
}'
curl -X POST http://localhost:3020/api/mappings \
-H "Content-Type: application/json" \
-d '{
"source_name": "bank_transactions",
"rule_name": "extract_merchant",
"input_value": "WALMART",
"output": {
"vendor": "Walmart",
"category": "Groceries"
}
}'
Step 10: Reprocess With Mappings
Clear and reapply transformations to pick up the new mappings:
curl -X POST http://localhost:3020/api/sources/bank_transactions/reprocess
Step 11: View Final Results
curl http://localhost:3020/api/records/source/bank_transactions?limit=5
Now the transformed field contains:
- Original fields (date, description, amount, category)
- Extracted fields (merchant, location)
- Mapped fields (vendor, category from mappings)
Example result:
{
"id": 1,
"data": {
"date": "2024-01-02",
"description": "GOOGLE *YOUTUBE VIDEOS",
"amount": "4.26",
"category": "Services"
},
"transformed": {
"date": "2024-01-02",
"description": "GOOGLE *YOUTUBE VIDEOS",
"amount": "4.26",
"category": "Services",
"merchant": "GOOGLE",
"vendor": "Google",
"category": "Technology"
}
}
Step 12: Test Deduplication
Try importing the same file again:
curl -X POST http://localhost:3020/api/sources/bank_transactions/import \
-F "file=@examples/bank_transactions.csv"
Response:
{
"success": true,
"imported": 0,
"duplicates": 14,
"log_id": 2
}
All records were rejected as duplicates! ✓
Summary
You've now:
- ✅ Created a data source with deduplication rules
- ✅ Defined transformation rules to extract data
- ✅ Imported CSV data
- ✅ Applied transformations
- ✅ Created value mappings for clean output
- ✅ Reprocessed data with mappings
- ✅ Tested deduplication
Next Steps
- Add more rules for other extraction patterns
- Create more value mappings as needed
- Query the
transformeddata for reporting - Import additional CSV files
Useful Commands
# View all sources
curl http://localhost:3020/api/sources
# View source statistics
curl http://localhost:3020/api/sources/bank_transactions/stats
# View all rules for a source
curl http://localhost:3020/api/rules/source/bank_transactions
# View all mappings for a source
curl http://localhost:3020/api/mappings/source/bank_transactions
# Search for specific records
curl -X POST http://localhost:3020/api/records/search \
-H "Content-Type: application/json" \
-d '{
"source_name": "bank_transactions",
"query": {"vendor": "Google"},
"limit": 10
}'
Troubleshooting
API won't start:
- Check
.envfile exists with correct database credentials - Verify PostgreSQL is running:
psql -U postgres -l - Check logs for error messages
Import fails:
- Verify source exists:
curl http://localhost:3020/api/sources - Check CSV format matches expectations
- Ensure constraint_fields match CSV column names
Transformations not working:
- Check rules exist:
curl http://localhost:3020/api/rules/source/bank_transactions - Test regex pattern manually
- Check records have the specified field