From 7dfe2da2a9330301b2952f4a830040b54cc32a73 Mon Sep 17 00:00:00 2001 From: Carlos Escalante Date: Fri, 12 Jun 2026 07:53:00 -0600 Subject: [PATCH] OpenAPI type codegen with CI drift gate (plan 3.4, ARCH-16, FE-14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every dict-returning route the SPA consumes now declares a response model (auth, bulk, sync-status, notifications). api-types.gen.ts is generated from app.openapi() via openapi-typescript, and the 22 hand- written read interfaces in api.ts are now aliases onto the generated schemas — backend schema drift becomes a typecheck failure, and CI regenerates the file and fails the deploy if it's stale. The aliasing immediately caught two real drifts: RecurringItemType was missing SAVINGS, and raw_charges (untyped JSON column) is now explicitly shaped at the boundary. Co-Authored-By: Claude Fable 5 --- .github/workflows/deploy.yml | 10 +- .gitignore | 1 + CLAUDE.md | 14 + backend/app/api/v1/endpoints/auth.py | 14 +- backend/app/api/v1/endpoints/notifications.py | 15 +- backend/app/api/v1/endpoints/sync_status.py | 18 +- backend/app/api/v1/endpoints/transactions.py | 6 +- frontend/package.json | 4 +- frontend/pnpm-lock.yaml | 191 +- frontend/src/lib/api-types.gen.ts | 4058 +++++++++++++++++ frontend/src/lib/api.ts | 267 +- 11 files changed, 4347 insertions(+), 251 deletions(-) create mode 100644 frontend/src/lib/api-types.gen.ts diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index ae5be34..6874a25 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -18,11 +18,17 @@ jobs: /tmp/test-venv/bin/pip install --quiet -r requirements.txt -r requirements-dev.txt /tmp/test-venv/bin/python -m pytest tests/ -q - - name: Frontend typecheck + - name: Frontend typecheck + API type drift gate run: | - cd frontend + cd backend + DATABASE_URL='sqlite://' SECRET_KEY='ci-only-0123456789abcdef0123456789abcdef0123456789abcdef' \ + ADMIN_USERNAME=ci ADMIN_PASSWORD=ci-not-admin OPENAI_API_KEY=dummy \ + /tmp/test-venv/bin/python -c "import json; from app.main import app; json.dump(app.openapi(), open('../frontend/openapi.json','w'), indent=1, sort_keys=True)" + cd ../frontend corepack enable pnpm install --frozen-lockfile + pnpm exec openapi-typescript openapi.json -o src/lib/api-types.gen.ts + git diff --exit-code src/lib/api-types.gen.ts pnpm typecheck deploy: diff --git a/.gitignore b/.gitignore index 42478db..70a7bc6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,3 +15,4 @@ tech_docs/ # Claude Code local state .claude/ .venv/ +frontend/openapi.json diff --git a/CLAUDE.md b/CLAUDE.md index 557dbdc..e0dc3ef 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -68,3 +68,17 @@ Four automated flows on old-vps feed data into WealthySmart: 4. **Pension PDFs** (`e88c3UhBeo9WCbcy`) — Gmail trigger (daily midnight) → POST /pensions/upload Flow export: `docs/WealthySmart_ BAC Pensions Statements parser.json` + +## API Type Generation + +Frontend read types are generated from the backend OpenAPI spec. After changing +backend response models: + +```bash +cd backend && DATABASE_URL='sqlite://' SECRET_KEY=$(openssl rand -hex 32) \ + ADMIN_USERNAME=x ADMIN_PASSWORD=x-x OPENAI_API_KEY=dummy \ + .venv/bin/python -c "import json; from app.main import app; json.dump(app.openapi(), open('../frontend/openapi.json','w'), indent=1, sort_keys=True)" +cd ../frontend && pnpm gen:api # regenerates src/lib/api-types.gen.ts +``` + +CI regenerates and fails the deploy if `api-types.gen.ts` is stale. diff --git a/backend/app/api/v1/endpoints/auth.py b/backend/app/api/v1/endpoints/auth.py index 682a251..80a88e6 100644 --- a/backend/app/api/v1/endpoints/auth.py +++ b/backend/app/api/v1/endpoints/auth.py @@ -1,4 +1,5 @@ from fastapi import APIRouter, Depends, Request +from pydantic import BaseModel from fastapi.security import OAuth2PasswordRequestForm from app.auth import ( @@ -11,7 +12,16 @@ from app.auth import ( router = APIRouter(prefix="/auth", tags=["auth"]) -@router.post("/login") +class TokenResponse(BaseModel): + access_token: str + token_type: str + + +class MeResponse(BaseModel): + username: str + + +@router.post("/login", response_model=TokenResponse) def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()): check_login_rate_limit(request) verify_admin_credentials(form_data.username, form_data.password) @@ -19,6 +29,6 @@ def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()): return {"access_token": token, "token_type": "bearer"} -@router.get("/me") +@router.get("/me", response_model=MeResponse) def me(username: str = Depends(get_current_user_cookie_or_bearer)): return {"username": username} diff --git a/backend/app/api/v1/endpoints/notifications.py b/backend/app/api/v1/endpoints/notifications.py index c8d6de0..bcdb9af 100644 --- a/backend/app/api/v1/endpoints/notifications.py +++ b/backend/app/api/v1/endpoints/notifications.py @@ -1,6 +1,7 @@ import json import logging +from pydantic import BaseModel from fastapi import APIRouter, Depends, HTTPException from pywebpush import WebPushException, webpush from sqlmodel import Session, select @@ -15,14 +16,22 @@ logger = logging.getLogger(__name__) router = APIRouter(prefix="/notifications", tags=["notifications"]) -@router.get("/vapid-public-key") +class VapidPublicKeyResponse(BaseModel): + publicKey: str + + +class SubscriptionStatusResponse(BaseModel): + status: str + + +@router.get("/vapid-public-key", response_model=VapidPublicKeyResponse) def get_vapid_public_key(_user: str = Depends(get_current_user)): if not settings.VAPID_PUBLIC_KEY: raise HTTPException(status_code=503, detail="Push notifications not configured") return {"publicKey": settings.VAPID_PUBLIC_KEY} -@router.post("/subscribe", status_code=201) +@router.post("/subscribe", status_code=201, response_model=SubscriptionStatusResponse) def subscribe( data: PushSubscriptionCreate, session: Session = Depends(get_session), @@ -48,7 +57,7 @@ def subscribe( return {"status": "subscribed"} -@router.delete("/unsubscribe") +@router.delete("/unsubscribe", response_model=SubscriptionStatusResponse) def unsubscribe( data: PushSubscriptionCreate, session: Session = Depends(get_session), diff --git a/backend/app/api/v1/endpoints/sync_status.py b/backend/app/api/v1/endpoints/sync_status.py index 6413081..e5365a6 100644 --- a/backend/app/api/v1/endpoints/sync_status.py +++ b/backend/app/api/v1/endpoints/sync_status.py @@ -1,4 +1,5 @@ from fastapi import APIRouter, Depends +from pydantic import BaseModel from sqlmodel import Session from app.auth import get_current_user @@ -8,7 +9,22 @@ from app.services.sync_status import compute_sync_status router = APIRouter(prefix="/sync-status", tags=["sync-status"]) -@router.get("/") +class SyncSourceStatus(BaseModel): + key: str + label: str + description: str + last_received: str | None + age_days: float | None + warn_after_days: float + status: str # 'ok' | 'warning' | 'never' + + +class SyncStatusResponse(BaseModel): + sources: list[SyncSourceStatus] + warnings: int + + +@router.get("/", response_model=SyncStatusResponse) def sync_status( session: Session = Depends(get_session), _user: str = Depends(get_current_user), diff --git a/backend/app/api/v1/endpoints/transactions.py b/backend/app/api/v1/endpoints/transactions.py index 082e88e..8810fcc 100644 --- a/backend/app/api/v1/endpoints/transactions.py +++ b/backend/app/api/v1/endpoints/transactions.py @@ -106,7 +106,11 @@ class BulkActionRequest(BaseModel): deferred: Optional[bool] = None # for set_deferred -@router.post("/bulk") +class BulkActionResponse(BaseModel): + affected: int + + +@router.post("/bulk", response_model=BulkActionResponse) def bulk_action( req: BulkActionRequest, session: Session = Depends(get_session), diff --git a/frontend/package.json b/frontend/package.json index 63d7912..65c5539 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,7 +8,8 @@ "dev": "concurrently -k -n vite,ck -c cyan,magenta \"vite --host 0.0.0.0 --port 3000\" \"tsx watch server.ts\"", "build": "vite build", "preview": "tsx server.ts", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "gen:api": "openapi-typescript openapi.json -o src/lib/api-types.gen.ts" }, "dependencies": { "@ag-ui/client": "0.0.52", @@ -42,6 +43,7 @@ "@types/react": "^19", "@types/react-dom": "^19", "@vitejs/plugin-react-swc": "^3.9.0", + "openapi-typescript": "^7.13.0", "tailwindcss": "^4", "typescript": "^5", "vite": "^6.3.5" diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml index cdee852..439e672 100644 --- a/frontend/pnpm-lock.yaml +++ b/frontend/pnpm-lock.yaml @@ -96,6 +96,9 @@ importers: '@vitejs/plugin-react-swc': specifier: ^3.9.0 version: 3.11.0(@swc/helpers@0.5.21)(vite@6.4.2(@types/node@20.19.39)(jiti@2.6.1)(lightningcss@1.32.0)(tsx@4.21.0)) + openapi-typescript: + specifier: ^7.13.0 + version: 7.13.0(typescript@5.9.3) tailwindcss: specifier: ^4 version: 4.2.4 @@ -225,6 +228,14 @@ packages: '@antfu/install-pkg@1.1.0': resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==} + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + '@babel/runtime@7.29.2': resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} engines: {node: '>=6.9.0'} @@ -1215,6 +1226,16 @@ packages: peerDependencies: react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1 + '@redocly/ajv@8.11.2': + resolution: {integrity: sha512-io1JpnwtIcvojV7QKDUSIuMN/ikdOUd1ReEnUnMKGfDVridQZ31J0MmIuqwuRjWDZfmvr+Q0MqCcfHM2gTivOg==} + + '@redocly/config@0.22.0': + resolution: {integrity: sha512-gAy93Ddo01Z3bHuVdPWfCwzgfaYgMdaZPcfL7JZ7hWJoK9V0lXDbigTWkhiPFAaLWzbOJ+kbUQG1+XwIm0KRGQ==} + + '@redocly/openapi-core@1.34.15': + resolution: {integrity: sha512-HAwCnNyKcs5XGQqms+9t7OdAPM/5TDstmhF+0i7tdCFato2QKuYIlyWETwkXd8c5zbltr1oB+6y9NTeQLr2d6Q==} + engines: {node: '>=18.17.0', npm: '>=9.5.0'} + '@reduxjs/toolkit@2.11.2': resolution: {integrity: sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==} peerDependencies: @@ -1894,6 +1915,10 @@ packages: ajv@8.20.0: resolution: {integrity: sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -1906,6 +1931,9 @@ packages: resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} engines: {node: '>=10'} + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + aria-hidden@1.2.6: resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} engines: {node: '>=10'} @@ -1923,6 +1951,9 @@ packages: bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1937,6 +1968,9 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} + buffer-equal-constant-time@1.0.1: resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} @@ -1966,6 +2000,9 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + change-case@5.4.4: + resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} + character-entities-html4@2.1.0: resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} @@ -2024,6 +2061,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@1.4.0: + resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==} + colorette@2.0.20: resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} @@ -2724,6 +2764,10 @@ packages: immer@11.1.4: resolution: {integrity: sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==} + index-to-position@1.2.0: + resolution: {integrity: sha512-Yg7+ztRkqslMAS2iFaU+Oa4KTSidr63OsFGlOrJoW981kIYO3CGCS3wA95P1mUi/IVSJkn0D479KTJpVpvFNuw==} + engines: {node: '>=18'} + inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2808,12 +2852,20 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + js-levenshtein@1.1.6: + resolution: {integrity: sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g==} + engines: {node: '>=0.10.0'} + js-tiktoken@1.0.21: resolution: {integrity: sha512-biOj/6M5qdgx5TKjDnFT1ymSpM5tbd3ylwDtrQvFQSu0Z7bBYko2dF+W/aUkXUPuk6IVpRxk/3Q2sHOzGlS36g==} js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + json-bigint@1.0.0: resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} @@ -3305,6 +3357,10 @@ packages: engines: {node: '>=4'} hasBin: true + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} + engines: {node: '>=10'} + minimist@1.2.8: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} @@ -3393,6 +3449,12 @@ packages: zod: optional: true + openapi-typescript@7.13.0: + resolution: {integrity: sha512-EFP392gcqXS7ntPvbhBzbF8TyBA+baIYEm791Hy5YkjDYKTnk/Tn5OQeKm5BIZvJihpp8Zzr4hzx0Irde1LNGQ==} + hasBin: true + peerDependencies: + typescript: ^5.x + p-finally@1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -3426,6 +3488,10 @@ packages: parse-entities@4.0.2: resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse-json@8.3.0: + resolution: {integrity: sha512-ybiGyvspI+fAoRQbIPRddCcSTV9/LsJbf0e/S85VLowVGzRmokfneg2kwVW/KU5rOXrPSbF1qAKPMgNTqqROQQ==} + engines: {node: '>=18'} + parse5@7.3.0: resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} @@ -3483,6 +3549,10 @@ packages: pkg-types@1.3.1: resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + pluralize@8.0.0: + resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} + engines: {node: '>=4'} + points-on-curve@0.2.0: resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==} @@ -3925,6 +3995,10 @@ packages: stylis@4.4.0: resolution: {integrity: sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==} + supports-color@10.2.2: + resolution: {integrity: sha512-SS+jx45GF1QjgEXQx4NJZV9ImqmO2NPz5FNsIHrsDjh2YsHnawpan7SNQ1o8NuhrbHZy9AZhIoCUiCeaW/C80g==} + engines: {node: '>=18'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -3992,6 +4066,10 @@ packages: tw-animate-css@1.4.0: resolution: {integrity: sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==} + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + type-graphql@2.0.0-rc.1: resolution: {integrity: sha512-HCu4j3jR0tZvAAoO7DMBT3MRmah0DFRe5APymm9lXUghXA0sbhiMf6SLRafRYfk0R0KiUQYRduuGP3ap1RnF1Q==} engines: {node: '>= 18.12.0'} @@ -4077,6 +4155,9 @@ packages: untruncate-json@0.0.1: resolution: {integrity: sha512-4W9enDK4X1y1s2S/Rz7ysw6kDuMS3VmRjMFg7GZrNO+98OSe+x5Lh7PKYoVjy3lW/1wmhs6HW0lusnQRHgMarA==} + uri-js-replace@1.0.1: + resolution: {integrity: sha512-W+C9NWNLFOoBI2QWDp4UT9pv65r2w5Cx+3sTYFvtMdDBxkKt1syCqsUdSFAChbEe1uK5TfS04wt/nGwmaeIQ0g==} + urlpattern-polyfill@10.1.0: resolution: {integrity: sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==} @@ -4275,6 +4356,9 @@ packages: resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} engines: {node: '>=10'} + yaml-ast-parser@0.0.43: + resolution: {integrity: sha512-2PTINUwsRqSd+s8XxKaJWQlUuEMHJQyEuh2edBbW8KNJz0SJPwUSD2zRWqezFEdN7IzAgeuYHFUCF7o8zRdZ0A==} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} @@ -4464,6 +4548,14 @@ snapshots: package-manager-detector: 1.6.0 tinyexec: 1.1.1 + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.29.7': {} + '@babel/runtime@7.29.2': {} '@base-ui/react@1.4.1(@types/react@19.2.14)(date-fns@4.1.0)(react-dom@19.2.5(react@19.2.5))(react@19.2.5)': @@ -5430,6 +5522,29 @@ snapshots: dependencies: react: 19.2.5 + '@redocly/ajv@8.11.2': + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js-replace: 1.0.1 + + '@redocly/config@0.22.0': {} + + '@redocly/openapi-core@1.34.15(supports-color@10.2.2)': + dependencies: + '@redocly/ajv': 8.11.2 + '@redocly/config': 0.22.0 + colorette: 1.4.0 + https-proxy-agent: 7.0.6(supports-color@10.2.2) + js-levenshtein: 1.1.6 + js-yaml: 4.1.1 + minimatch: 5.1.9 + pluralize: 8.0.0 + yaml-ast-parser: 0.0.43 + transitivePeerDependencies: + - supports-color + '@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@19.2.5)(redux@5.0.1))(react@19.2.5)': dependencies: '@standard-schema/spec': 1.1.0 @@ -6039,6 +6154,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + ansi-colors@4.1.3: {} + ansi-regex@5.0.1: {} ansi-styles@4.3.0: @@ -6047,6 +6164,8 @@ snapshots: ansi-styles@5.2.0: {} + argparse@2.0.1: {} + aria-hidden@1.2.6: dependencies: tslib: 2.8.1 @@ -6060,6 +6179,8 @@ snapshots: bail@2.0.2: {} + balanced-match@1.0.2: {} + base64-js@1.5.1: {} bignumber.js@9.3.1: {} @@ -6085,7 +6206,7 @@ snapshots: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) http-errors: 2.0.1 iconv-lite: 0.7.2 on-finished: 2.4.1 @@ -6095,6 +6216,10 @@ snapshots: transitivePeerDependencies: - supports-color + brace-expansion@2.1.1: + dependencies: + balanced-match: 1.0.2 + buffer-equal-constant-time@1.0.1: {} buffer@6.0.3: @@ -6123,6 +6248,8 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + change-case@5.4.4: {} + character-entities-html4@2.1.0: {} character-entities-legacy@1.1.4: {} @@ -6178,6 +6305,8 @@ snapshots: color-name@1.1.4: {} + colorette@1.4.0: {} + colorette@2.0.20: {} combined-stream@1.0.8: @@ -6443,9 +6572,11 @@ snapshots: dependencies: ms: 2.0.0 - debug@4.4.3: + debug@4.4.3(supports-color@10.2.2): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 decamelize@1.2.0: {} @@ -6660,7 +6791,7 @@ snapshots: content-type: 1.0.5 cookie: 0.7.2 cookie-signature: 1.2.2 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 encodeurl: 2.0.0 escape-html: 1.0.3 @@ -6724,7 +6855,7 @@ snapshots: finalhandler@2.1.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 on-finished: 2.4.1 @@ -6771,7 +6902,7 @@ snapshots: gaxios@7.1.4: dependencies: extend: 3.0.2 - https-proxy-agent: 7.0.6 + https-proxy-agent: 7.0.6(supports-color@10.2.2) node-fetch: 3.3.2 transitivePeerDependencies: - supports-color @@ -7032,10 +7163,10 @@ snapshots: statuses: 2.0.2 toidentifier: 1.0.1 - https-proxy-agent@7.0.6: + https-proxy-agent@7.0.6(supports-color@10.2.2): dependencies: agent-base: 7.1.4 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) transitivePeerDependencies: - supports-color @@ -7062,6 +7193,8 @@ snapshots: immer@11.1.4: {} + index-to-position@1.2.0: {} + inherits@2.0.4: {} inline-style-parser@0.1.1: {} @@ -7118,12 +7251,18 @@ snapshots: joycon@3.1.1: {} + js-levenshtein@1.1.6: {} + js-tiktoken@1.0.21: dependencies: base64-js: 1.5.1 js-tokens@4.0.0: {} + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + json-bigint@1.0.0: dependencies: bignumber.js: 9.3.1 @@ -7865,7 +8004,7 @@ snapshots: micromark@3.2.0: dependencies: '@types/debug': 4.1.13 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) decode-named-character-reference: 1.3.0 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -7887,7 +8026,7 @@ snapshots: micromark@4.0.2: dependencies: '@types/debug': 4.1.13 - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) decode-named-character-reference: 1.3.0 devlop: 1.1.0 micromark-core-commonmark: 2.0.3 @@ -7920,6 +8059,10 @@ snapshots: mime@1.6.0: {} + minimatch@5.1.9: + dependencies: + brace-expansion: 2.1.1 + minimist@1.2.8: {} mlly@1.8.2: @@ -7993,6 +8136,16 @@ snapshots: - encoding optional: true + openapi-typescript@7.13.0(typescript@5.9.3): + dependencies: + '@redocly/openapi-core': 1.34.15(supports-color@10.2.2) + ansi-colors: 4.1.3 + change-case: 5.4.4 + parse-json: 8.3.0 + supports-color: 10.2.2 + typescript: 5.9.3 + yargs-parser: 21.1.1 + p-finally@1.0.0: {} p-queue@6.6.2: @@ -8036,6 +8189,12 @@ snapshots: is-decimal: 2.0.1 is-hexadecimal: 2.0.1 + parse-json@8.3.0: + dependencies: + '@babel/code-frame': 7.29.7 + index-to-position: 1.2.0 + type-fest: 4.41.0 + parse5@7.3.0: dependencies: entities: 6.0.1 @@ -8105,6 +8264,8 @@ snapshots: mlly: 1.8.2 pathe: 2.0.3 + pluralize@8.0.0: {} + points-on-curve@0.2.0: {} points-on-path@0.2.1: @@ -8520,7 +8681,7 @@ snapshots: router@2.2.0: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) depd: 2.0.0 is-promise: 4.0.0 parseurl: 1.3.3 @@ -8574,7 +8735,7 @@ snapshots: send@1.2.1: dependencies: - debug: 4.4.3 + debug: 4.4.3(supports-color@10.2.2) encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 @@ -8743,6 +8904,8 @@ snapshots: stylis@4.4.0: {} + supports-color@10.2.2: {} + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -8795,6 +8958,8 @@ snapshots: tw-animate-css@1.4.0: {} + type-fest@4.41.0: {} + type-graphql@2.0.0-rc.1(class-validator@0.14.4)(graphql-scalars@1.25.0(graphql@16.13.2))(graphql@16.13.2): dependencies: '@graphql-yoga/subscription': 5.0.5 @@ -8910,6 +9075,8 @@ snapshots: untruncate-json@0.0.1: {} + uri-js-replace@1.0.1: {} + urlpattern-polyfill@10.1.0: {} urql@4.2.2(@urql/core@5.2.0(graphql@16.13.2))(react@19.2.5): @@ -9070,6 +9237,8 @@ snapshots: y18n@5.0.8: {} + yaml-ast-parser@0.0.43: {} + yargs-parser@21.1.1: {} yargs@17.7.2: diff --git a/frontend/src/lib/api-types.gen.ts b/frontend/src/lib/api-types.gen.ts new file mode 100644 index 0000000..6e2495b --- /dev/null +++ b/frontend/src/lib/api-types.gen.ts @@ -0,0 +1,4058 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + +export interface paths { + "/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Root */ + get: operations["root__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Cookie Login */ + post: operations["cookie_login_api_auth_login_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/auth/logout": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Cookie Logout */ + post: operations["cookie_logout_api_auth_logout_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/health": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Health */ + get: operations["health_api_health_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/accounts/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Accounts */ + get: operations["list_accounts_api_v1_accounts__get"]; + put?: never; + /** Create Account */ + post: operations["create_account_api_v1_accounts__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/accounts/{account_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Update Account */ + patch: operations["update_account_api_v1_accounts__account_id__patch"]; + trace?: never; + }; + "/api/v1/agent/agui": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Agent Endpoint + * @description Handle AG-UI agent requests. + * + * Note: Function is accessed via FastAPI's decorator registration, + * despite appearing unused to static analysis. + */ + post: operations["agent_endpoint_api_v1_agent_agui_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/analytics/by-category": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Spending By Category */ + get: operations["spending_by_category_api_v1_analytics_by_category_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/analytics/daily-spending": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Daily Spending */ + get: operations["daily_spending_api_v1_analytics_daily_spending_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/analytics/monthly-trend": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Monthly Trend + * @description Monthly spending totals using billing cycle boundaries (18th-18th). + * + * total_crc includes all currencies converted to CRC at current rates. + * total_usd is the raw USD amount (unconverted) for display purposes. + */ + get: operations["monthly_trend_api_v1_analytics_monthly_trend_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/auth/login": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Login */ + post: operations["login_api_v1_auth_login_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/auth/me": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Me */ + get: operations["me_api_v1_auth_me_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/budget/balance-override/{year}/{month}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** Upsert Balance Override */ + put: operations["upsert_balance_override_api_v1_budget_balance_override__year___month__put"]; + post?: never; + /** Delete Balance Override */ + delete: operations["delete_balance_override_api_v1_budget_balance_override__year___month__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/budget/month/{year}/{month}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Monthly Detail */ + get: operations["get_monthly_detail_api_v1_budget_month__year___month__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/budget/projection/{year}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Yearly Projection */ + get: operations["get_yearly_projection_api_v1_budget_projection__year__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/budget/recurring": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Recurring Items */ + get: operations["list_recurring_items_api_v1_budget_recurring_get"]; + put?: never; + /** Create Recurring Item */ + post: operations["create_recurring_item_api_v1_budget_recurring_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/budget/recurring/{item_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete Recurring Item */ + delete: operations["delete_recurring_item_api_v1_budget_recurring__item_id__delete"]; + options?: never; + head?: never; + /** Update Recurring Item */ + patch: operations["update_recurring_item_api_v1_budget_recurring__item_id__patch"]; + trace?: never; + }; + "/api/v1/categories/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Categories */ + get: operations["list_categories_api_v1_categories__get"]; + put?: never; + /** Create Category */ + post: operations["create_category_api_v1_categories__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/categories/{category_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete Category */ + delete: operations["delete_category_api_v1_categories__category_id__delete"]; + options?: never; + head?: never; + /** Update Category */ + patch: operations["update_category_api_v1_categories__category_id__patch"]; + trace?: never; + }; + "/api/v1/exchange-rate/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Current Rate */ + get: operations["current_rate_api_v1_exchange_rate__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/exchange-rate/history": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Rate History */ + get: operations["rate_history_api_v1_exchange_rate_history_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/import/paste": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Paste Import */ + post: operations["paste_import_api_v1_import_paste_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/municipal-receipts/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Receipts */ + get: operations["list_receipts_api_v1_municipal_receipts__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/municipal-receipts/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Upload Municipal Receipt */ + post: operations["upload_municipal_receipt_api_v1_municipal_receipts_upload_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/municipal-receipts/water-consumption": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Water Consumption */ + get: operations["get_water_consumption_api_v1_municipal_receipts_water_consumption_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/municipal-receipts/{receipt_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Receipt Detail */ + get: operations["get_receipt_detail_api_v1_municipal_receipts__receipt_id__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/notifications/subscribe": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Subscribe */ + post: operations["subscribe_api_v1_notifications_subscribe_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/notifications/unsubscribe": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Unsubscribe */ + delete: operations["unsubscribe_api_v1_notifications_unsubscribe_delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/notifications/vapid-public-key": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Vapid Public Key */ + get: operations["get_vapid_public_key_api_v1_notifications_vapid_public_key_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/pensions/fund-summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Get Fund Summary + * @description Return the latest snapshot per fund (by most recent period_end). + */ + get: operations["get_fund_summary_api_v1_pensions_fund_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/pensions/manual": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Submit Manual Entries */ + post: operations["submit_manual_entries_api_v1_pensions_manual_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/pensions/snapshots": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Snapshots */ + get: operations["get_snapshots_api_v1_pensions_snapshots_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/pensions/upload": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** Upload Pension Pdfs */ + post: operations["upload_pension_pdfs_api_v1_pensions_upload_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/salarios/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Salarios */ + get: operations["list_salarios_api_v1_salarios__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/salarios/summary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Salarios Summary */ + get: operations["salarios_summary_api_v1_salarios_summary_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/savings-accrual/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Accruals */ + get: operations["list_accruals_api_v1_savings_accrual__get"]; + put?: never; + /** Create Accrual */ + post: operations["create_accrual_api_v1_savings_accrual__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/savings-accrual/{accrual_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete Accrual */ + delete: operations["delete_accrual_api_v1_savings_accrual__accrual_id__delete"]; + options?: never; + head?: never; + /** Update Accrual */ + patch: operations["update_accrual_api_v1_savings_accrual__accrual_id__patch"]; + trace?: never; + }; + "/api/v1/settings/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Get Settings */ + get: operations["get_settings_api_v1_settings__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + /** Update Settings */ + patch: operations["update_settings_api_v1_settings__patch"]; + trace?: never; + }; + "/api/v1/sync-status/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Sync Status */ + get: operations["sync_status_api_v1_sync_status__get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/tokens/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Tokens */ + get: operations["list_tokens_api_v1_tokens__get"]; + put?: never; + /** Create Token */ + post: operations["create_token_api_v1_tokens__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/tokens/{token_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Revoke Token */ + delete: operations["revoke_token_api_v1_tokens__token_id__delete"]; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions/": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** List Transactions */ + get: operations["list_transactions_api_v1_transactions__get"]; + put?: never; + /** Create Transaction */ + post: operations["create_transaction_api_v1_transactions__post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions/bulk": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * Bulk Action + * @description Apply one action to many transactions atomically (review 4.4). + */ + post: operations["bulk_action_api_v1_transactions_bulk_post"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions/cycles": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * List Billing Cycles + * @description Return available billing cycles based on transaction dates. + */ + get: operations["list_billing_cycles_api_v1_transactions_cycles_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions/export": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** + * Export Transactions Csv + * @description Download transactions as CSV (filters optional). Cookie-authenticated, + * so a plain download works from the SPA. + */ + get: operations["export_transactions_csv_api_v1_transactions_export_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions/recent": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** Recent Transactions */ + get: operations["recent_transactions_api_v1_transactions_recent_get"]; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/v1/transactions/{transaction_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** Delete Transaction */ + delete: operations["delete_transaction_api_v1_transactions__transaction_id__delete"]; + options?: never; + head?: never; + /** Update Transaction */ + patch: operations["update_transaction_api_v1_transactions__transaction_id__patch"]; + trace?: never; + }; +} +export type webhooks = Record; +export interface components { + schemas: { + /** + * AGUIRequest + * @description Request model for AG-UI endpoints. + */ + AGUIRequest: { + /** + * Availableinterrupts + * @description List of interrupts that can be resumed by the server + */ + availableInterrupts?: { + [key: string]: unknown; + }[] | null; + /** + * Context + * @description List of context objects provided to the agent + */ + context?: { + [key: string]: unknown; + }[] | null; + /** + * Forwarded Props + * @description Additional properties forwarded to the agent + */ + forwarded_props?: { + [key: string]: unknown; + } | null; + /** + * Messages + * @description AG-UI format messages array + */ + messages: { + [key: string]: unknown; + }[]; + /** + * Parent Run Id + * @description ID of the run that spawned this run + */ + parent_run_id?: string | null; + /** + * Resume + * @description Resume payload containing interrupt responses + */ + resume?: { + [key: string]: unknown; + } | null; + /** + * Run Id + * @description Optional run identifier for tracking + */ + run_id?: string | null; + /** + * State + * @description Optional shared state for agentic generative UI + */ + state?: { + [key: string]: unknown; + } | null; + /** + * Thread Id + * @description Optional thread identifier for conversation context + */ + thread_id?: string | null; + /** + * Tools + * @description Client-side tools to advertise to the LLM + */ + tools?: { + [key: string]: unknown; + }[] | null; + }; + /** APITokenCreate */ + APITokenCreate: { + /** Expires Days */ + expires_days?: number | null; + /** Name */ + name: string; + }; + /** APITokenRead */ + APITokenRead: { + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Expires At */ + expires_at: string | null; + /** Id */ + id: number; + /** Is Active */ + is_active: boolean; + /** Name */ + name: string; + }; + /** AccountCreate */ + AccountCreate: { + /** @default BANK */ + account_type: components["schemas"]["AccountType"]; + /** + * Balance + * @default 0 + */ + balance: number | string; + bank: components["schemas"]["Bank"]; + currency: components["schemas"]["Currency"]; + /** Label */ + label: string; + /** Next Payment */ + next_payment?: number | string | null; + }; + /** AccountRead */ + AccountRead: { + /** @default BANK */ + account_type: components["schemas"]["AccountType"]; + /** + * Balance + * @default 0 + */ + balance: number; + bank: components["schemas"]["Bank"]; + currency: components["schemas"]["Currency"]; + /** Id */ + id: number; + /** Label */ + label: string; + /** Next Payment */ + next_payment?: number | null; + /** + * Updated At + * Format: date-time + */ + updated_at: string; + }; + /** + * AccountType + * @enum {string} + */ + AccountType: "BANK" | "PENSION" | "CRYPTO" | "SAVINGS" | "LIABILITY"; + /** AccountUpdate */ + AccountUpdate: { + /** Balance */ + balance?: number | string | null; + /** Label */ + label?: string | null; + /** Next Payment */ + next_payment?: number | string | null; + }; + /** ActualsBySource */ + ActualsBySource: { + /** Count */ + count: number; + /** Net */ + net: number; + /** Source */ + source: string; + /** Total Compra */ + total_compra: number; + /** Total Devolucion */ + total_devolucion: number; + }; + /** BalanceOverrideCreate */ + BalanceOverrideCreate: { + /** Override Balance */ + override_balance: number | string; + }; + /** BalanceOverrideRead */ + BalanceOverrideRead: { + /** Id */ + id: number; + /** Month */ + month: number; + /** Override Balance */ + override_balance: number; + /** + * Updated At + * Format: date-time + */ + updated_at: string; + /** Year */ + year: number; + }; + /** + * Bank + * @enum {string} + */ + Bank: "BAC" | "BCR" | "DAVIVIENDA" | "FCL" | "ROP" | "VOL" | "MEMP" | "MPAT" | "MORTGAGE"; + /** BillingCycle */ + BillingCycle: { + /** Count */ + count: number; + /** Label */ + label: string; + /** Month */ + month: number; + /** Total */ + total: number; + /** Year */ + year: number; + }; + /** Body_login_api_v1_auth_login_post */ + Body_login_api_v1_auth_login_post: { + /** Client Id */ + client_id?: string | null; + /** + * Client Secret + * Format: password + */ + client_secret?: string | null; + /** Grant Type */ + grant_type?: string | null; + /** + * Password + * Format: password + */ + password: string; + /** + * Scope + * @default + */ + scope: string; + /** Username */ + username: string; + }; + /** Body_upload_municipal_receipt_api_v1_municipal_receipts_upload_post */ + Body_upload_municipal_receipt_api_v1_municipal_receipts_upload_post: { + /** File */ + file: string; + }; + /** Body_upload_pension_pdfs_api_v1_pensions_upload_post */ + Body_upload_pension_pdfs_api_v1_pensions_upload_post: { + /** Files */ + files: string[]; + }; + /** BulkActionRequest */ + BulkActionRequest: { + /** Action */ + action: string; + /** Category Id */ + category_id?: number | null; + /** Deferred */ + deferred?: boolean | null; + /** Ids */ + ids: number[]; + }; + /** BulkActionResponse */ + BulkActionResponse: { + /** Affected */ + affected: number; + }; + /** CCCategorySpending */ + CCCategorySpending: { + /** Amount */ + amount: number; + /** Category Name */ + category_name: string; + }; + /** CategoryCreate */ + CategoryCreate: { + /** + * Auto Match Patterns + * @description Comma-separated merchant substrings for auto-matching + */ + auto_match_patterns?: string | null; + /** + * Icon + * @default tag + */ + icon: string; + /** Name */ + name: string; + }; + /** CategoryRead */ + CategoryRead: { + /** + * Auto Match Patterns + * @description Comma-separated merchant substrings for auto-matching + */ + auto_match_patterns?: string | null; + /** + * Icon + * @default tag + */ + icon: string; + /** Id */ + id: number; + /** Name */ + name: string; + }; + /** CategorySpending */ + CategorySpending: { + /** Category Id */ + category_id: number | null; + /** Category Name */ + category_name: string; + /** Count */ + count: number; + /** Percentage */ + percentage: number; + /** Total */ + total: number; + }; + /** CategoryUpdate */ + CategoryUpdate: { + /** Auto Match Patterns */ + auto_match_patterns?: string | null; + /** Icon */ + icon?: string | null; + /** Name */ + name?: string | null; + }; + /** + * Currency + * @enum {string} + */ + Currency: "CRC" | "USD" | "EUR" | "BTC" | "XMR"; + /** DailySpending */ + DailySpending: { + /** Count */ + count: number; + /** Date */ + date: string; + /** Total */ + total: number; + }; + /** ExchangeRateRead */ + ExchangeRateRead: { + /** Buy Rate */ + buy_rate: number; + /** + * Date + * Format: date-time + */ + date: string; + /** + * Fetched At + * Format: date-time + */ + fetched_at: string; + /** Sell Rate */ + sell_rate: number; + }; + /** HTTPValidationError */ + HTTPValidationError: { + /** Detail */ + detail?: components["schemas"]["ValidationError"][]; + }; + /** LoginRequest */ + LoginRequest: { + /** Password */ + password: string; + /** Username */ + username: string; + }; + /** MeResponse */ + MeResponse: { + /** Username */ + username: string; + }; + /** MonthlyDetailResponse */ + MonthlyDetailResponse: { + /** Actuals By Source */ + actuals_by_source: components["schemas"]["ActualsBySource"][]; + /** Cc By Category */ + cc_by_category: components["schemas"]["CCCategorySpending"][]; + /** Expense Items */ + expense_items: components["schemas"]["RecurringItemDetail"][]; + /** Gran Total Egresos */ + gran_total_egresos: number; + /** Income Items */ + income_items: components["schemas"]["RecurringItemDetail"][]; + /** Month */ + month: number; + /** Net Balance */ + net_balance: number; + /** Total Projected Expenses */ + total_projected_expenses: number; + /** Total Projected Income */ + total_projected_income: number; + /** Uncovered Actual */ + uncovered_actual: number; + /** Year */ + year: number; + }; + /** MonthlyProjectionResponse */ + MonthlyProjectionResponse: { + /** Actual Cash */ + actual_cash: number; + /** Actual Credit Card */ + actual_credit_card: number; + /** Actual Transfers */ + actual_transfers: number; + /** + * Balance Overridden + * @default false + */ + balance_overridden: boolean; + /** + * Carryover Balance + * @default 0 + */ + carryover_balance: number; + /** + * Cumulative Balance + * @default 0 + */ + cumulative_balance: number; + /** Gran Total Egresos */ + gran_total_egresos: number; + /** Month */ + month: number; + /** Net Balance */ + net_balance: number; + /** Projected Fixed Expenses */ + projected_fixed_expenses: number; + /** Projected Income */ + projected_income: number; + /** Uncovered Actual */ + uncovered_actual: number; + /** Year */ + year: number; + }; + /** MonthlyTrend */ + MonthlyTrend: { + /** Count */ + count: number; + /** Label */ + label: string; + /** Month */ + month: number; + /** Total Crc */ + total_crc: number; + /** Total Usd */ + total_usd: number; + /** Year */ + year: number; + }; + /** MunicipalReceiptDetailRead */ + MunicipalReceiptDetailRead: { + /** Account */ + account: string; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** + * Due Date + * Format: date + */ + due_date: string; + /** Finca */ + finca: string; + /** Holder Address */ + holder_address: string; + /** Holder Cedula */ + holder_cedula: string; + /** Holder Name */ + holder_name: string; + /** Id */ + id: number; + /** Interests */ + interests: number; + /** Iva */ + iva: number; + /** Period */ + period: string; + /** Raw Charges */ + raw_charges?: { + [key: string]: unknown; + }[]; + /** + * Receipt Date + * Format: date + */ + receipt_date: string; + /** Source Filename */ + source_filename: string; + /** Subtotal */ + subtotal: number; + /** Total */ + total: number; + /** + * Water Readings + * @default [] + */ + water_readings: components["schemas"]["WaterMeterReadingRead"][]; + }; + /** MunicipalReceiptRead */ + MunicipalReceiptRead: { + /** Account */ + account: string; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** + * Due Date + * Format: date + */ + due_date: string; + /** Finca */ + finca: string; + /** Holder Address */ + holder_address: string; + /** Holder Cedula */ + holder_cedula: string; + /** Holder Name */ + holder_name: string; + /** Id */ + id: number; + /** Interests */ + interests: number; + /** Iva */ + iva: number; + /** Period */ + period: string; + /** Raw Charges */ + raw_charges?: { + [key: string]: unknown; + }[]; + /** + * Receipt Date + * Format: date + */ + receipt_date: string; + /** Source Filename */ + source_filename: string; + /** Subtotal */ + subtotal: number; + /** Total */ + total: number; + }; + /** MunicipalReceiptUploadResult */ + MunicipalReceiptUploadResult: { + /** Errors */ + errors: string[]; + /** Imported */ + imported: number; + receipt?: components["schemas"]["MunicipalReceiptRead"] | null; + /** Updated */ + updated: number; + }; + /** PasteImportRequest */ + PasteImportRequest: { + /** + * Allow Duplicates + * @default false + */ + allow_duplicates: boolean; + /** @default BAC */ + bank: components["schemas"]["Bank"]; + /** @default CREDIT_CARD */ + source: components["schemas"]["TransactionSource"]; + /** Text */ + text: string; + }; + /** PasteImportResult */ + PasteImportResult: { + /** Duplicates */ + duplicates: number; + /** Errors */ + errors: string[]; + /** Imported */ + imported: number; + /** + * Skipped + * @default [] + */ + skipped: components["schemas"]["SkippedDuplicate"][]; + }; + /** PensionManualEntry */ + PensionManualEntry: { + /** Aportes */ + aportes: number; + /** + * Bonificacion + * @default 0 + */ + bonificacion: number; + /** Comision */ + comision: number; + /** + * Correccion + * @default 0 + */ + correccion: number; + /** Fund */ + fund: string; + /** + * Period End + * Format: date + */ + period_end: string; + /** + * Period Start + * Format: date + */ + period_start: string; + /** Rendimientos */ + rendimientos: number; + /** Retiros */ + retiros: number; + /** Saldo Anterior */ + saldo_anterior: number; + /** Saldo Final */ + saldo_final: number; + /** Traslados */ + traslados: number; + }; + /** PensionManualRequest */ + PensionManualRequest: { + /** Entries */ + entries: components["schemas"]["PensionManualEntry"][]; + }; + /** PensionSnapshotRead */ + PensionSnapshotRead: { + /** Aportes */ + aportes: number; + /** Bonificacion */ + bonificacion: number; + /** Comision */ + comision: number; + /** Contract Number */ + contract_number: string; + /** Correccion */ + correccion: number; + /** + * Created At + * Format: date-time + */ + created_at: string; + fund: components["schemas"]["Bank"]; + /** Id */ + id: number; + /** + * Period End + * Format: date + */ + period_end: string; + /** + * Period Start + * Format: date + */ + period_start: string; + /** Rendimientos */ + rendimientos: number; + /** Retiros */ + retiros: number; + /** Saldo Anterior */ + saldo_anterior: number; + /** Saldo Final */ + saldo_final: number; + /** Source Filename */ + source_filename: string; + /** Traslados */ + traslados: number; + }; + /** PensionUploadResult */ + PensionUploadResult: { + /** Duplicates */ + duplicates: number; + /** Errors */ + errors: string[]; + /** Imported */ + imported: number; + /** Snapshots */ + snapshots: components["schemas"]["PensionSnapshotRead"][]; + /** Updated */ + updated: number; + }; + /** PushSubscriptionCreate */ + PushSubscriptionCreate: { + /** Endpoint */ + endpoint: string; + /** Keys */ + keys: { + [key: string]: unknown; + }; + }; + /** + * RecurringFrequency + * @enum {string} + */ + RecurringFrequency: "WEEKLY" | "MONTHLY" | "QUARTERLY" | "BIANNUAL" | "YEARLY"; + /** RecurringItemCreate */ + RecurringItemCreate: { + /** Amount */ + amount: number | string; + /** Category Id */ + category_id?: number | null; + /** @default CRC */ + currency: components["schemas"]["Currency"]; + /** Day Of Month */ + day_of_month?: number | null; + /** @default MONTHLY */ + frequency: components["schemas"]["RecurringFrequency"]; + /** + * Is Active + * @default true + */ + is_active: boolean; + item_type: components["schemas"]["RecurringItemType"]; + /** Month Of Year */ + month_of_year?: number | null; + /** Name */ + name: string; + /** Notes */ + notes?: string | null; + /** Override Amounts */ + override_amounts?: { + [key: string]: unknown; + } | null; + }; + /** RecurringItemDetail */ + RecurringItemDetail: { + /** Amount */ + amount: number; + /** Category Id */ + category_id?: number | null; + /** Category Name */ + category_name?: string | null; + /** Frequency */ + frequency: string; + /** Id */ + id: number; + /** Item Type */ + item_type: string; + /** Name */ + name: string; + /** Projected Amount */ + projected_amount?: number | null; + /** + * Used Actual + * @default false + */ + used_actual: boolean; + }; + /** RecurringItemRead */ + RecurringItemRead: { + /** Amount */ + amount: number; + category?: components["schemas"]["CategoryRead"] | null; + /** Category Id */ + category_id?: number | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** @default CRC */ + currency: components["schemas"]["Currency"]; + /** Day Of Month */ + day_of_month?: number | null; + /** @default MONTHLY */ + frequency: components["schemas"]["RecurringFrequency"]; + /** Id */ + id: number; + /** + * Is Active + * @default true + */ + is_active: boolean; + item_type: components["schemas"]["RecurringItemType"]; + /** Month Of Year */ + month_of_year?: number | null; + /** Name */ + name: string; + /** Notes */ + notes?: string | null; + /** Override Amounts */ + override_amounts?: { + [key: string]: unknown; + } | null; + }; + /** + * RecurringItemType + * @enum {string} + */ + RecurringItemType: "INCOME" | "EXPENSE" | "SAVINGS"; + /** RecurringItemUpdate */ + RecurringItemUpdate: { + /** Amount */ + amount?: number | string | null; + /** Category Id */ + category_id?: number | null; + currency?: components["schemas"]["Currency"] | null; + /** Day Of Month */ + day_of_month?: number | null; + frequency?: components["schemas"]["RecurringFrequency"] | null; + /** Is Active */ + is_active?: boolean | null; + item_type?: components["schemas"]["RecurringItemType"] | null; + /** Month Of Year */ + month_of_year?: number | null; + /** Name */ + name?: string | null; + /** Notes */ + notes?: string | null; + /** Override Amounts */ + override_amounts?: { + [key: string]: unknown; + } | null; + }; + /** SalariosSummary */ + SalariosSummary: { + /** Count */ + count: number; + /** Latest Date */ + latest_date?: string | null; + /** Total Amount */ + total_amount: number; + }; + /** SavingsAccrualCreate */ + SavingsAccrualCreate: { + /** + * Memp Amount + * @default 200000 + */ + memp_amount: number | string; + /** Month */ + month: number; + /** + * Mpat Amount + * @default 200000 + */ + mpat_amount: number | string; + /** Notes */ + notes?: string | null; + /** Trigger Transaction Id */ + trigger_transaction_id?: number | null; + /** Year */ + year: number; + }; + /** SavingsAccrualRead */ + SavingsAccrualRead: { + /** + * Applied At + * Format: date-time + */ + applied_at: string; + /** Id */ + id: number; + /** + * Memp Amount + * @default 200000 + */ + memp_amount: number; + /** Month */ + month: number; + /** + * Mpat Amount + * @default 200000 + */ + mpat_amount: number; + /** Notes */ + notes?: string | null; + /** Trigger Transaction Id */ + trigger_transaction_id?: number | null; + /** Year */ + year: number; + }; + /** SavingsAccrualUpdate */ + SavingsAccrualUpdate: { + /** Memp Amount */ + memp_amount?: number | string | null; + /** Mpat Amount */ + mpat_amount?: number | string | null; + /** Notes */ + notes?: string | null; + }; + /** SkippedDuplicate */ + SkippedDuplicate: { + /** Amount */ + amount: number; + /** Currency */ + currency: string; + /** Date */ + date: string; + /** Existing Amount */ + existing_amount: number; + /** Existing Date */ + existing_date: string; + /** Existing Id */ + existing_id: number; + /** Existing Merchant */ + existing_merchant: string; + /** Existing Source */ + existing_source: string; + /** Line */ + line: number; + /** Merchant */ + merchant: string; + }; + /** SubscriptionStatusResponse */ + SubscriptionStatusResponse: { + /** Status */ + status: string; + }; + /** SyncSourceStatus */ + SyncSourceStatus: { + /** Age Days */ + age_days: number | null; + /** Description */ + description: string; + /** Key */ + key: string; + /** Label */ + label: string; + /** Last Received */ + last_received: string | null; + /** Status */ + status: string; + /** Warn After Days */ + warn_after_days: number; + }; + /** SyncStatusResponse */ + SyncStatusResponse: { + /** Sources */ + sources: components["schemas"]["SyncSourceStatus"][]; + /** Warnings */ + warnings: number; + }; + /** TokenCreatedResponse */ + TokenCreatedResponse: { + /** Expires At */ + expires_at: string | null; + /** Name */ + name: string; + /** Token */ + token: string; + }; + /** TokenResponse */ + TokenResponse: { + /** Access Token */ + access_token: string; + /** Token Type */ + token_type: string; + }; + /** TransactionCreate */ + TransactionCreate: { + /** Amount */ + amount: number | string; + /** Authorization Code */ + authorization_code?: string | null; + /** @default BAC */ + bank: components["schemas"]["Bank"]; + /** Card Last4 */ + card_last4?: string | null; + /** Card Type */ + card_type?: string | null; + /** Category Id */ + category_id?: number | null; + /** City */ + city?: string | null; + /** @default CRC */ + currency: components["schemas"]["Currency"]; + /** + * Date + * Format: date-time + */ + date: string; + /** + * Deferred To Next Cycle + * @default false + */ + deferred_to_next_cycle: boolean; + /** Merchant */ + merchant: string; + /** Notes */ + notes?: string | null; + /** Reference */ + reference?: string | null; + /** @default CREDIT_CARD */ + source: components["schemas"]["TransactionSource"]; + /** @default COMPRA */ + transaction_type: components["schemas"]["TransactionType"]; + }; + /** TransactionRead */ + TransactionRead: { + /** Amount */ + amount: number; + /** Authorization Code */ + authorization_code?: string | null; + /** @default BAC */ + bank: components["schemas"]["Bank"]; + /** Card Last4 */ + card_last4?: string | null; + /** Card Type */ + card_type?: string | null; + category?: components["schemas"]["CategoryRead"] | null; + /** Category Id */ + category_id?: number | null; + /** City */ + city?: string | null; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** @default CRC */ + currency: components["schemas"]["Currency"]; + /** + * Date + * Format: date-time + */ + date: string; + /** + * Deferred To Next Cycle + * @default false + */ + deferred_to_next_cycle: boolean; + /** Id */ + id: number; + /** Merchant */ + merchant: string; + /** Notes */ + notes?: string | null; + /** Reference */ + reference?: string | null; + /** @default CREDIT_CARD */ + source: components["schemas"]["TransactionSource"]; + /** @default COMPRA */ + transaction_type: components["schemas"]["TransactionType"]; + }; + /** + * TransactionSource + * @enum {string} + */ + TransactionSource: "CREDIT_CARD" | "CASH" | "TRANSFER"; + /** + * TransactionType + * @enum {string} + */ + TransactionType: "COMPRA" | "DEVOLUCION" | "DEPOSITO" | "SALARY"; + /** TransactionUpdate */ + TransactionUpdate: { + /** Amount */ + amount?: number | string | null; + /** Category Id */ + category_id?: number | null; + /** City */ + city?: string | null; + currency?: components["schemas"]["Currency"] | null; + /** Date */ + date?: string | null; + /** Deferred To Next Cycle */ + deferred_to_next_cycle?: boolean | null; + /** Merchant */ + merchant?: string | null; + /** Notes */ + notes?: string | null; + source?: components["schemas"]["TransactionSource"] | null; + transaction_type?: components["schemas"]["TransactionType"] | null; + }; + /** UserSettingsRead */ + UserSettingsRead: { + /** Data */ + data: { + [key: string]: unknown; + }; + /** Key */ + key: string; + /** + * Updated At + * Format: date-time + */ + updated_at: string; + }; + /** UserSettingsUpdate */ + UserSettingsUpdate: { + /** Data */ + data: { + [key: string]: unknown; + }; + }; + /** ValidationError */ + ValidationError: { + /** Context */ + ctx?: Record; + /** Input */ + input?: unknown; + /** Location */ + loc: (string | number)[]; + /** Message */ + msg: string; + /** Error Type */ + type: string; + }; + /** VapidPublicKeyResponse */ + VapidPublicKeyResponse: { + /** Publickey */ + publicKey: string; + }; + /** WaterMeterReadingRead */ + WaterMeterReadingRead: { + /** + * Agua Potable + * @default 0 + */ + agua_potable: number; + /** + * Alcant Sanitario + * @default 0 + */ + alcant_sanitario: number; + /** Consumption M3 */ + consumption_m3: number; + /** + * Created At + * Format: date-time + */ + created_at: string; + /** Id */ + id: number; + /** + * Is Historical + * @default false + */ + is_historical: boolean; + /** + * Iva + * @default 0 + */ + iva: number; + /** Meter Id */ + meter_id: string; + /** Period */ + period: string; + /** + * Reading Current + * @default 0 + */ + reading_current: number; + /** + * Reading Previous + * @default 0 + */ + reading_previous: number; + /** Receipt Id */ + receipt_id?: number | null; + /** + * Serv Ambientales + * @default 0 + */ + serv_ambientales: number; + }; + /** YearlyProjectionResponse */ + YearlyProjectionResponse: { + /** Annual Expenses */ + annual_expenses: number; + /** Annual Income */ + annual_income: number; + /** Annual Net */ + annual_net: number; + /** Months */ + months: components["schemas"]["MonthlyProjectionResponse"][]; + /** Year */ + year: number; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + root__get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + cookie_login_api_auth_login_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["LoginRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + cookie_logout_api_auth_logout_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + health_api_health_get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + }; + }; + list_accounts_api_v1_accounts__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AccountRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_account_api_v1_accounts__post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AccountCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AccountRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_account_api_v1_accounts__account_id__patch: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + account_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AccountUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AccountRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + agent_endpoint_api_v1_agent_agui_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AGUIRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + spending_by_category_api_v1_analytics_by_category_get: { + parameters: { + query?: { + cycle_year?: number | null; + cycle_month?: number | null; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CategorySpending"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + daily_spending_api_v1_analytics_daily_spending_get: { + parameters: { + query?: { + cycle_year?: number | null; + cycle_month?: number | null; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DailySpending"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + monthly_trend_api_v1_analytics_monthly_trend_get: { + parameters: { + query?: { + months?: number; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MonthlyTrend"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + login_api_v1_auth_login_post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/x-www-form-urlencoded": components["schemas"]["Body_login_api_v1_auth_login_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TokenResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + me_api_v1_auth_me_get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MeResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + upsert_balance_override_api_v1_budget_balance_override__year___month__put: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + year: number; + month: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BalanceOverrideCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BalanceOverrideRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_balance_override_api_v1_budget_balance_override__year___month__delete: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + year: number; + month: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_monthly_detail_api_v1_budget_month__year___month__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + year: number; + month: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MonthlyDetailResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_yearly_projection_api_v1_budget_projection__year__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + year: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["YearlyProjectionResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_recurring_items_api_v1_budget_recurring_get: { + parameters: { + query?: { + item_type?: components["schemas"]["RecurringItemType"] | null; + is_active?: boolean | null; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RecurringItemRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_recurring_item_api_v1_budget_recurring_post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RecurringItemCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RecurringItemRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_recurring_item_api_v1_budget_recurring__item_id__delete: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + item_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_recurring_item_api_v1_budget_recurring__item_id__patch: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + item_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RecurringItemUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RecurringItemRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_categories_api_v1_categories__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CategoryRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_category_api_v1_categories__post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CategoryCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CategoryRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_category_api_v1_categories__category_id__delete: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + category_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_category_api_v1_categories__category_id__patch: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + category_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["CategoryUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["CategoryRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + current_rate_api_v1_exchange_rate__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExchangeRateRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + rate_history_api_v1_exchange_rate_history_get: { + parameters: { + query?: { + days?: number; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ExchangeRateRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + paste_import_api_v1_import_paste_post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PasteImportRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PasteImportResult"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_receipts_api_v1_municipal_receipts__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MunicipalReceiptRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + upload_municipal_receipt_api_v1_municipal_receipts_upload_post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_municipal_receipt_api_v1_municipal_receipts_upload_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MunicipalReceiptUploadResult"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_water_consumption_api_v1_municipal_receipts_water_consumption_get: { + parameters: { + query?: { + months?: number; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WaterMeterReadingRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_receipt_detail_api_v1_municipal_receipts__receipt_id__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + receipt_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["MunicipalReceiptDetailRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + subscribe_api_v1_notifications_subscribe_post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PushSubscriptionCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SubscriptionStatusResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + unsubscribe_api_v1_notifications_unsubscribe_delete: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PushSubscriptionCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SubscriptionStatusResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_vapid_public_key_api_v1_notifications_vapid_public_key_get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["VapidPublicKeyResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_fund_summary_api_v1_pensions_fund_summary_get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PensionSnapshotRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + submit_manual_entries_api_v1_pensions_manual_post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["PensionManualRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PensionUploadResult"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_snapshots_api_v1_pensions_snapshots_get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PensionSnapshotRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + upload_pension_pdfs_api_v1_pensions_upload_post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "multipart/form-data": components["schemas"]["Body_upload_pension_pdfs_api_v1_pensions_upload_post"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["PensionUploadResult"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_salarios_api_v1_salarios__get: { + parameters: { + query?: { + limit?: number; + offset?: number; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TransactionRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + salarios_summary_api_v1_salarios_summary_get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SalariosSummary"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_accruals_api_v1_savings_accrual__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SavingsAccrualRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_accrual_api_v1_savings_accrual__post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SavingsAccrualCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SavingsAccrualRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_accrual_api_v1_savings_accrual__accrual_id__delete: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + accrual_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_accrual_api_v1_savings_accrual__accrual_id__patch: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + accrual_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SavingsAccrualUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SavingsAccrualRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + get_settings_api_v1_settings__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UserSettingsRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_settings_api_v1_settings__patch: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["UserSettingsUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UserSettingsRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + sync_status_api_v1_sync_status__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SyncStatusResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_tokens_api_v1_tokens__get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["APITokenRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_token_api_v1_tokens__post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["APITokenCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TokenCreatedResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + revoke_token_api_v1_tokens__token_id__delete: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + token_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_transactions_api_v1_transactions__get: { + parameters: { + query?: { + source?: components["schemas"]["TransactionSource"] | null; + exclude_source?: components["schemas"]["TransactionSource"] | null; + search?: string | null; + category_id?: number | null; + cycle_year?: number | null; + cycle_month?: number | null; + start_date?: string | null; + end_date?: string | null; + limit?: number; + offset?: number; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TransactionRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + create_transaction_api_v1_transactions__post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TransactionCreate"]; + }; + }; + responses: { + /** @description Successful Response */ + 201: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TransactionRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + bulk_action_api_v1_transactions_bulk_post: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["BulkActionRequest"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BulkActionResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + list_billing_cycles_api_v1_transactions_cycles_get: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["BillingCycle"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + export_transactions_csv_api_v1_transactions_export_get: { + parameters: { + query?: { + source?: components["schemas"]["TransactionSource"] | null; + transaction_type?: components["schemas"]["TransactionType"] | null; + start_date?: string | null; + end_date?: string | null; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": unknown; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + recent_transactions_api_v1_transactions_recent_get: { + parameters: { + query?: { + limit?: number; + }; + header?: { + authorization?: string | null; + }; + path?: never; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TransactionRead"][]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + delete_transaction_api_v1_transactions__transaction_id__delete: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + transaction_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody?: never; + responses: { + /** @description Successful Response */ + 204: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; + update_transaction_api_v1_transactions__transaction_id__patch: { + parameters: { + query?: never; + header?: { + authorization?: string | null; + }; + path: { + transaction_id: number; + }; + cookie?: { + ws_token?: string | null; + }; + }; + requestBody: { + content: { + "application/json": components["schemas"]["TransactionUpdate"]; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["TransactionRead"]; + }; + }; + /** @description Validation Error */ + 422: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; +} diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 86999f7..af10707 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -1,3 +1,10 @@ +import type { components } from "./api-types.gen"; + +/** Schemas generated from the backend OpenAPI spec (pnpm gen:api). Aliasing + * the app's read types to them makes backend schema drift a typecheck error + * instead of a runtime surprise (review ARCH-16 / FE-14 / plan 3.4). */ +type Schema = components["schemas"][K]; + const BASE_URL = "/api/v1"; class ApiError extends Error { @@ -125,91 +132,22 @@ export async function logout() { // ─── Types ────────────────────────────────────────────────────────────────── -export interface Account { - id: number; - bank: string; - currency: string; - label: string; - balance: number; - account_type: string; - next_payment: number | null; - updated_at: string; -} +export type Account = Schema<'AccountRead'>; -export interface Category { - id: number; - name: string; - icon: string; - auto_match_patterns: string | null; -} +export type Category = Schema<'CategoryRead'>; -export interface SkippedDuplicate { - line: number; - merchant: string; - date: string; - amount: number; - currency: string; - existing_id: number; - existing_merchant: string; - existing_date: string; - existing_amount: number; - existing_source: string; -} +export type SkippedDuplicate = Schema<'SkippedDuplicate'>; -export interface ImportResult { - imported: number; - duplicates: number; - errors: string[]; - skipped: SkippedDuplicate[]; -} +export type ImportResult = Schema<'PasteImportResult'>; -export interface Transaction { - id: number; - amount: number; - currency: string; - merchant: string; - city: string | null; - date: string; - card_type: string | null; - card_last4: string | null; - authorization_code: string | null; - reference: string | null; - transaction_type: string; - source: string; - bank: string; - notes: string | null; - category_id: number | null; - category: Category | null; - deferred_to_next_cycle: boolean; - created_at: string; -} +export type Transaction = Schema<'TransactionRead'>; // --- Budget / Recurring Items --- -export type RecurringItemType = "INCOME" | "EXPENSE"; -export type RecurringFrequency = - | "WEEKLY" - | "MONTHLY" - | "QUARTERLY" - | "BIANNUAL" - | "YEARLY"; +export type RecurringItemType = Schema<"RecurringItemType">; +export type RecurringFrequency = Schema<"RecurringFrequency">; -export interface RecurringItem { - id: number; - name: string; - amount: number; - currency: string; - item_type: RecurringItemType; - frequency: RecurringFrequency; - day_of_month: number | null; - month_of_year: number | null; - override_amounts: Record | null; - category_id: number | null; - is_active: boolean; - notes: string | null; - created_at: string; - category: Category | null; -} +export type RecurringItem = Schema<'RecurringItemRead'>; export interface RecurringItemCreate { name: string; @@ -239,76 +177,19 @@ export interface RecurringItemUpdate { notes?: string | null; } -export interface RecurringItemDetail { - id: number; - name: string; - amount: number; - projected_amount: number | null; - used_actual: boolean; - item_type: string; - frequency: string; - category_name: string | null; - category_id: number | null; -} +export type RecurringItemDetail = Schema<'RecurringItemDetail'>; -export interface ActualsBySource { - source: string; - total_compra: number; - total_devolucion: number; - net: number; - count: number; -} +export type ActualsBySource = Schema<'ActualsBySource'>; -export interface MonthlyProjection { - month: number; - year: number; - projected_income: number; - projected_fixed_expenses: number; - actual_credit_card: number; - actual_cash: number; - actual_transfers: number; - uncovered_actual: number; - gran_total_egresos: number; - net_balance: number; - carryover_balance: number; - cumulative_balance: number; - balance_overridden: boolean; -} +export type MonthlyProjection = Schema<'MonthlyProjectionResponse'>; -export interface YearlyProjection { - year: number; - months: MonthlyProjection[]; - annual_income: number; - annual_expenses: number; - annual_net: number; -} +export type YearlyProjection = Schema<'YearlyProjectionResponse'>; -export interface MonthlyDetail { - year: number; - month: number; - income_items: RecurringItemDetail[]; - expense_items: RecurringItemDetail[]; - actuals_by_source: ActualsBySource[]; - total_projected_income: number; - total_projected_expenses: number; - uncovered_actual: number; - gran_total_egresos: number; - net_balance: number; - cc_by_category: { category_name: string; amount: number }[]; -} +export type MonthlyDetail = Schema<'MonthlyDetailResponse'>; // --- Savings Accrual --- -export interface SavingsAccrual { - id: number; - year: number; - month: number; - memp_amount: number; - mpat_amount: number; - trigger_transaction_id: number | null; - applied_at: string; - notes: string | null; -} +export type SavingsAccrual = Schema<'SavingsAccrualRead'>; export interface SavingsAccrualCreate { year: number; @@ -361,11 +242,7 @@ export const deleteBalanceOverride = (year: number, month: number) => // --- Salarios --- -export interface SalariosSummary { - count: number; - total_amount: number; - latest_date: string | null; -} +export type SalariosSummary = Schema<'SalariosSummary'>; export const getSalarios = (params?: { limit?: number; offset?: number }) => api.get("/salarios/", { params }); @@ -374,32 +251,9 @@ export const getSalariosSummary = () => // --- Pensions --- -export interface PensionSnapshot { - id: number; - fund: string; - contract_number: string; - period_start: string; - period_end: string; - saldo_anterior: number; - aportes: number; - rendimientos: number; - retiros: number; - traslados: number; - comision: number; - correccion: number; - bonificacion: number; - saldo_final: number; - source_filename: string; - created_at: string; -} +export type PensionSnapshot = Schema<'PensionSnapshotRead'>; -export interface PensionUploadResult { - imported: number; - updated: number; - duplicates: number; - errors: string[]; - snapshots: PensionSnapshot[]; -} +export type PensionUploadResult = Schema<'PensionUploadResult'>; export interface PensionManualEntry { fund: string; @@ -436,51 +290,19 @@ export interface MunicipalCharge { amount: number; } -export interface WaterMeterReading { - id: number; - meter_id: string; - period: string; - reading_previous: number; - reading_current: number; - consumption_m3: number; - agua_potable: number; - serv_ambientales: number; - alcant_sanitario: number; - iva: number; - is_historical: boolean; - receipt_id: number | null; - created_at: string; -} +export type WaterMeterReading = Schema<'WaterMeterReadingRead'>; -export interface MunicipalReceipt { - id: number; - receipt_date: string; - due_date: string; - period: string; - account: string; - finca: string; - holder_name: string; - holder_cedula: string; - holder_address: string; - subtotal: number; - interests: number; - iva: number; - total: number; - raw_charges: MunicipalCharge[]; - source_filename: string; - created_at: string; -} +export type MunicipalReceipt = Omit< + Schema<'MunicipalReceiptRead'>, + 'raw_charges' +> & { raw_charges: MunicipalCharge[] }; -export interface MunicipalReceiptDetail extends MunicipalReceipt { - water_readings: WaterMeterReading[]; -} +export type MunicipalReceiptDetail = Omit< + Schema<'MunicipalReceiptDetailRead'>, + 'raw_charges' +> & { raw_charges: MunicipalCharge[] }; -export interface MunicipalReceiptUploadResult { - imported: number; - updated: number; - errors: string[]; - receipt: MunicipalReceipt | null; -} +export type MunicipalReceiptUploadResult = Schema<'MunicipalReceiptUploadResult'>; export const uploadMunicipalReceipt = (file: File) => { const form = new FormData(); @@ -502,20 +324,9 @@ export const getWaterConsumption = (months?: number) => // --- Sync Status --- -export interface SyncSource { - key: string; - label: string; - description: string; - last_received: string | null; - age_days: number | null; - warn_after_days: number; - status: 'ok' | 'warning' | 'never'; -} +export type SyncSource = Schema<'SyncSourceStatus'>; -export interface SyncStatusResponse { - sources: SyncSource[]; - warnings: number; -} +export type SyncStatusResponse = Schema<'SyncStatusResponse'>; export const getSyncStatus = () => api.get('/sync-status/'); @@ -523,10 +334,6 @@ export const getSyncStatus = () => api.get('/sync-status/'); export type UserSettingsData = Record; -export interface UserSettingsRead { - key: string; - data: UserSettingsData; - updated_at: string; -} +export type UserSettingsRead = Schema<'UserSettingsRead'>; export const getUserSettings = () => api.get('/settings/');