Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1d4bd82
fix: reject unsafe/injection-prone names in property and meter-group …
Shinitaii Jul 10, 2026
db297fb
fix: harden chatbot against disclosure and scope-creep leaks
Shinitaii Jul 10, 2026
fbbcfdf
fix: add per-user rate limit on /chatbot to bound LLM spend
Shinitaii Jul 10, 2026
0efa235
chore: fix indexes
Shinitaii Jul 10, 2026
863c869
feat(api): serialize per-provider vision content in LlmClient
Shinitaii Jul 10, 2026
7e99206
feat(api): replace Gemini OCR with llm-config-driven vision extraction
Shinitaii Jul 10, 2026
57629e2
feat(llm-config): support independent chat and vision provider configs
Shinitaii Jul 10, 2026
abdfd55
feat(ui): split chatbot and vision provider settings into separate tabs
Shinitaii Jul 10, 2026
f91f294
docs: update navigation docs for vision-driven OCR and dual LLM configs
Shinitaii Jul 10, 2026
a55856d
feat(api): add per-user photo-settings for meter-reading photo persis…
Shinitaii Jul 10, 2026
cd21d6e
feat(mobile): add OCR suggest to CaptureReadings, respect photo-settings
Shinitaii Jul 10, 2026
a4718a6
feat(ui): merge upload/suggest into a single draggable photo box on r…
Shinitaii Jul 10, 2026
f84ca88
docs: document photo-settings feature and mobile OCR in root CLAUDE.md
Shinitaii Jul 10, 2026
804d544
fix(api): harden vision OCR against OWASP LLM Top 10 risks
Shinitaii Jul 11, 2026
7fad16c
docs: add privacy notice covering OCR data flow and shared-tenant model
Shinitaii Jul 11, 2026
cd9fffe
feat(api): add archive-then-purge hard-delete lifecycle for all entities
Shinitaii Jul 11, 2026
29b3e05
feat(api,ui): restrict chatbot to admin role and mask property names
Shinitaii Jul 11, 2026
5d73b61
fix(api): strip EXIF/GPS metadata from images before vision OCR
Shinitaii Jul 11, 2026
000f4be
docs: document purge lifecycle, admin-only chatbot, and OCR data flow
Shinitaii Jul 11, 2026
f0710f7
fix(api): resolve eslint false positives on CRLF checkouts and contro…
Shinitaii Jul 11, 2026
ce2d3d3
style(ui): apply prettier formatting to CI-flagged files
Shinitaii Jul 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions API_SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@ shell take precedence over the file.

## Optional

- **`GEMINI_API_KEY`** — Gemini Vision API key for OCR (image-extraction, billing-cycle/bills
OCR). If unset, `gemini.lib.ts` logs a warning and returns mock OCR responses — useful for
local dev without a real key.
- **`REDIS_URL`** — connection string for the rate-limiter / cache backend. If unset, falls back
to an in-memory store (single-instance only).
- **`ALLOWED_ORIGINS`** — comma-separated list of allowed CORS origins (e.g. your Vercel UI
domain). Required in non-development environments — `cors.config.ts` warns if unset outside
`development`. Localhost dev origins are always allowed regardless of this setting.

## OCR (image extraction)

OCR (meter reading photos, utility bill photos) is driven entirely by the user's **vision**
`llm-config` — no env var, no Gemini fallback. Set via `PATCH /llm-config/vision`
(Groq or Ollama Cloud), independent from the chat config set via `PATCH /llm-config` — some
providers (e.g. Ollama Cloud) have no usable free vision model, so chat and vision commonly use
different providers. If the vision provider matches the chat provider, the vision config reuses
the chat API key (no need to enter it twice); otherwise its own API key is required. OCR
endpoints return 404 ("Vision model not configured...") until the vision config exists.

## Notes

- `secrets/*.json` and `secrets/.env.*` are gitignored — never commit them. See root
Expand Down
93 changes: 58 additions & 35 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Welcome to the Utilitool project. This document guides you through the repo stru
### Read Once Per Session

When Claude Code starts work on a task:

- **API task?** Read `api/CLAUDE.md` only
- **UI task?** Read `ui/CLAUDE.md` only
- **Mobile task?** Read `mobile/CLAUDE.md` only
Expand Down Expand Up @@ -64,6 +65,7 @@ utilitool/
**Utilitool** is a utility meter reading and billing management system. It automates the workflow from capturing readings to generating accurate per-tenant bills.

### Core Entities

1. **Meter Groups** — Containers for utility types (electricity, water)
2. **Properties** — Buildings/units that consume utilities
3. **Tenants** — Individual renters/occupants
Expand All @@ -72,6 +74,7 @@ utilitool/
6. **Billing Cycles** — Periods that validate and rate all readings (enforces 3% tolerance, no meter rollback)

### The Happy Path

1. Capture readings from meters
2. Create a billing cycle (date range + total consumption + total charges)
3. System validates readings + consumption (3% tolerance)
Expand All @@ -82,19 +85,20 @@ utilitool/

## Technology Stack

| Layer | Stack | Details |
|-------|-------|---------|
| **Backend** | Express + Firebase Cloud Functions | TypeScript, Firestore, Zod validation, custom JWT auth |
| **Frontend** | SvelteKit 5 + Svelte 5 runes | TypeScript, Tailwind v4, Playwright E2E |
| **Mobile** | Svelte 5 SPA + Capacitor 6 | TypeScript, Tailwind v4, Android target, Firebase Auth |
| **Database** | Firestore (emulated locally) | No-SQL, real-time capabilities |
| **Deployment** | Firebase (API) + Vercel (UI) | Staging & production aliases configured |
| Layer | Stack | Details |
| -------------- | ---------------------------------- | ------------------------------------------------------ |
| **Backend** | Express + Firebase Cloud Functions | TypeScript, Firestore, Zod validation, custom JWT auth |
| **Frontend** | SvelteKit 5 + Svelte 5 runes | TypeScript, Tailwind v4, Playwright E2E |
| **Mobile** | Svelte 5 SPA + Capacitor 6 | TypeScript, Tailwind v4, Android target, Firebase Auth |
| **Database** | Firestore (emulated locally) | No-SQL, real-time capabilities |
| **Deployment** | Firebase (API) + Vercel (UI) | Staging & production aliases configured |

---

## Local Development

### Quick Start

```bash
# Terminal 1 — API (port 5002, watch mode, connected to utilitool-staging)
cd api/functions
Expand All @@ -115,6 +119,7 @@ npm run dev
```

### Docker alternative

```bash
docker-compose up
```
Expand All @@ -126,33 +131,37 @@ Starts API (port 5002), UI (port 5173), and the mobile web preview (port 5174) i
## Commands Quick Reference

### API (`api/functions/`)
| Task | Command |
|------|---------|

| Task | Command |
| ---------------- | ------------------- |
| Dev (watch mode) | `npm run dev:watch` |
| Type check | `npx tsc --noEmit` |
| Lint | `npm run lint` |
| Test | `npm test` |
| Build | `npm run build` |
| Type check | `npx tsc --noEmit` |
| Lint | `npm run lint` |
| Test | `npm test` |
| Build | `npm run build` |

### UI (`ui/`)
| Task | Command |
|------|---------|
| Dev server | `npm run dev` |
| Type check | `npm run check` |
| Lint | `npm run lint` |

| Task | Command |
| ----------- | ------------------- |
| Dev server | `npm run dev` |
| Type check | `npm run check` |
| Lint | `npm run lint` |
| Test (unit) | `npm run test:unit` |
| Test (E2E) | `npm run test:e2e` |
| Build | `npm run build` |
| Test (E2E) | `npm run test:e2e` |
| Build | `npm run build` |

---

## Swagger / API Documentation

**When API is running** (`npm run serve`):

- **Swagger UI**: http://localhost:5002/docs
- **OpenAPI spec**: http://localhost:5002/docs/swagger.json

Each feature has a `.swagger.ts` file defining its endpoints. Reference Swagger for:

- Request/response shapes
- Error codes & meanings
- Business rule constraints (e.g., 3% tolerance)
Expand All @@ -164,6 +173,7 @@ Each feature has a `.swagger.ts` file defining its endpoints. Reference Swagger
## CI/CD & Deployment

### Staging

- **API**: Automatically deploys on push to `main` in `api/functions/**`
- Project alias: `staging` (utilitool-staging)
- Requires: `FIREBASE_TOKEN_STAGING` in GitHub secrets
Expand All @@ -173,6 +183,7 @@ Each feature has a `.swagger.ts` file defining its endpoints. Reference Swagger
- Requires: `VERCEL_TOKEN`, `VERCEL_ORG_ID`, `VERCEL_PROJECT_ID`

### Production

- **Firestore**: utilitool-3fe70
- **Manual deployment only** — not in CI/CD

Expand All @@ -183,6 +194,7 @@ See `.github/workflows/` for full pipeline definitions.
## File Maps

### Per-Feature File Map

Each API feature is self-contained in `api/functions/src/features/<feature>/` following this pattern:

```
Expand All @@ -201,7 +213,9 @@ Each API feature is self-contained in `api/functions/src/features/<feature>/` fo
**Full details**: See `api/CLAUDE.md` → "File & Folder Reference by Feature"

### Per-Component File Map (UI)

Each page/component is organized by:

- **Pages**: `ui/src/routes/(app)/<feature>/+page.svelte`
- **API modules**: `ui/src/lib/api/<feature>.ts` (calls backend)
- **Components**: `ui/src/lib/components/<shared|feature>/`
Expand All @@ -214,21 +228,24 @@ Each page/component is organized by:
## Feature Status

### API Features (Complete + Audited May 2026)

- ✅ Meter Groups (CRUD, batch; dynamic sorting; `POST /:id/reset` and its `current_version`/`versions` fields are **@deprecated** — version tracking now lives per-property on `Property.meter_groups[entry]`, see `decisions/`)
- ✅ Properties (CRUD, batch; dynamic sorting; optimized duplicate detection)
- ✅ Tenants (CRUD, batch; dynamic sorting)
- ✅ Readings (CRUD, batch; auto-billing on single create; anomaly guard; meter rollback prevention; utility extraction)
- ✅ Billings (CRUD, batch; normally auto-created; meter rollback prevention)
- ✅ Billing Cycles (CRUD, batch, validation; editable via `PATCH /:id` for rate/consumption/date corrections; version-aware consumption (handles N meter resets cumulatively via `calculateTrueReading`/`resolveVersionsSource` in `reading.util.ts`); `POST /ocr` bill photo extraction; dynamic sorting)
- ✅ Auth (Firebase Auth: sign up, login, logout)
- ✅ Image Extraction (`POST /image-extraction/readings` + `POST /image-extraction/billings` — Gemini Vision OCR)
- ✅ Image Extraction (`POST /image-extraction/readings` + `POST /image-extraction/billings` — vision OCR via the user's configured `llm-config` vision provider, Groq or Ollama Cloud only; no Gemini)
- ✅ Reports (`GET /reports/summary`, `/consumption`, `/billing-trends`, `/collection-status`)
- ✅ Bills (`POST /bills/ocr` — functional 3-step UI wizard; overlaps with image-extraction)
- ⚠️ Users (`POST /users` — partial stub for user role management)
- ✅ LLM Config (`GET`/`PATCH /llm-config` provider/model/API key for the insight chatbot; API key AES-256-GCM encrypted at rest via `lib/crypto.lib.ts`)
- ✅ LLM Config (`GET`/`PATCH /llm-config` for the chatbot provider/model/API key + `PATCH /llm-config/vision` for an **independent** vision provider/model/API key used by OCR — separate because not every provider has a usable free vision model; reuses the chatbot's API key when both configs use the same provider; API keys AES-256-GCM encrypted at rest via `lib/crypto.lib.ts`)
- ✅ Chatbot (`POST /chatbot` — insight assistant scoped to the authenticated user's data, tool-calling via `lib/llm.lib.ts` against Groq/Ollama Cloud; regex jailbreak guard in `chatbot.guard.ts`)
- ✅ Photo Settings (`GET`/`PATCH /photo-settings` — per-user `savePhotos` preference, defaults to `false`; web and mobile check it before attaching a meter-reading `image_url` on create. OCR suggest always works regardless; billing-cycle/bill photos are never persisted either way)

**Audit Highlights (25 fixes)**:

- **D1**: Soft-delete pattern — all DELETE endpoints soft-delete (set `is_deleted` flag), no hard delete
- **D2**: Timestamp serialization — JSON responses use ISO 8601 strings
- **D3**: Firestore indices — composite indices for soft-delete + filter queries
Expand All @@ -239,6 +256,7 @@ Each page/component is organized by:
- **H1–H4**: Consistent pagination, batch ops, archive/restore semantics

### UI Pages (Complete + Audited May 2026)

- ✅ Login
- ✅ Dashboard (stat cards + properties table)
- ✅ Meter Groups (full CRUD table; archive page — Version column and Reset Meter button removed, version tracking moved to per-property)
Expand All @@ -252,18 +270,20 @@ Each page/component is organized by:
- ✅ Insight Chatbot (`ChatWidget` floating widget, mounted globally on all protected routes)

### Mobile Screens (May 2026)

- ✅ Login (Firebase Auth)
- ✅ Home (dashboard + "New Reading Session" CTA)
- ✅ CaptureReadings (3-step wizard: meter group select → per-property readings + camera → review & batch submit)
- ✅ CaptureReadings (3-step wizard: meter group select → per-property readings + camera with auto OCR suggest → review & batch submit)
- ✅ ReadingHistory (filterable by utility type + property)
- ✅ Billings (grouped by status: overdue/pending/paid; mark-as-paid action)
- ✅ Settings (account info + sign out)
- ✅ Settings (account info + sign out; save-photos preference toggle)

---

## Getting Started

### 1. First Time Setup

```bash
# Clone repo
git clone <repo>
Expand All @@ -285,17 +305,20 @@ cd ui && npm ci && npm run dev
```

### 2. Register a User

- Go to http://localhost:5173
- Click "Sign up"
- Create test account (e.g., `test@example.com` / `password123`)
- Login → Dashboard

### 3. Explore the API

- Visit http://localhost:5002/docs
- Try endpoints: POST meter-group, POST property, POST tenant, POST reading, etc.
- See real request/response shapes in Swagger UI

### 4. Understand Features

- Need to add a **new API feature**? → See `api/CLAUDE.md` → "Adding a New Feature"
- Need to add a **new UI page**? → See `ui/CLAUDE.md` → "Adding a New Page"
- Need to understand a **specific business rule**? → See section in this doc or the Business Overview
Expand All @@ -304,17 +327,17 @@ cd ui && npm ci && npm run dev

## Key Files

| File | Why It Matters |
|------|---|
| `docker-compose.yml` | Docker alternative for running the full stack (see Local Development) |
| `api/functions/src/index.ts` | API entry point — all routes mounted here |
| `api/functions/src/config/swagger.config.ts` | OpenAPI spec generator — aggregates all `.swagger.ts` files |
| `ui/src/routes/(app)/+layout.ts` | Auth guard for all protected routes |
| `ui/src/lib/api/client.ts` | JWT token refresh interceptor — handles 401 + retry |
| `ui/src/lib/stores/auth.svelte.ts` | Authentication state (Svelte writable store) |
| `mobile/src/App.svelte` | Mobile root: auth guard + hash-based screen router |
| `mobile/src/lib/api/client.ts` | Mobile fetch client: Bearer token + 401 retry |
| `mobile/capacitor.config.ts` | Capacitor app ID, webDir, Camera plugin settings |
| File | Why It Matters |
| -------------------------------------------- | --------------------------------------------------------------------- |
| `docker-compose.yml` | Docker alternative for running the full stack (see Local Development) |
| `api/functions/src/index.ts` | API entry point — all routes mounted here |
| `api/functions/src/config/swagger.config.ts` | OpenAPI spec generator — aggregates all `.swagger.ts` files |
| `ui/src/routes/(app)/+layout.ts` | Auth guard for all protected routes |
| `ui/src/lib/api/client.ts` | JWT token refresh interceptor — handles 401 + retry |
| `ui/src/lib/stores/auth.svelte.ts` | Authentication state (Svelte writable store) |
| `mobile/src/App.svelte` | Mobile root: auth guard + hash-based screen router |
| `mobile/src/lib/api/client.ts` | Mobile fetch client: Bearer token + 401 retry |
| `mobile/capacitor.config.ts` | Capacitor app ID, webDir, Camera plugin settings |

---

Expand Down
Loading
Loading