Skip to content

Commit 1ca37ce

Browse files
authored
Added Experiments (and small fixes) (#65)
1 parent ebf5db5 commit 1ca37ce

File tree

15 files changed

+430
-47
lines changed

15 files changed

+430
-47
lines changed

.cursorrules

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,20 @@
33
## Project Overview
44
This is a Next.js prediction market application built by a **solo founder** that integrates with Polymarket and Colosseum APIs. The app uses Prisma with PostgreSQL for data management.
55

6-
**Infrastructure:** The entire application runs on Vercel infrastructure, including hosting, serverless functions, and database connections.
6+
**Infrastructure:** The entire application runs on Vercel infrastructure, including hosting, serverless functions, and database connections. Please see RUNBOOK.md for more information.
77

8-
**Solo Founder Context:** This project is built and maintained by a single developer. Prioritize:
8+
9+
10+
## High level guidance to Claude on coding and design
11+
**Solo Founder Context**: This project is built and maintained by a single developer. Prioritize:
912
- **Simplicity over complexity** - Choose straightforward solutions that are easy to maintain
10-
- **Proven technologies** - Use well-established libraries and patterns
13+
- **Less code is better** - Choose solutions that involve less custom code where possible.
14+
- **Proven technologies** - Use well-established libraries and patterns
1115
- **Minimal dependencies** - Avoid over-engineering for a 1-person team
1216
- **Clear documentation** - Write code that's easy to understand and modify later
1317
- **Gradual scaling** - Start simple, optimize later when needed
1418

19+
1520
## Code Style & Patterns
1621
- Use TypeScript with strict typing
1722
- Follow Next.js 14+ App Router patterns
@@ -48,7 +53,7 @@ curl -H "Authorization: Bearer $CRON_SECRET" http://localhost:3000/api/cron/upda
4853
- Add new queries to the appropriate query object
4954
- Use transactions for multi-step database operations
5055
- Follow the existing schema patterns (snake_case for DB columns, camelCase for JS)
51-
- Try to use the migration commands in package.json such as "db:migrate:deploy"
56+
- Try to use the migration commands in package.json such as "db:migrate:deploy:dev"
5257
- **Prefer simple queries** - Complex joins can be hard to debug for a solo developer
5358
### Database Operations
5459
- Use commands in package.json where possible for database migration in Dev and Preview builds.

CLAUDE.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,11 @@ BetterAI is a Next.js 15 prediction market application built by a **solo founder
88

99
**Core Value Proposition**: Everyone should be able to access world-class AI models with enriched data through a single click to enhance their prediction market decisions.
1010

11+
12+
## High level guidance to Claude on coding and design
1113
**Solo Founder Context**: This project is built and maintained by a single developer. Prioritize:
1214
- **Simplicity over complexity** - Choose straightforward solutions that are easy to maintain
15+
- **Less code is better** - Choose solutions that involve less custom code where possible.
1316
- **Proven technologies** - Use well-established libraries and patterns
1417
- **Minimal dependencies** - Avoid over-engineering for a 1-person team
1518
- **Clear documentation** - Write code that's easy to understand and modify later
@@ -102,8 +105,8 @@ Follow the project's `.cursorrules` for consistent development:
102105

103106
### Database Migrations
104107
- Use `pnpm run db:migrate:dev` for development migrations
105-
- Use `pnpm run db:migrate:deploy` for production deployments (requires `.env.local`)
106-
- Use `pnpm run db:migrate:status` to check migration status (requires `.env.local`)
108+
- Use `pnpm run db:migrate:deploy:dev` for development deployments (requires `.env.local`)
109+
- Use `pnpm run db:migrate:status:dev` to check migration status (requires `.env.local`)
107110
- **Migration naming**: Provide `--name descriptive_name` to avoid interactive prompts
108111
- **Example**: `pnpm run db:migrate:dev --name add_user_table`
109112
- Always test migrations in development before deploying to production

app/api/experiments/route.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { prisma } from '@/lib/db/prisma'
3+
4+
export async function GET(request: NextRequest) {
5+
try {
6+
// Get experiment summary with accuracy metrics
7+
const experiments = await prisma.prediction.findMany({
8+
where: {
9+
experimentTag: {
10+
not: null
11+
}
12+
},
13+
select: {
14+
experimentTag: true,
15+
experimentNotes: true,
16+
modelName: true,
17+
createdAt: true,
18+
predictionChecks: {
19+
select: {
20+
absDelta: true,
21+
marketClosed: true,
22+
createdAt: true
23+
}
24+
}
25+
},
26+
orderBy: {
27+
createdAt: 'desc'
28+
}
29+
})
30+
31+
// Group by experiment tag and calculate stats
32+
const experimentStats = experiments.reduce((acc: any, pred: any) => {
33+
const tag = pred.experimentTag!
34+
if (!acc[tag]) {
35+
acc[tag] = {
36+
experimentTag: tag,
37+
experimentNotes: pred.experimentNotes,
38+
modelName: pred.modelName,
39+
totalPredictions: 0,
40+
checkedPredictions: 0,
41+
avgAccuracy: null,
42+
firstCreated: pred.createdAt,
43+
lastCreated: pred.createdAt,
44+
}
45+
}
46+
47+
acc[tag].totalPredictions += 1
48+
49+
// Update date range
50+
if (pred.createdAt < acc[tag].firstCreated) {
51+
acc[tag].firstCreated = pred.createdAt
52+
}
53+
if (pred.createdAt > acc[tag].lastCreated) {
54+
acc[tag].lastCreated = pred.createdAt
55+
}
56+
57+
// Calculate accuracy from prediction checks
58+
if (pred.predictionChecks && pred.predictionChecks.length > 0) {
59+
const validChecks = pred.predictionChecks.filter((check: any) =>
60+
check.absDelta !== null && check.marketClosed === true
61+
)
62+
63+
if (validChecks.length > 0) {
64+
acc[tag].checkedPredictions += 1
65+
const avgDelta = validChecks.reduce((sum: number, check: any) =>
66+
sum + parseFloat(check.absDelta.toString()), 0
67+
) / validChecks.length
68+
69+
// Accuracy = 100 - (avgDelta * 100) to convert delta to percentage accuracy
70+
const accuracy = Math.max(0, 100 - (avgDelta * 100))
71+
72+
if (acc[tag].avgAccuracy === null) {
73+
acc[tag].avgAccuracy = accuracy
74+
} else {
75+
// Running average
76+
acc[tag].avgAccuracy = ((acc[tag].avgAccuracy * (acc[tag].checkedPredictions - 1)) + accuracy) / acc[tag].checkedPredictions
77+
}
78+
}
79+
}
80+
81+
return acc
82+
}, {})
83+
84+
const results = Object.values(experimentStats).sort((a: any, b: any) =>
85+
new Date(b.lastCreated).getTime() - new Date(a.lastCreated).getTime()
86+
)
87+
88+
return NextResponse.json({
89+
success: true,
90+
experiments: results
91+
})
92+
} catch (error) {
93+
console.error('Error fetching experiments:', error)
94+
return NextResponse.json(
95+
{ success: false, error: 'Failed to fetch experiments' },
96+
{ status: 500 }
97+
)
98+
}
99+
}

app/experiments/page.tsx

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
'use client'
2+
3+
import { useEffect, useState } from 'react'
4+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
5+
import { Badge } from '@/components/ui/badge'
6+
7+
interface ExperimentStat {
8+
experimentTag: string
9+
experimentNotes: string | null
10+
modelName: string | null
11+
totalPredictions: number
12+
checkedPredictions: number
13+
avgAccuracy: number | null
14+
firstCreated: string
15+
lastCreated: string
16+
}
17+
18+
interface ApiResponse {
19+
success: boolean
20+
experiments: ExperimentStat[]
21+
}
22+
23+
export default function ExperimentsPage() {
24+
const [experiments, setExperiments] = useState<ExperimentStat[]>([])
25+
const [loading, setLoading] = useState(true)
26+
const [error, setError] = useState<string | null>(null)
27+
28+
useEffect(() => {
29+
fetchExperiments()
30+
}, [])
31+
32+
const fetchExperiments = async () => {
33+
try {
34+
const response = await fetch('/api/experiments')
35+
const data: ApiResponse = await response.json()
36+
37+
if (data.success) {
38+
setExperiments(data.experiments)
39+
} else {
40+
setError('Failed to fetch experiments')
41+
}
42+
} catch (err) {
43+
setError('Network error fetching experiments')
44+
} finally {
45+
setLoading(false)
46+
}
47+
}
48+
49+
const formatDate = (dateStr: string) => {
50+
return new Date(dateStr).toLocaleDateString('en-US', {
51+
month: 'short',
52+
day: 'numeric',
53+
hour: '2-digit',
54+
minute: '2-digit'
55+
})
56+
}
57+
58+
const formatAccuracy = (accuracy: number | null) => {
59+
if (accuracy === null) return 'N/A'
60+
return `${accuracy.toFixed(1)}%`
61+
}
62+
63+
if (loading) {
64+
return (
65+
<div className="container mx-auto p-6">
66+
<h1 className="text-3xl font-bold mb-8">🧪 Prediction Experiments</h1>
67+
<div>Loading experiments...</div>
68+
</div>
69+
)
70+
}
71+
72+
if (error) {
73+
return (
74+
<div className="container mx-auto p-6">
75+
<h1 className="text-3xl font-bold mb-8">🧪 Prediction Experiments</h1>
76+
<div className="text-red-600">Error: {error}</div>
77+
</div>
78+
)
79+
}
80+
81+
return (
82+
<div className="container mx-auto p-6">
83+
<h1 className="text-3xl font-bold mb-8">🧪 Prediction Experiments</h1>
84+
85+
{experiments.length === 0 ? (
86+
<Card>
87+
<CardContent className="p-8 text-center text-gray-500">
88+
No experiments found. Run the batch prediction script with --experiment-tag to create your first experiment.
89+
</CardContent>
90+
</Card>
91+
) : (
92+
<div className="grid gap-6">
93+
{experiments.map((exp, index) => (
94+
<Card key={index} className="w-full">
95+
<CardHeader>
96+
<div className="flex items-center justify-between">
97+
<CardTitle className="text-xl font-semibold">
98+
{exp.experimentTag}
99+
</CardTitle>
100+
<div className="flex gap-2">
101+
{exp.modelName && (
102+
<Badge variant="outline">{exp.modelName}</Badge>
103+
)}
104+
<Badge variant="secondary">
105+
{exp.totalPredictions} predictions
106+
</Badge>
107+
</div>
108+
</div>
109+
</CardHeader>
110+
<CardContent>
111+
<div className="grid md:grid-cols-2 lg:grid-cols-4 gap-4">
112+
<div>
113+
<div className="text-sm text-gray-500">Accuracy</div>
114+
<div className="text-2xl font-bold">
115+
{formatAccuracy(exp.avgAccuracy)}
116+
</div>
117+
<div className="text-sm text-gray-500">
118+
({exp.checkedPredictions} checked)
119+
</div>
120+
</div>
121+
122+
<div>
123+
<div className="text-sm text-gray-500">Total Predictions</div>
124+
<div className="text-2xl font-bold">{exp.totalPredictions}</div>
125+
</div>
126+
127+
<div>
128+
<div className="text-sm text-gray-500">Started</div>
129+
<div className="text-lg">{formatDate(exp.firstCreated)}</div>
130+
</div>
131+
132+
<div>
133+
<div className="text-sm text-gray-500">Last Activity</div>
134+
<div className="text-lg">{formatDate(exp.lastCreated)}</div>
135+
</div>
136+
</div>
137+
138+
{exp.experimentNotes && (
139+
<div className="mt-4 p-3 bg-gray-50 rounded-lg">
140+
<div className="text-sm text-gray-500">Notes</div>
141+
<div className="text-sm">{exp.experimentNotes}</div>
142+
</div>
143+
)}
144+
</CardContent>
145+
</Card>
146+
))}
147+
</div>
148+
)}
149+
</div>
150+
)
151+
}

docs/DATABASE_MIGRATIONS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ pnpm prisma migrate status
184184

185185
```bash
186186
# Apply to local DB
187-
pnpm run db:migrate:deploy
187+
pnpm run db:migrate:deploy:dev
188188

189189
# Generate Prisma client
190190
pnpm run db:prisma:generate

docs/MIGRATION_ROLLBACK.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ The GitHub Actions workflow includes basic rollback detection:
9090
1. **Assess the situation**:
9191
```bash
9292
# Check current migration status
93-
pnpm run db:migrate:status
93+
pnpm run db:migrate:status:dev
9494

9595
# Check application health
9696
curl -f $PRODUCTION_URL/api/health
@@ -113,7 +113,7 @@ The GitHub Actions workflow includes basic rollback detection:
113113
4. **Verify rollback**:
114114
```bash
115115
# Check schema state
116-
pnpm run db:migrate:status
116+
pnpm run db:migrate:status:dev
117117

118118
# Test application functionality
119119
# Run critical user flows
@@ -350,7 +350,7 @@ pnpm run db:migrate:dev --name "rollback_${MIGRATION_NAME}"
350350
# scripts/check-db-state.sh
351351

352352
echo "Checking database state..."
353-
pnpm run db:migrate:status
353+
pnpm run db:migrate:status:dev
354354
echo "Checking application health..."
355355
curl -f $NEXT_PUBLIC_APP_URL/api/health
356356
echo "Checking recent migrations..."

0 commit comments

Comments
 (0)