# Project export: Pocket Semester

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: OpenAI Build Week
- Tagline: A semester-runway budget coach that turns everyday student spending into a calmer plan through finals.
- Devpost: https://devpost.com/software/pocket-semester
- GitHub: https://github.com/iDxtry/pocket-semester
- Demo: https://pocket-semester.vercel.app/demo
- Video: https://www.youtube.com/embed/ywQCbqRg0Ng?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — iDxtry (29 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# Pocket Semester

Pocket Semester is a student budget coach built for OpenAI Build Week's **Apps for Your Life** track. Its core question is simple: **will my money last through finals?**

Live demo: [pocket-semester.vercel.app/demo](https://pocket-semester.vercel.app/demo) — no sign-in, fictional data, and resettable browser state.

## What it does

- Add, edit, delete, correct, or CSV-import expenses.
- Show category health and a fixed-cost-aware month-end forecast.
- Learn a signed-in student's corrected merchant categories.
- Test a planned purchase against a deterministic semester runway, then apply concrete tradeoffs to see the covered-through date and finals buffer change.
- Generate clearly labeled AI expense categorization and monthly coaching when Gemini is configured.

The demo uses a fictional student and does not connect to a bank account or request bank credentials. Budgeting guidance is educational only.

## Judge path

1. Open [the public demo](https://pocket-semester.vercel.app/demo).
2. Choose **Add expense**, leave the category on auto, and save it.
3. Review its category, confidence band, category consequence, and live finals-buffer effect.
4. Toggle a runway tradeoff to see the calculated buffer change.
5. Refresh the coach plan while viewing the current month. The result is labeled with its actual provider.

The sample import file is [public/samples/pocket-semester-demo.csv](public/samples/pocket-semester-demo.csv).

## Architecture

- Next.js 16, React 19, TypeScript, and CSS.
- Neon Postgres and Drizzle for private accounts, with app-owned scrypt password hashing and opaque httpOnly sessions.
- Zod for API and structured-output validation.
- Gemini runtime adapter for categorization and coaching; the UI always identifies Gemini, OpenAI, saved merchant rules, or local fallback truthfully.
- Deterministic integer-cents math for forecasts, category totals, and semester runway calculations. AI explains the result; it does not invent financial calculations.

Private API ownership always comes from the server session, not a client-supplied user ID.

## Local setup

Requirements: Node.js 20.9+ and npm.

```bash
npm ci
cp .env.example .env.local
npm run dev
```

Open [http://localhost:3000/demo](http://localhost:3000/demo). For private accounts, set `DATABASE_URL` and run:

```bash
npm run db:push
```

Runtime Gemini configuration is server-only:

```bash
AI_PROVIDER=gemini
GEMINI_API_KEY=your_key_here
GEMINI_MODEL=gemini-3.1-flash-lite
```

Without a provider key, an expense can still be saved with a clearly labeled local category; coach refreshes report that they are unavailable.

An optional OpenAI Responses adapter is included for users with separate API billing. It is not active in the public demo and must never be described as active without a verified live response.

## Verification

```bash
npm test
npm run lint
npm run build
```

Tests cover cents math, forecasts, month states, seasonal demo fixtures, CSV validation, AI schemas, provider provenance, local authentication, and the runway before/after calculation.

## GPT-5.6 and Codex contribution

Build Week provides Codex credits, not OpenAI API credits. Pocket Semester therefore uses **GPT-5.6 through Codex for the substantive build work** and Gemini for the deployed runtime AI. This is intentional and disclosed in the product, README, and demo video.

Codex with GPT-5.6 accelerated the product framing, semester-runway engine, provider boundary, structured schemas, mobile/accessibility improvements, test coverage, visual audits, and final submission materials. The human builder selected the student problem, the money assumptions, privacy boundaries, and the final design direction. See [docs/build-week.md](docs/build-week.md) for the dated build record.

Use the Judge path above to reproduce the public demo. The repository keeps product documentation and the build record rather than a word-for-word submission script or paste-ready application copy.

## Privacy

The public demo is browser-only fictional data. Signed-in accounts store budget records in the configured Neon database. When a student actively requests AI help, Pocket Semester sends only the merchant/description/amount or aggregate budget context needed for that request to the configured provider. See the live [privacy page](https://pocket-semester.vercel.app/privacy).

## License

[MIT](LICENSE)


## Detected evidence (automated analysis)

Indexed codebase: 73 recognized source files, 280 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- SQL (language) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository
- AI coding agent: Codex — evidence: config files committed to the repository

## Codebase structure (from repository index)

### Files (84 of 84)

```
.env.example
.gitignore
.vscode/settings.json
AGENTS.md
CLAUDE.md
docs/build-week.md
drizzle.config.ts
drizzle/0000_uneven_maggott.sql
drizzle/0001_local_auth.sql
drizzle/meta/_journal.json
drizzle/meta/0000_snapshot.json
eslint.config.mjs
LICENSE
middleware.ts
next.config.ts
package.json
playwright.config.ts
postcss.config.mjs
public/samples/pocket-semester-demo.csv
README.md
src/app/api/analyze/route.ts
src/app/api/auth/sign-in/route.ts
src/app/api/auth/sign-out/route.ts
src/app/api/auth/sign-up/route.ts
src/app/api/budgets/route.ts
src/app/api/coach/route.ts
src/app/api/goals/route.ts
src/app/api/imports/transactions/route.ts
src/app/api/onboarding/route.ts
src/app/api/profile/route.ts
src/app/api/transactions/[id]/route.ts
src/app/api/transactions/route.ts
src/app/budgets/page.tsx
src/app/dashboard/page.tsx
src/app/demo/page.tsx
src/app/error.tsx
src/app/globals.css
src/app/goals/page.tsx
src/app/insights/page.tsx
src/app/layout.tsx
src/app/loading.tsx
src/app/not-found.tsx
src/app/onboarding/page.tsx
src/app/page.tsx
src/app/privacy/page.tsx
src/app/settings/page.tsx
src/app/sign-in/[[...sign-in]]/page.tsx
src/app/sign-up/[[...sign-up]]/page.tsx
src/app/transactions/page.tsx
src/components/account-setup-notice.tsx
src/components/account-workspace-page.tsx
src/components/auth-provider.tsx
src/components/auth-shell.tsx
src/components/budget-dashboard.tsx
src/components/budget-snapshot.tsx
src/components/budget-workspace.tsx
src/components/landing-page.tsx
src/components/onboarding-wizard.tsx
src/components/password-auth-form.tsx
src/components/sign-out-button.tsx
src/db/index.ts
src/db/schema.ts
src/lib/ai/provider.ts
src/lib/ai/types.ts
src/lib/auth.ts
src/lib/budget-math.ts
src/lib/budget.ts
src/lib/csv.ts
src/lib/data.ts
src/lib/platform.ts
src/lib/routes.ts
src/lib/runway.ts
src/lib/server/account.ts
src/lib/server/api.ts
src/lib/server/rate-limit.ts
src/lib/validation.ts
tests/ai-provider.test.ts
tests/auth.test.ts
tests/budget-math.test.ts
tests/csv-and-schema.test.ts
tests/e2e/judge-path.spec.ts
tests/forecast-fixed-costs.test.ts
tests/runway.test.ts
tsconfig.json
```

### Dependencies

- package.json: @neondatabase/serverless@^1.1.0, @phosphor-icons/react@^2.1.10, @playwright/test@^1.61.1, @tailwindcss/postcss@^4, @types/node@^20, @types/papaparse@^5.5.2, @types/react@^19, @types/react-dom@^19, drizzle-kit@^0.31.10, drizzle-orm@^0.45.2, eslint@^9, eslint-config-next@16.2.10, geist@^1.7.2, next@16.2.10, papaparse@^5.5.4, react@19.2.4, react-dom@19.2.4, recharts@^3.9.2, tailwindcss@^4, tsx@^4.23.1, typescript@^5, zod@^4.4.3

### Recent commits (newest first)

- Remove unused starter assets
- Merge pull request #8 from iDxtry/codex/judge-closeout
- Fix runway math and judge demo layout
- Merge pull request #7 from iDxtry/codex/dynamic-weekly-digest
- Strengthen judge demo scenario
- Merge pull request #6 from iDxtry/codex/dynamic-weekly-digest
- Make weekly digest data-driven
- style: add standard line-clamp property to spending styles and ignore unknown CSS rules in VS Code settings
- feat: enhance UI with glassmorphism, gradients, and entrance animations throughout the application.
- Merge pull request #5 from iDxtry/agent/pocket-semester-judge-ready
- Strengthen semester runway demo story
- Make demo spending cadence believable
- Fix demo calendar and account controls
- Merge pull request #4 from iDxtry/agent/pocket-semester-judge-ready
- Add semester runway and judge proof
- Make demo months visibly seasonal
- Fix month scoping and date validation bugs
- Make demo months realistic
- Replace Clerk with free local auth
- Fix judge readiness issues

## Key source files (fetched from GitHub, selected and truncated for size)

### CLAUDE.md

```markdown
@AGENTS.md

```

### AGENTS.md

```markdown
<!-- BEGIN:nextjs-agent-rules -->
# This is NOT the Next.js you know

This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
<!-- END:nextjs-agent-rules -->

```

### package.json

```
{
  "name": "pocket-semester",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "eslint",
    "db:generate": "drizzle-kit generate",
    "db:push": "node --env-file=.env.local ./node_modules/drizzle-kit/bin.cjs push",
    "test": "tsx --test tests/*.test.ts",
    "test:e2e": "playwright test"
  },
  "dependencies": {
    "@neondatabase/serverless": "^1.1.0",
    "@phosphor-icons/react": "^2.1.10",
    "@types/papaparse": "^5.5.2",
    "drizzle-orm": "^0.45.2",
    "geist": "^1.7.2",
    "next": "16.2.10",
    "papaparse": "^5.5.4",
    "react": "19.2.4",
    "react-dom": "19.2.4",
    "recharts": "^3.9.2",
    "zod": "^4.4.3"
  },
  "devDependencies": {
    "@playwright/test": "^1.61.1",
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "drizzle-kit": "^0.31.10",
    "eslint": "^9",
    "eslint-config-next": "16.2.10",
    "tailwindcss": "^4",
    "tsx": "^4.23.1",
    "typescript": "^5"
  }
}

```

### src/app/page.tsx

```typescript
import { LandingPage } from "@/components/landing-page";

export default function Home() {
  return <LandingPage />;
}

```

### src/db/index.ts

```typescript
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
import * as schema from "@/db/schema";

function connectionString() {
  return process.env.DATABASE_URL ?? process.env.POSTGRES_URL ?? null;
}

function createDatabase() {
  const url = connectionString();
  if (!url) throw new Error("Pocket Semester database is not configured.");
  return drizzle({ client: neon(url), schema });
}

export type Database = ReturnType<typeof createDatabase>;

let database: Database | undefined;

export function isDatabaseConfigured() {
  return Boolean(connectionString());
}

export function getDb() {
  database ??= createDatabase();
  return database;
}

```

### src/app/layout.tsx

```typescript
import type { Metadata } from "next";
import { GeistSans } from "geist/font/sans";
import { GeistMono } from "geist/font/mono";
import { AuthProvider } from "@/components/auth-provider";
import "./globals.css";

export const metadata: Metadata = {
  title: "Pocket Semester | Student budget coach",
  description: "A student budget coach that shows whether your money will last through finals.",
  metadataBase: new URL("https://pocket-semester.vercel.app"),
  alternates: { canonical: "/" },
  openGraph: { title: "Pocket Semester", description: "A student budget coach that shows whether your money will last through finals.", type: "website", url: "/" },
  twitter: { card: "summary", title: "Pocket Semester", description: "A student budget coach that shows whether your money will last through finals." },
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html
      lang="en"
      className={`${GeistSans.variable} ${GeistMono.variable} h-full antialiased`}
    >
      <body className="min-h-full flex flex-col"><AuthProvider>{children}</AuthProvider></body>
    </html>
  );
}

```

### src/app/goals/page.tsx

```typescript
import { AccountWorkspacePage } from "@/components/account-workspace-page";

export const dynamic = "force-dynamic";

export default function GoalsPage({ searchParams }: { searchParams: Promise<{ month?: string | string[]; category?: string | string[] }> }) {
  return <AccountWorkspacePage view="goals" searchParams={searchParams} />;
}

```

### src/app/budgets/page.tsx

```typescript
import { AccountWorkspacePage } from "@/components/account-workspace-page";

export const dynamic = "force-dynamic";

export default function BudgetsPage({ searchParams }: { searchParams: Promise<{ month?: string | string[]; category?: string | string[] }> }) {
  return <AccountWorkspacePage view="budgets" searchParams={searchParams} />;
}

```

### src/app/insights/page.tsx

```typescript
import { AccountWorkspacePage } from "@/components/account-workspace-page";

export const dynamic = "force-dynamic";

export default function InsightsPage({ searchParams }: { searchParams: Promise<{ month?: string | string[]; category?: string | string[] }> }) {
  return <AccountWorkspacePage view="insights" searchParams={searchParams} />;
}

```

### src/app/settings/page.tsx

```typescript
import { AccountWorkspacePage } from "@/components/account-workspace-page";

export const dynamic = "force-dynamic";

export default function SettingsPage({ searchParams }: { searchParams: Promise<{ month?: string | string[]; category?: string | string[] }> }) {
  return <AccountWorkspacePage view="settings" searchParams={searchParams} />;
}

```

[61 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]