# Project export: Ant

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: ANT turns everyday receipts into clear, evidence-backed insights—helping people notice spending patterns, make healthier choices, and take small actions that add up.
- Devpost: https://devpost.com/software/42-futures
- GitHub: https://github.com/sqrl13/ant-purchase-intelligence
- Demo: https://github.com/sqrl13/ant-purchase-intelligence/releases/tag/v1.0.0
- Video: https://www.youtube.com/embed/XjpkB7XjmV8?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 1 GitHub contributor(s) — sqrl13 (3 commits)

## Devpost submission (written by the team)

No Devpost description available.

## README (from the GitHub repository)

# ANT — Purchase intelligence you can verify

ANT is a mobile purchase-intelligence companion for the everyday spending people rarely remember clearly: a coffee between meetings, a Friday snack, a small top-up at the supermarket, one more thing in the basket. None of those moments seems important alone. Across weeks, they can quietly shape someone’s budget, shopping routine and confidence in the choices they are trying to make.

ANT turns **confirmed receipt data** into understandable, evidence-backed discoveries. It helps a person see where small purchases are accumulating, recognise recurring products before an unnecessary repeat shop, spot changes in the cost of their usual basket, and turn a pattern they care about into a practical goal. Instead of showing a vague score or demanding that users log every habit, it works from receipts they already have.

The problem ANT solves is not a lack of data—it is the gap between what people think they bought and what their receipts can actually show. A person may feel that they are spending carefully, buying fewer snacks, or planning healthier shops, yet still miss the recurring purchases hidden in a busy week. ANT makes those patterns visible early enough to support a more intentional next decision.

Every conclusion is explainable: ANT shows the period analysed, the receipts used, the calculation and the limitation. It only describes what was purchased; it never claims to know who consumed an item, whether it was shared, or what it means for someone’s health. The result is a gentle, useful mirror—not a judgement.

This is a runnable Android submission for **OpenAI Build Week · Apps for Your Life**.

## Why ANT

Most spending tools show a total after the money is gone. Most wellbeing tools ask people to remember every detail of their habits. ANT starts with something people already have: receipts.

Each insight is designed to be inspectable. ANT shows the period analysed, supporting receipts, calculation and limitation. It can say what was purchased; it cannot know who consumed it, whether it was shared, or what it means for someone’s health.

## Judge quick start

| What | Details |
|---|---|
| Platform | Android 8.0+ (API 26+) |
| Test build | `artifacts/hackathon/ANT-hackathon-demo.apk` — upload this exact file to a public release link before submitting |
| Network | Not required for the judge route |
| Account / API key | Not required |
| Demo data | Synthetic only; no personal receipts or real merchant brands |

Install the APK, then follow this route:

1. Complete or skip onboarding.
2. Open **Scan** and choose **Use prepared demo receipt**.
3. Select **Process receipt**.
4. In **Review receipt**, correct the highlighted uncertain product and confirm it.
5. Open **See evidence** to inspect the receipts, calculation and limitation.
6. Open a likely-needed prediction and add it to the shopping list.

The critical route is covered by Android instrumentation testing.

## What is implemented

- Kotlin + Jetpack Compose Android application with a complete mobile flow.
- Editable receipt review, local quality checks, duplicate protection and Room persistence.
- Deterministic Kotlin calculations for discoveries, evidence, category spending and likely-needed products.
- Evidence screens that expose source receipts, comparison logic and claim limitations.
- Purchase-safe nutrition language: no diagnosis, calorie claim or assumed consumption.
- Local-first demo mode that works without credentials or a deployed backend.
- A production backend template using the OpenAI Responses API with structured receipt extraction. Its configured default is `gpt-5.6`; API credentials never enter the APK.
- Shared Kotlin Multiplatform domain contracts for future iOS work.

## How we used Codex and GPT‑5.6

The product owner set the problem, tone, user safeguards and evidence standard: ANT must be a gentle mirror, not a scorecard. Codex accelerated implementation and review across the Android architecture, receipt-review flow, deterministic evidence model, tests, accessibility checks, release audit and submission materials.

GPT‑5.6 is configured in the production receipt-extraction backend as the structured multimodal model. The submitted APK deliberately runs a transparent synthetic local flow so judges can test it without credentials or network access. Before submitting, replace the placeholders below only with the real Codex thread evidence and `/feedback` Session ID.

## Submission fields to complete before Devpost

| Field | Value |
|---|---|
| Public repository | [github.com/sqrl13/ant-purchase-intelligence](https://github.com/sqrl13/ant-purchase-intelligence) |
| Public Android test build | [Download ANT Build Week Demo v1.0.0](https://github.com/sqrl13/ant-purchase-intelligence/releases/download/v1.0.0/ANT-hackathon-demo.apk) |
| Public YouTube demo (< 3 minutes, English audio) | `REPLACE_WITH_VIDEO_URL` |
| Codex `/feedback` Session ID | `019f61f9-caae-7722-a7b2-ca5621e24106` |
| Codex model shown by the qualifying thread | `REPLACE_WITH_VERIFIED_GPT_5_6_EVIDENCE` |

Do not state that a particular Codex model was used unless the qualifying thread proves it. The backend configuration alone does not prove the model used by a Codex session.

## Build from source

Requirements: JDK 17, Android SDK 36 and Android Studio compatible with AGP 9.2.x.

```powershell
.\gradlew.bat :app:testDebugUnitTest
.\gradlew.bat :app:lintDebug
.\gradlew.bat :app:assembleDebug
```

To package the test build and SHA-256 manifest:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\package-hackathon-apk.ps1
```

To run the full repository checks:

```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\verify-hackathon.ps1
```

## Repository map

| Path | Purpose |
|---|---|
| `app/` | Android app: Compose UI, Room data layer, receipt flow and tests. |
| `shared/` | Kotlin Multiplatform domain contracts and rules. |
| `composeApp/`, `iosApp/` | KMP/iOS foundation for the long-term product. |
| `backend/` | Optional production backend template; never needed for the offline demo. |
| `docs/HACKATHON_SUBMISSION.md` | Copy-ready English Devpost description and testing instructions. |
| `docs/HACKATHON_VIDEO_RUNBOOK.md` | Exact capture route and video safety checklist. |
| `docs/HACKATHON_COMPLIANCE.md` | Completed safeguards and entrant-owned external steps. |
| `docs/HACKATHON_BUILD_LOG.md` | Meaningful Build Week extension record. |
| `SUBMISSION_MANIFEST.md` | Exact repository contents and Devpost items submitted outside the repository. |
| `docs/PUBLISHING_HANDOFF.md` | Exact GitHub Release and YouTube upload handoff. |

## Safety and privacy

Receipt images remain local in the demo. Insights use confirmed purchases only. ANT never treats an extraction as truth, never embeds an AI key in the mobile app, and never turns a purchase into a medical or moral claim.

## Licence

ANT is released under the [MIT License](LICENSE). It permits use, modification and distribution of this source code while retaining the copyright and licence notice.


## Detected evidence (automated analysis)

Indexed codebase: 106 recognized source files, 494 KB.
- Kotlin (language) — detected in the code
- OpenAI (technology) — detected in the code
- PostgreSQL (technology) — detected in the code
- SQL (language) — detected in the code
- Swift (language) — detected in the code
- TypeScript (language) — detected in the code
- Docker (technology) — claimed on Devpost, not found in the code
- Node.js (technology) — claimed on Devpost, not found in the code

## Codebase structure (from repository index)

### Files (120 of 144)

```
.gitignore
app/build.gradle.kts
app/proguard-rules.pro
app/schemas/com.ant.intelligence.data.local.AntDatabase/1.json
app/schemas/com.ant.intelligence.data.local.AntDatabase/2.json
app/schemas/com.ant.intelligence.data.local.AntDatabase/3.json
app/src/androidTest/assets/com.ant.intelligence.data.local.AntDatabase/1.json
app/src/androidTest/assets/com.ant.intelligence.data.local.AntDatabase/2.json
app/src/androidTest/kotlin/com/ant/intelligence/AccessibilityContractTest.kt
app/src/androidTest/kotlin/com/ant/intelligence/CriticalPathTest.kt
app/src/androidTest/kotlin/com/ant/intelligence/MigrationTest.kt
app/src/androidTest/kotlin/com/ant/intelligence/ReceiptQualityAnalyzerTest.kt
app/src/androidTest/kotlin/com/ant/intelligence/RepositoryIntegrationTest.kt
app/src/main/AndroidManifest.xml
app/src/main/kotlin/com/ant/intelligence/AntAndroidApp.kt
app/src/main/kotlin/com/ant/intelligence/AntApplication.kt
app/src/main/kotlin/com/ant/intelligence/app/AppModule.kt
app/src/main/kotlin/com/ant/intelligence/core/designsystem/Theme.kt
app/src/main/kotlin/com/ant/intelligence/core/image/ReceiptQualityAnalyzer.kt
app/src/main/kotlin/com/ant/intelligence/core/model/Models.kt
app/src/main/kotlin/com/ant/intelligence/core/model/ReceiptDocument.kt
app/src/main/kotlin/com/ant/intelligence/data/AntRepository.kt
app/src/main/kotlin/com/ant/intelligence/data/local/AntDao.kt
app/src/main/kotlin/com/ant/intelligence/data/local/AntDatabase.kt
app/src/main/kotlin/com/ant/intelligence/data/local/Entities.kt
app/src/main/kotlin/com/ant/intelligence/data/PreferencesRepository.kt
app/src/main/kotlin/com/ant/intelligence/data/remote/ProductionServices.kt
app/src/main/kotlin/com/ant/intelligence/data/remote/ReceiptExtractionService.kt
app/src/main/kotlin/com/ant/intelligence/data/TelemetryRepository.kt
app/src/main/kotlin/com/ant/intelligence/data/worker/ReceiptProcessingWorker.kt
app/src/main/kotlin/com/ant/intelligence/domain/InsightEngine.kt
app/src/main/kotlin/com/ant/intelligence/feature/AppViewModels.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/AccountPrivacyScreens.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/Common.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/GoalsProfileScreens.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/HomeScreen.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/InsightScreens.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/OnboardingScreen.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/PredictionScreen.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/ReceiptHistoryScreen.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/ScanScreens.kt
app/src/main/kotlin/com/ant/intelligence/feature/ui/WeeklyReportScreen.kt
app/src/main/kotlin/com/ant/intelligence/MainActivity.kt
app/src/main/res/drawable/ic_splash.xml
app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
app/src/main/res/mipmap-anydpi/ic_launcher.xml
app/src/main/res/values/colors.xml
app/src/main/res/values/strings.xml
app/src/main/res/values/themes.xml
app/src/main/res/xml/data_extraction_rules.xml
app/src/test/kotlin/com/ant/intelligence/core/ReceiptExtractionContractTest.kt
app/src/test/kotlin/com/ant/intelligence/domain/InsightEngineTest.kt
backend/.env.example
backend/docker-compose.yml
backend/Dockerfile
backend/migrations/001_production.sql
backend/migrations/002_receipt_extraction_idempotency.sql
backend/migrations/003_legal_acceptance.sql
backend/package.json
backend/pnpm-workspace.yaml
backend/src/auth.ts
backend/src/config.ts
backend/src/contracts.ts
backend/src/crypto.ts
backend/src/legal.ts
backend/src/main.ts
backend/src/migrate-cli.ts
backend/src/migrate.ts
backend/src/openai.ts
backend/src/server.ts
backend/test/contracts.test.ts
backend/test/production-config.test.ts
backend/tsconfig.json
build.gradle.kts
composeApp/build.gradle.kts
composeApp/src/androidMain/AndroidManifest.xml
composeApp/src/androidMain/kotlin/com/ant/intelligence/MainActivity.kt
composeApp/src/androidMain/res/values/styles.xml
composeApp/src/commonMain/kotlin/com/ant/intelligence/App.kt
composeApp/src/commonMain/kotlin/com/ant/intelligence/data/DemoRepository.kt
composeApp/src/commonMain/kotlin/com/ant/intelligence/domain/InsightEngine.kt
composeApp/src/commonMain/kotlin/com/ant/intelligence/domain/Models.kt
composeApp/src/commonTest/kotlin/com/ant/intelligence/domain/InsightEngineTest.kt
composeApp/src/iosMain/kotlin/com/ant/intelligence/MainViewController.kt
docs/01_PROJECT_VISION.md
docs/02_PRODUCT_REQUIREMENTS.md
docs/03_USER_EXPERIENCE.md
docs/04_DESIGN_SYSTEM.md
docs/05_DATABASE_SCHEMA.md
docs/06_TECHNICAL_ARCHITECTURE.md
docs/07_AI_SYSTEM.md
docs/08_USER_STORIES_AND_TESTS.md
docs/09_HACKATHON_DEMO.md
docs/10_BUILD_PLAN.md
docs/ACCESSIBILITY_AUDIT.md
docs/HACKATHON_BUILD_LOG.md
docs/HACKATHON_CODEX_COLLABORATION.md
docs/HACKATHON_COMPLIANCE.md
docs/HACKATHON_PITCH.md
docs/HACKATHON_SUBMISSION_CHECKLIST.md
docs/HACKATHON_SUBMISSION.md
docs/HACKATHON_VIDEO_RUNBOOK.md
docs/HACKATHON_VIDEO_SCRIPT.md
docs/IMPLEMENTATION_STATUS.md
docs/PUBLISHING_HANDOFF.md
docs/README.md
docs/RELEASE_RUNBOOK.md
gradle.properties
gradle/libs.versions.toml
gradle/wrapper/gradle-wrapper.properties
gradlew
gradlew.bat
iosApp/iosApp.xcodeproj/project.pbxproj
iosApp/iosApp/ContentView.swift
iosApp/iosApp/Info.plist
iosApp/iosApp/iOSApp.swift
LICENSE
README.md
scripts/create-upload-key.ps1
scripts/package-hackathon-apk.ps1
[24 more files omitted for size]
```

### Dependencies

- backend/package.json: @fastify/helmet@^13.0.2, @fastify/multipart@^9.2.1, @fastify/rate-limit@^10.3.0, @types/node@^24.10.0, @types/pg@^8.15.5, argon2@^0.43.1, fastify@^5.6.2, jose@^6.1.0, openai@^6.17.0, pg@^8.16.3, sharp@^0.34.4, tsx@^4.20.6, typescript@^5.9.3, zod@^4.1.12

### Recent commits (newest first)

- Document Codex feedback session
- Add public repository and APK links
- Initial OpenAI Build Week submission

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

### SUBMISSION_MANIFEST.md

```markdown
# ANT submission manifest

This repository is the code and documentation package for the OpenAI Build Week committee. It is intentionally separate from the video upload and Devpost account fields.

## Included in the repository

- Runnable Android source code in `app/`.
- Kotlin Multiplatform domain foundation in `shared/`, `composeApp/` and `iosApp/`.
- Optional production-backend source in `backend/`; it is not required for the offline judge route.
- Android instrumentation, unit and receipt-corpus tests.
- Synthetic prepared receipt fixture only; no personal receipt or production secret.
- English judge instructions and evidence in `README.md` and `docs/HACKATHON_*.md`.
- `artifacts/hackathon/ANT-hackathon-demo.apk` and `SHA256SUMS.txt` as the upload-ready Android test build.

## Submitted outside the repository

| Item | Owner action |
|---|---|
| Public code URL | Create/push the repository with the included MIT licence, or share private read access with the two addresses specified in the rules. |
| Public test-build URL | Upload the APK to a public GitHub Release or equivalent free link. |
| Public YouTube video | Upload the final English, audio-enabled demonstration under three minutes. |
| Codex Session ID | Run `/feedback` in the qualifying Codex thread and paste the real Session ID into Devpost. |
| Devpost submission | Select **Apps for Your Life**, paste the English description and enter all public URLs. |

## Required final checks

1. Run `powershell -ExecutionPolicy Bypass -File .\scripts\verify-hackathon.ps1`.
2. Install the packaged APK and rehearse the route in `docs/HACKATHON_VIDEO_RUNBOOK.md`.
3. Check that the video shows the same build, contains no third-party trademarks or copyrighted music, and includes English audio.
4. Complete the placeholders in the root `README.md` with the actual public links and verified Codex evidence.

The official rules, not this file, are the source of truth: https://openai.devpost.com/rules

```

### docs/ACCESSIBILITY_AUDIT.md

```markdown
# Auditoría de accesibilidad Android

## Implementado

- Componentes Material 3 con semántica y objetivos táctiles mínimos proporcionados por la librería.
- Descripciones en botones de navegación, atrás, eliminación, cámara y acciones solo con icono.
- Iconos decorativos excluidos con `contentDescription = null` para evitar ruido.
- Texto en `sp`, layouts desplazables y sin orientación fija.
- Estados expresados también con texto, no únicamente con color.
- Errores accionables y confirmaciones destructivas explícitas.
- Recorrido crítico automatizado mediante el árbol semántico de Compose.

## Matriz manual obligatoria antes de publicar

- TalkBack: onboarding, registro, escáner inteligente de Google Play Services, fallback CameraX/galería, recorte/reordenación, revisión de cobertura y de cada línea, confirmación, evidencia, sync, exportación y borrado.
- Tamaños de fuente 100 %, 150 % y 200 %; Display size grande.
- Contraste claro/oscuro y corrección de color.
- Switch Access, teclado externo y Voice Access.
- Pantallas 320 dp, teléfono estándar, tablet y modo apaisado.
- Animaciones desactivadas/reducidas y haptics opcionales.
- Prueba con personas usuarias de tecnologías de asistencia.

Registrar dispositivo, Android, versión ANT, hallazgo, severidad, evidencia, corrección y retest. No marcar esta auditoría como completa únicamente por pasar lint.

```

### backend/Dockerfile

```
FROM node:22-alpine AS build
WORKDIR /app
RUN npm install --global pnpm@11.7.0
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml tsconfig.json ./
RUN pnpm install --frozen-lockfile
COPY src ./src
COPY migrations ./migrations
RUN pnpm run build && pnpm prune --prod
FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY --from=build /app/node_modules ./node_modules
COPY --from=build /app/package.json ./package.json
COPY --from=build /app/dist ./dist
COPY --from=build /app/migrations ./migrations
USER node
EXPOSE 8080
CMD ["sh", "-c", "node dist/src/migrate-cli.js && exec node dist/src/main.js"]

```

### backend/package.json

```
{
  "name": "ant-production-backend",
  "private": true,
  "version": "1.0.0",
  "type": "module",
  "engines": { "node": ">=22" },
  "scripts": {
    "dev": "tsx watch src/main.ts",
    "build": "tsc -p tsconfig.json",
    "start": "node dist/src/main.js",
    "migrate": "tsx src/migrate-cli.ts",
    "test": "tsx --test test/**/*.test.ts",
    "check": "npm run build && npm test"
  },
  "dependencies": {
    "@fastify/helmet": "^13.0.2",
    "@fastify/multipart": "^9.2.1",
    "@fastify/rate-limit": "^10.3.0",
    "argon2": "^0.43.1",
    "fastify": "^5.6.2",
    "jose": "^6.1.0",
    "openai": "^6.17.0",
    "pg": "^8.16.3",
    "sharp": "^0.34.4",
    "zod": "^4.1.12"
  },
  "devDependencies": {
    "@types/node": "^24.10.0",
    "@types/pg": "^8.15.5",
    "tsx": "^4.20.6",
    "typescript": "^5.9.3"
  }
}

```

### backend/docker-compose.yml

```yaml
services:
  postgres:
    image: postgres:17-alpine
    environment: { POSTGRES_DB: ant, POSTGRES_USER: ant, POSTGRES_PASSWORD: local-only-change-me }
    ports: ["5432:5432"]
    volumes: ["ant-postgres:/var/lib/postgresql/data", "./migrations:/docker-entrypoint-initdb.d:ro"]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ant -d ant"]
      interval: 5s
      timeout: 5s
      retries: 20
  api:
    build: .
    env_file: .env
    environment:
      DATABASE_URL: postgres://ant:local-only-change-me@postgres:5432/ant
    ports: ["8080:8080"]
    depends_on:
      postgres:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "node", "-e", "fetch('http://127.0.0.1:8080/ready').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"]
      interval: 10s
      timeout: 5s
      retries: 12
volumes: { ant-postgres: {} }

```

### backend/src/main.ts

```typescript
import { runMigrations } from "./migrate.js";

const applied = await runMigrations();
if (applied.length > 0) process.stdout.write(`Applied migrations: ${applied.join(", ")}\n`);
await import("./server.js");


```

### backend/src/server.ts

```typescript
import Fastify from "fastify";
import helmet from "@fastify/helmet";
import multipart from "@fastify/multipart";
import rateLimit from "@fastify/rate-limit";
import pg from "pg";
import { createHash, randomUUID } from "node:crypto";
import { loadConfig, parseAllowedOrigins } from "./config.js";
import { AuthService, type Principal } from "./auth.js";
import { loginInput, registerInput, refreshInput, syncSnapshot, telemetryBatch, RECEIPT_EXTRACTION_VERSION } from "./contracts.js";
import { decryptJson, encryptJson } from "./crypto.js";
import { ReceiptVisionExtractor } from "./openai.js";
import { accountDeletionPage, privacyPolicyPage, termsPage } from "./legal.js";
import sharp from "sharp";
import { ZodError } from "zod";

declare module "fastify" { interface FastifyRequest { principal?: Principal } }
const config = loadConfig();
const db = new pg.Pool({ connectionString: config.DATABASE_URL, ssl: config.NODE_ENV === "production" ? { rejectUnauthorized: true } : undefined, max: 10 });
const auth = new AuthService(db, config);
const extractor = new ReceiptVisionExtractor(config.OPENAI_API_KEY, config.OPENAI_MODEL);
const syncKey = Buffer.from(config.SYNC_ENCRYPTION_KEY_BASE64, "base64");
const app = Fastify({ logger: { redact: ["req.headers.authorization", "req.body.password", "req.body.refreshToken"] }, bodyLimit: 73 * 1024 * 1024, requestIdHeader: "x-request-id", genReqId: () => randomUUID() });
const allowedOrigins = new Set(parseAllowedOrigins(config.ALLOWED_ORIGINS));

await app.register(helmet, { global: true });
await app.register(multipart, { limits: { files: 6, fileSize: 12 * 1024 * 1024, fields: 5, parts: 6 } });
await app.register(rateLimit, { max: 120, timeWindow: "1 minute", keyGenerator: request => request.ip });
app.addHook("onRequest", async (request, reply) => {
  const origin = request.headers.origin;
  if (!origin) return;
  if (!allowedOrigins.has(origin)) return reply.code(403).send({ data: null, error: { code: "ORIGIN_NOT_ALLOWED", message: "This browser origin is not allowed.", recoverable: false }, meta: { requestId: request.id } });
  reply.header("Access-Control-Allow-Origin", origin).header("Vary", "Origin").header("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS").header("Access-Control-Allow-Headers", "Authorization,Content-Type");
  if (request.method === "OPTIONS") return reply.code(204).send();
});

app.setErrorHandler((error, request, reply) => {
  const message = error instanceof Error ? error.message : "INTERNAL_ERROR";
  const expected = ["INVALID_CREDENTIALS", "INVALID_REFRESH_TOKEN", "UNAUTHORIZED", "LEGAL_VERSION_OUTDATED", "EMAIL_ALREADY_REGISTERED"].includes(message) || error instanceof ZodError;
  request.log[expected ? "info" : "error"]({ err: error, requestId: request.id }, "request failed");
  const authFailure = message === "UNAUTHORIZED" || message === "INVALID_CREDENTIALS" || message === "INVALID_REFRESH_TOKEN";
  const errorCode = error instanceof ZodError ? "VALIDATION_ERROR" : expected ? message : "INTERNAL_ERROR";
  const clientMessage = authFailure ? "The request could not be authenticated." : message === "LEGAL_VERSION_OUTDATED" ? "Please review the latest legal terms before creating an account." : message === "EMAIL_ALREADY_REGISTERED" ? "An account already exists for this email address." : error instanceof ZodError ? "The request contains invalid data." : "The request could not be completed.";
  reply.code(message === "UNAUTHORIZED" ? 401 : expected ? 400 : 500).send({ data: null, error: { code: errorCode, message: clientMessage, recoverable: expected }, meta: { requestId: request.id } });
});
const protectedRoute = async (request: Parameters<typeof auth.verify>[0] extends never ? never : any) => { request.principal = await auth.verify(request.headers.authorization); };

app.get("/health", async () => ({ data: { status: "ok" }, error: null, meta: {} }));
app.get("/ready", async (_request, reply) => {
  try { await db.query("select 1"); return { data: { status: "ready" }, error: null, meta: {} }; }
  catch { return reply.code(503).send({ data: null, error: { code: "DATABASE_UNAVAILABLE", message: "Service is not ready.", recoverable: true }, meta: {} }); }
});
app.get("/legal/privacy", async (_request, reply) => reply.type("text/html; charset=utf-8").send(privacyPolicyPage(config)));
app.get("/legal/terms", async (_request, reply) => reply.type("text/html; charset=utf-8").send(termsPage(config)));
app.get("/legal/delete-account", async (_request, reply) => reply.type("text/html; charset=utf-8").send(accountDeletionPage(config)));
app.post("/api/auth/register", { config: { rateLimit: { max: 5, timeWindow: "15 minutes" } } }, async request => {
  const body = registerInput.parse(request.body); return { data: await auth.register(body.email, body.password, body.legalVersion), error: null, meta: {} };
});
app.post("/api/auth/login", { config: { rateLimit: { max: 10, timeWindow: "15 minutes" } } }, async request => {
  const body = loginInput.parse(request.body); return { data: await auth.login(body.email, body.password), error: null, meta: {} };
});
app.post("/api/auth/refresh", async request => { const body = refreshInput.parse(request.body); return { data: await auth.refresh(body.refreshToken), error: null, meta: {} }; });

app.post("/api/receipts/extract", { preHandler: protectedRoute, config: { rateLimit: { max: 20, timeWindow: "1 hour" } } }, async (request, reply) => {
  const allowed = new Set(["image/jpeg", "image/png", "image/webp", "application/pdf"]);
  const documents: Array<{ bytes: Buffer; mime: string; filename: string }> = [];
  const documentHash = createHash("sha256");
  for await (const upload of request.files()) {
    if (!allowed.has(upload.mimetype)) return reply.code(415).send({ data: null, error: { code: "UNSUPPORTED_DOCUMENT", message: "Use JPEG, PNG, WebP or PDF.", recoverable: true }, meta: {} });
    const bytes = await upload.toBuffer();
    documentHash.update(upload.mimetype).update(Buffer.from(
[truncated — 5797 more characters]
```

### settings.gradle.kts

```kotlin
pluginManagement {
    repositories {
        google()
        mavenCentral()
        gradlePluginPortal()
    }
}

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
    repositories {
        google()
        mavenCentral()
    }
}

rootProject.name = "ANT"
include(":app")
include(":composeApp")
include(":shared")

```

### build.gradle.kts

```kotlin
plugins {
    alias(libs.plugins.kotlinMultiplatform) apply false
    alias(libs.plugins.kotlinAndroid) apply false
    alias(libs.plugins.kotlinKapt) apply false
    alias(libs.plugins.kotlinSerialization) apply false
    alias(libs.plugins.androidApplication) apply false
    alias(libs.plugins.androidLibrary) apply false
    alias(libs.plugins.jetbrainsCompose) apply false
    alias(libs.plugins.composeCompiler) apply false
    alias(libs.plugins.hilt) apply false
}

```

### backend/pnpm-workspace.yaml

```yaml
allowBuilds:
  argon2: true
  esbuild: true
  sharp: true
onlyBuiltDependencies:
  - argon2
  - esbuild
  - sharp

```

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