Commit graph

75 commits

Author SHA1 Message Date
81b1cacc5c refactor(llm): use response_schema with typed enums in all Gemini calls
Pass response_schema to all three generate_content calls so Gemini
constrains its output to valid enum values and correct JSON structure:

- routines.py: _StepOut.action_type Optional[str] → Optional[GroomingAction]
- skincare.py: add _SkinAnalysisOut(PydanticBase) with OverallSkinState,
  SkinType, SkinTexture, BarrierState, SkinConcern enums; add response_schema
- products.py: pass ProductParseResponse directly as response_schema;
  remove NaN/Infinity/undefined regex cleanup, markdown-fence extraction,
  finish_reason logging, and re import — all now unnecessary

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 00:46:23 +01:00
6e7f715ef2 feat: AI-generated skincare routine suggestions (single + batch)
Add Gemini-powered endpoints and frontend pages for proposing skincare
routines based on skin state, product compatibility, grooming schedule,
and recent history.

Backend (routines.py):
- POST /routines/suggest — single AM/PM routine for a date
- POST /routines/suggest-batch — AM+PM plan for up to 14 days
- Prompt context: skin snapshot, grooming schedule, 7-day history,
  filtered product list with effects/incompatibilities/context rules
- Respects retinoid frequency limits, acid/retinoid separation,
  grooming-aware safe_after_shaving rules

Frontend:
- /routines/suggest page with tab switcher (single / batch)
- Single tab: date + AM/PM + optional notes → generate → preview → save
- Batch tab: date range + notes → collapsible day cards (AM+PM) → save all
- Loading spinner during Gemini calls; product names resolved from map
- "Zaproponuj rutynę AI" button added to routines list page

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-01 00:34:43 +01:00
1b1566e6d7 feat(frontend): group products by category with ownership filter
Replace category filter dropdown with client-side grouping and a
3-way ownership toggle (All / Owned / Not owned). Products are grouped
by category with header rows as visual dividers, sorted brand → name
within each group. Category column removed (redundant with headings).

Backend: GET /products now returns ProductWithInventory so inventory
data is available for ownership filtering (bulk-loaded in one query).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 23:07:37 +01:00
2691708304 fix(models): cascade delete inventory rows when product is deleted
SQLAlchemy was nulling out product_id on ProductInventory rows instead
of deleting them. Added cascade="all, delete-orphan" to the ORM
relationship and ondelete="CASCADE" to the FK field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:48:00 +01:00
794650afc6 feat(models): add anti_aging to IngredientFunction enum
Model was emitting "anti_aging" as a valid ingredient function
(e.g. for retinoids, peptides). Add it to the enum and the
parse-text system prompt allowed values.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:32:57 +01:00
a3753d0929 fix(backend): restore response_mime_type=json, raise max_output_tokens to 16384
Regular generation was hitting MAX_TOKENS at 8192. Constrained decoding with
16384 should be a viable middle ground between the truncation at 8192 and the
timeout at 65536.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:26:41 +01:00
3fbf6d7041 fix(backend): drop response_mime_type=application/json to avoid constrained decoding
Constrained decoding is ~10x slower and consumes hidden tokens for constraint
processing, causing truncation at ~1000 chars even with 8192 max_output_tokens.
The system prompt already instructs the model to output raw minified JSON; our
NaN/markdown-fence sanitisation handles edge cases.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 22:03:49 +01:00
26069f5d66 fix(backend): increase max_output_tokens to 65536, log finish_reason on error
Replace truncation-recovery heuristic with a higher token budget.
On JSON parse failure, log finish_reason and 160-char error context
to make the root cause visible.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:57:12 +01:00
4abdc88286 fix(backend): request minified JSON from Gemini to avoid token truncation
Pretty-printed JSON wastes 2-3x tokens on indentation/newlines.
Minified output fits more data (e.g. long INCI lists) within the
8192 output token limit.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:49:26 +01:00
3e85858d41 fix(backend): sanitize NaN/Infinity/undefined in Gemini JSON response
Models sometimes emit JS-style literals for unknown numeric fields.
Replace NaN, Infinity, undefined with null before parsing.
Also add error logging to capture raw response on parse failure.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:46:47 +01:00
54903a3bed fix(backend): handle invalid/empty JSON from Gemini in product parse endpoint
- Increase max_output_tokens 4096 → 8192 to prevent truncated JSON on
  products with long INCI lists
- Return explicit 502 when response.text is None (safety filter blocks)
- Fallback JSON extraction strips markdown fences or leading preamble

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 21:43:39 +01:00
3c1dcbeb06 feat(backend): add Alembic migrations
- Add alembic 1.14 to dependencies (uv sync → 1.18.4 installed)
- Configure alembic/env.py: loads DATABASE_URL from env, imports all
  SQLModel models so metadata is fully populated for autogenerate
- Generate initial migration (c2d626a2b36c) covering all 9 tables:
  products, product_inventory, medication_entries, medication_usages,
  lab_results, routines, routine_steps, grooming_schedule,
  skin_condition_snapshots — with all indexes and constraints
- Add ExecStartPre to innercontext.service: runs alembic upgrade head
  before uvicorn starts (idempotent, safe on every restart)
- Update DEPLOYMENT.md: add migration step to backend setup and update
  flow; document alembic stamp head for existing installations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 20:14:57 +01:00
ac829171d9 feat(mcp): add FastMCP server with 14 tools for LLM agent access
- Add backend/innercontext/mcp_server.py with tools covering products,
  inventory, routines, skin snapshots, medications, lab results, and
  grooming schedule
- Mount MCP app at /mcp in main.py using combine_lifespans
- Fix test isolation: patch app.router.lifespan_context in conftest to
  avoid StreamableHTTPSessionManager single-run limitation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 17:59:11 +01:00
4954d4f449 refactor(skin): replace trend with texture field on SkinConditionSnapshot
Remove the derived `trend` field (better computed from history by the MCP
agent) and add `texture: smooth|rough|flaky|bumpy` which LLM can reliably
assess from photos. Updates model, API, system prompt, tests, and frontend.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 13:25:57 +01:00
abf9593857 fix: correct Part.from_text() call and increase max_output_tokens for skin analysis
Pass `text=` as keyword arg to Part.from_text() and raise max_output_tokens
from 1024 to 2048 to prevent JSON truncation in the notes field.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 13:17:22 +01:00
66ee473deb feat: AI photo analysis for skin snapshots
Add POST /skincare/analyze-photos endpoint that accepts 1–3 skin
photos, sends them to Gemini vision, and returns a structured
SkinPhotoAnalysisResponse for pre-filling the snapshot form.

Extract shared Gemini client setup into innercontext/llm.py
(get_gemini_client) so both products and skincare use a single
default model (gemini-flash-latest) and API key check.

Frontend: AI photo card on /skin page with file picker, previews,
and auto-fill of all form fields from the analysis result.
New fields (skin_type, sebum_tzone, sebum_cheeks) added to form
and server action.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-28 12:47:51 +01:00
e60dee5015 style: reformat import block in main.py
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 23:05:14 +01:00
31e030eaac feat: AI pre-fill for product form via Gemini API
Add POST /products/parse-text endpoint that accepts raw product text,
calls Gemini (google-genai) with a structured extraction prompt, and
returns a partial ProductParseResponse. Frontend gains a collapsible
"AI pre-fill" card at the top of ProductForm that merges the LLM
response into all form fields reactively.

- Backend: ProductParseRequest/Response schemas, system prompt with
  enum constraints, temperature=0.0 for deterministic extraction,
  effect_profile always returned in full
- Frontend: parseProductText() in api.ts; controlled $state bindings
  for all text/number/checkbox inputs; applyAiResult() merges response

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 23:04:24 +01:00
43fcba4de6 feat: add full/empty weight fields to Product and last_weighed_at to ProductInventory
Adds full_weight_g and empty_weight_g to ProductBase (inherited by Product
and response models) so per-product package weight specs are captured.
Adds last_weighed_at to ProductInventory to record when a package was last
weighed. Wires up all fields through API schemas, frontend types, forms, and
the product detail page (add/edit/display).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 16:35:08 +01:00
c09acc7c81 refactor: split table models into Base/Table/Public for proper FastAPI serialization
Add ProductBase, ProductPublic, ProductWithInventory and
SkinConditionSnapshotBase, SkinConditionSnapshotPublic. Table models now inherit
from their Base counterpart and override JSON fields with sa_column. All
field_serializer hacks removed; FastAPI response models use the non-table Public
classes so Pydantic coerces raw DB dicts → typed models cleanly. ProductCreate
and SnapshotCreate now simply inherit their respective Base classes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 15:37:46 +01:00
6333c6678a refactor: remove personal_rating, DRY get_or_404, fix ty errors
- Drop Product.personal_rating from model, API schemas, and all frontend
  views (list table, detail view, quick-edit form, new-product form)
- Extract get_or_404 into backend/innercontext/api/utils.py; remove five
  duplicate copies from individual API modules
- Fix all ty type errors: generic get_or_404 with TypeVar, cast() in
  coerce_effect_profile validator, col() for ilike on SQLModel column,
  dict[str, Any] annotation in test helper, ty: ignore for CORSMiddleware

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 11:20:13 +01:00
9a069508af refactor: remove routine_role, recommended_frequency, evidence_level, cumulative_with
Drop fields identified as redundant or low-value from the Product model,
API schemas, frontend types, and forms. Raise effect_profile threshold in
to_llm_context() from >0 to >=2 to suppress noise values. Remove sku/barcode
from LLM context output (kept on model for catalog use).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-27 10:22:59 +01:00
9bf94a979c fix: resolve frontend/backend integration bugs
- Rename skincare route prefix /skin-snapshots → /skincare to match API client
- Add redirect_slashes=False to FastAPI app; change collection routes from "/" to ""
  to eliminate 307 redirects on POST/GET without trailing slash
- Fix redirect() inside try/catch in products/new and routines/new server actions
  (SvelteKit redirect() throws and was being caught as a 500 error)
- Eagerly load inventory and steps relationships via explicit SELECT + model_dump(mode="json"),
  working around SQLModel 0.0.37 not serializing Relationship fields in response_model
- Add field_validator for product_effect_profile to coerce DB-returned dict → ProductEffectProfile,
  eliminating Pydantic serializer warning
- Update all tests to use routes without trailing slash

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 21:53:17 +01:00
8d4f9d1fc6 fix: load .env via python-dotenv; SQLite default for local dev 2026-02-26 20:51:13 +01:00
8f7d893a63 Initial commit: backend API, data models, and test suite
FastAPI backend for personal health and skincare data with MCP export.
Includes SQLModel models for products, inventory, medications, lab results,
routines, and skin condition snapshots. Pytest suite with 111 tests running
on SQLite in-memory (no PostgreSQL required).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-26 15:10:24 +01:00