From b0790d719c907e0e5cf36c88e7ba6b0de6586319 Mon Sep 17 00:00:00 2001 From: "dolgolyov.alexei" Date: Thu, 19 Mar 2026 15:15:39 +0300 Subject: [PATCH] Generalize from health-specific to universal personal assistant The app manages multiple life areas (health, finance, personal, work), not just health. Updated all health-specific language throughout: Backend: - Default system prompt: general personal assistant (not health-only) - AI tool descriptions: generic (not health records/medications) - Memory categories: health, finance, personal, work, document_summary, other (replaces condition, medication, allergy, vital) - PDF template: "Prepared for" (not "Patient"), "Key Information" (not "Health Profile") - Renamed generate_health_pdf -> generate_pdf_report, health_report.html -> report.html - Renamed run_daily_health_review -> run_daily_review - Context assembly: "User Profile" (not "Health Profile") - OpenAPI: generic descriptions Frontend: - Dashboard subtitle: "Your personal AI assistant" - Memory categories: Health, Finance, Personal, Work - Document types: Report, Contract, Receipt, Certificate (not lab_result, etc.) - Updated en + ru translations throughout Documentation: - README: general personal assistant description - Removed health-only feature descriptions Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 12 +++++------ backend/app/api/v1/pdf.py | 2 +- backend/app/main.py | 6 +++--- backend/app/schemas/memory.py | 2 +- backend/app/services/ai_service.py | 18 ++++++++--------- backend/app/services/context_service.py | 14 ++++++------- backend/app/services/pdf_service.py | 4 ++-- backend/app/services/scheduler_service.py | 6 +++--- .../pdf/{health_report.html => report.html} | 4 ++-- backend/app/workers/health_review.py | 15 +++++++------- frontend/public/locales/en/translation.json | 20 +++++++++---------- frontend/public/locales/ru/translation.json | 20 +++++++++---------- .../components/documents/upload-dialog.tsx | 2 +- .../src/components/memory/memory-editor.tsx | 2 +- 14 files changed, 63 insertions(+), 64 deletions(-) rename backend/app/templates/pdf/{health_report.html => report.html} (97%) diff --git a/README.md b/README.md index c5478b8..0820749 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,14 @@ # Personal AI Assistant -A client-server web application for managing personal health and life areas with AI-powered assistance. Upload documents, chat with AI specialists, receive proactive health reminders, and track critical information across multiple life domains. +A client-server web application for managing different areas of personal life with AI-powered assistance. Upload documents, chat with AI specialists, receive proactive reminders, and track critical information across multiple life domains (health, finance, personal, work, etc.). ## Key Features -- **AI Chat with Specialists** — create chats using configurable skills (e.g., cardiologist, nutritionist). Each skill shapes the AI's behavior as a domain expert. -- **Document Management** — upload health records, lab results, prescriptions, and consultation notes. AI extracts and indexes content for intelligent retrieval. -- **Proactive Notifications** — AI analyzes your health profile and schedules reminders (checkups, medication reviews) via in-app, email, or Telegram. -- **PDF Compilation** — request AI-generated health summaries as downloadable PDF documents. -- **Global Memory** — AI maintains a shared memory of critical health information across all your chats. +- **AI Chat with Specialists** — create chats using configurable skills (e.g., doctor, financial advisor, personal coach). Each skill shapes the AI's behavior as a domain expert. +- **Document Management** — upload documents (records, reports, contracts, notes, etc.). AI extracts and indexes content for intelligent retrieval. +- **Proactive Notifications** — AI analyzes your stored data and schedules reminders (deadlines, follow-ups, recurring events) via in-app, email, or Telegram. +- **PDF Compilation** — request AI-generated summaries and reports as downloadable PDF documents. +- **Global Memory** — AI maintains a shared memory of critical information across all your chats. - **Multi-language** — English and Russian support. ## Tech Stack diff --git a/backend/app/api/v1/pdf.py b/backend/app/api/v1/pdf.py index 9a68164..40653d1 100644 --- a/backend/app/api/v1/pdf.py +++ b/backend/app/api/v1/pdf.py @@ -21,7 +21,7 @@ async def compile_pdf( user: Annotated[User, Depends(get_current_user)], db: Annotated[AsyncSession, Depends(get_db)], ): - pdf = await pdf_service.generate_health_pdf( + pdf = await pdf_service.generate_pdf_report( db, user.id, data.title, data.document_ids or None, data.chat_id, ) return PdfResponse.model_validate(pdf) diff --git a/backend/app/main.py b/backend/app/main.py index 67f6b12..10550fb 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -32,7 +32,7 @@ async def lifespan(app: FastAPI): def create_app() -> FastAPI: app = FastAPI( title="AI Assistant API", - description="Personal AI health assistant with document management, chat, and notifications.", + description="Personal AI assistant with document management, chat, memory, and notifications.", version="0.1.0", lifespan=lifespan, docs_url="/api/docs" if settings.DOCS_ENABLED else None, @@ -41,8 +41,8 @@ def create_app() -> FastAPI: openapi_tags=[ {"name": "auth", "description": "Authentication and registration"}, {"name": "chats", "description": "AI chat conversations"}, - {"name": "documents", "description": "Health document management"}, - {"name": "memory", "description": "Health memory entries"}, + {"name": "documents", "description": "Document management"}, + {"name": "memory", "description": "Memory entries"}, {"name": "skills", "description": "AI specialist skills"}, {"name": "notifications", "description": "User notifications"}, {"name": "pdf", "description": "PDF report generation"}, diff --git a/backend/app/schemas/memory.py b/backend/app/schemas/memory.py index 8df5612..4fea985 100644 --- a/backend/app/schemas/memory.py +++ b/backend/app/schemas/memory.py @@ -4,7 +4,7 @@ from typing import Literal from pydantic import BaseModel, Field -CategoryType = Literal["condition", "medication", "allergy", "vital", "document_summary", "other"] +CategoryType = Literal["health", "finance", "personal", "work", "document_summary", "other"] ImportanceType = Literal["critical", "high", "medium", "low"] diff --git a/backend/app/services/ai_service.py b/backend/app/services/ai_service.py index 7d45cdd..bcb7659 100644 --- a/backend/app/services/ai_service.py +++ b/backend/app/services/ai_service.py @@ -24,13 +24,13 @@ client = AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY) AI_TOOLS = [ { "name": "save_memory", - "description": "Save important health information to the user's memory. Use this when the user shares critical health data like conditions, medications, allergies, or important health facts.", + "description": "Save important information to the user's memory. Use this when the user shares critical personal data, facts, preferences, or key details they want to remember across conversations.", "input_schema": { "type": "object", "properties": { "category": { "type": "string", - "enum": ["condition", "medication", "allergy", "vital", "document_summary", "other"], + "enum": ["health", "finance", "personal", "work", "document_summary", "other"], "description": "Category of the memory entry", }, "title": {"type": "string", "description": "Short title for the memory entry"}, @@ -46,7 +46,7 @@ AI_TOOLS = [ }, { "name": "search_documents", - "description": "Search the user's uploaded health documents for relevant information. Use this when you need to find specific health records, lab results, or consultation notes.", + "description": "Search the user's uploaded documents for relevant information. Use this when you need to find specific records, files, or notes the user has uploaded.", "input_schema": { "type": "object", "properties": { @@ -57,13 +57,13 @@ AI_TOOLS = [ }, { "name": "get_memory", - "description": "Retrieve the user's stored health memories filtered by category. Use this to recall previously saved health information.", + "description": "Retrieve the user's stored memories filtered by category. Use this to recall previously saved information.", "input_schema": { "type": "object", "properties": { "category": { "type": "string", - "enum": ["condition", "medication", "allergy", "vital", "document_summary", "other"], + "enum": ["health", "finance", "personal", "work", "document_summary", "other"], "description": "Optional category filter. Omit to get all memories.", }, }, @@ -94,7 +94,7 @@ AI_TOOLS = [ }, { "name": "generate_pdf", - "description": "Generate a PDF health report compilation from the user's data. Use this when the user asks for a document or summary of their health information.", + "description": "Generate a PDF report compilation from the user's data. Use this when the user asks for a document or summary of their stored information.", "input_schema": { "type": "object", "properties": { @@ -173,8 +173,8 @@ async def _execute_tool( }) elif tool_name == "generate_pdf": - from app.services.pdf_service import generate_health_pdf - pdf = await generate_health_pdf(db, user_id, title=tool_input["title"]) + from app.services.pdf_service import generate_pdf_report + pdf = await generate_pdf_report(db, user_id, title=tool_input["title"]) await db.commit() return json.dumps({ "status": "generated", @@ -213,7 +213,7 @@ async def assemble_context( memories = await get_critical_memories(db, user_id) if memories: memory_lines = [f"- [{m.category}] {m.title}: {m.content}" for m in memories] - system_parts.append(f"---\nUser Health Profile:\n" + "\n".join(memory_lines)) + system_parts.append(f"---\nUser Profile (Key Information):\n" + "\n".join(memory_lines)) # 5. Relevant document excerpts (based on user message keywords) if user_message.strip(): diff --git a/backend/app/services/context_service.py b/backend/app/services/context_service.py index e052370..bdd3f5d 100644 --- a/backend/app/services/context_service.py +++ b/backend/app/services/context_service.py @@ -5,14 +5,14 @@ from sqlalchemy.ext.asyncio import AsyncSession from app.models.context_file import ContextFile -DEFAULT_SYSTEM_PROMPT = """You are a personal AI health assistant. Your role is to: -- Help users understand their health data and medical documents -- Provide health-related recommendations based on uploaded information -- Schedule reminders for checkups, medications, and health-related activities -- Compile health summaries when requested -- Answer health questions clearly and compassionately +DEFAULT_SYSTEM_PROMPT = """You are a personal AI assistant helping users manage different areas of their life. Your role is to: +- Help users organize and understand their uploaded documents and data +- Provide recommendations and insights based on stored information +- Schedule reminders for important events, deadlines, and recurring activities +- Compile summaries and reports when requested +- Answer questions clearly and helpfully -Always be empathetic, accurate, and clear. When uncertain, recommend consulting a healthcare professional. +Always be empathetic, accurate, and clear. When uncertain about specialized topics, recommend consulting a relevant professional. You can communicate in English and Russian based on the user's preference.""" diff --git a/backend/app/services/pdf_service.py b/backend/app/services/pdf_service.py index e85d815..8b96176 100644 --- a/backend/app/services/pdf_service.py +++ b/backend/app/services/pdf_service.py @@ -19,7 +19,7 @@ jinja_env = Environment( ) -async def generate_health_pdf( +async def generate_pdf_report( db: AsyncSession, user_id: uuid.UUID, title: str, @@ -53,7 +53,7 @@ async def generate_health_pdf( }) # Render HTML - template = jinja_env.get_template("health_report.html") + template = jinja_env.get_template("report.html") html = template.render( title=title, user_name=user.full_name or user.username, diff --git a/backend/app/services/scheduler_service.py b/backend/app/services/scheduler_service.py index 5b0c9e4..ee04a5a 100644 --- a/backend/app/services/scheduler_service.py +++ b/backend/app/services/scheduler_service.py @@ -16,11 +16,11 @@ def start_scheduler(): replace_existing=True, ) - from app.workers.health_review import run_daily_health_review + from app.workers.health_review import run_daily_review scheduler.add_job( - run_daily_health_review, + run_daily_review, trigger=CronTrigger(hour=8, minute=0), - id="daily_health_review", + id="daily_review", replace_existing=True, ) diff --git a/backend/app/templates/pdf/health_report.html b/backend/app/templates/pdf/report.html similarity index 97% rename from backend/app/templates/pdf/health_report.html rename to backend/app/templates/pdf/report.html index b307288..2efe70a 100644 --- a/backend/app/templates/pdf/health_report.html +++ b/backend/app/templates/pdf/report.html @@ -26,13 +26,13 @@

{{ title }}

-

Patient: {{ user_name }}

+

Prepared for: {{ user_name }}

Generated: {{ generated_at }}

{% if memories %}
-

Health Profile

+

Key Information

{% for m in memories %}
{{ m.category }} diff --git a/backend/app/workers/health_review.py b/backend/app/workers/health_review.py index b8cbb3a..34a2a1f 100644 --- a/backend/app/workers/health_review.py +++ b/backend/app/workers/health_review.py @@ -1,4 +1,4 @@ -"""Daily job: proactive health review for all users with health data.""" +"""Daily job: proactive review for all users with stored data.""" import asyncio import json import logging @@ -17,8 +17,8 @@ logger = logging.getLogger(__name__) client = AsyncAnthropic(api_key=settings.ANTHROPIC_API_KEY) -async def run_daily_health_review(): - """Review each user's health profile and generate reminder notifications.""" +async def run_daily_review(): + """Review each user's profile and generate reminder notifications.""" if not settings.ANTHROPIC_API_KEY: return @@ -31,7 +31,7 @@ async def run_daily_health_review(): await _review_user(user) await asyncio.sleep(1) # Rate limit except Exception: - logger.exception(f"Health review failed for user {user.id}") + logger.exception(f"Daily review failed for user {user.id}") async def _review_user(user: User): @@ -45,13 +45,12 @@ async def _review_user(user: User): response = await client.messages.create( model=settings.CLAUDE_MODEL, max_tokens=500, - system="You are a health assistant. Based on the user's health profile, suggest any upcoming checkups, medication reviews, or health actions that should be reminded. Respond with a JSON array of objects with 'title' and 'body' fields. If no reminders are needed, return an empty array [].", - messages=[{"role": "user", "content": f"User health profile:\n{memory_text}"}], + system="You are a personal assistant. Based on the user's stored information, suggest any upcoming actions, deadlines, follow-ups, or reminders that would be helpful. Respond with a JSON array of objects with 'title' and 'body' fields. If no reminders are needed, return an empty array [].", + messages=[{"role": "user", "content": f"User profile data:\n{memory_text}"}], ) try: text = response.content[0].text.strip() - # Extract JSON from response if "[" in text: json_str = text[text.index("["):text.rindex("]") + 1] reminders = json.loads(json_str) @@ -60,7 +59,7 @@ async def _review_user(user: User): except (json.JSONDecodeError, ValueError): return - for reminder in reminders[:5]: # Max 5 reminders per user per day + for reminder in reminders[:5]: if "title" in reminder and "body" in reminder: notif = await create_notification( db, diff --git a/frontend/public/locales/en/translation.json b/frontend/public/locales/en/translation.json index d3cffbf..5ed6e56 100644 --- a/frontend/public/locales/en/translation.json +++ b/frontend/public/locales/en/translation.json @@ -42,7 +42,7 @@ }, "dashboard": { "welcome": "Welcome, {{name}}", - "subtitle": "Your personal AI health assistant" + "subtitle": "Your personal AI assistant" }, "chat": { "new_chat": "New Chat", @@ -116,10 +116,10 @@ "clear_search": "Clear", "types": { "other": "Other", - "lab_result": "Lab Result", - "consultation": "Consultation", - "prescription": "Prescription", - "imaging": "Imaging" + "report": "Report", + "contract": "Contract", + "receipt": "Receipt", + "certificate": "Certificate" }, "status": { "pending": "Pending", @@ -131,7 +131,7 @@ "memory": { "create": "Add Memory Entry", "edit": "Edit Memory Entry", - "no_entries": "No memory entries yet. The AI will save important health information here.", + "no_entries": "No memory entries yet. The AI will save important information here.", "category": "Category", "importance": "Importance", "title_field": "Title", @@ -139,10 +139,10 @@ "content_field": "Content", "content_placeholder": "Detailed information...", "categories": { - "condition": "Condition", - "medication": "Medication", - "allergy": "Allergy", - "vital": "Vital Sign", + "health": "Health", + "finance": "Finance", + "personal": "Personal", + "work": "Work", "document_summary": "Document Summary", "other": "Other" }, diff --git a/frontend/public/locales/ru/translation.json b/frontend/public/locales/ru/translation.json index 26423c2..ba3382c 100644 --- a/frontend/public/locales/ru/translation.json +++ b/frontend/public/locales/ru/translation.json @@ -42,7 +42,7 @@ }, "dashboard": { "welcome": "Добро пожаловать, {{name}}", - "subtitle": "Ваш персональный ИИ-ассистент по здоровью" + "subtitle": "Ваш персональный ИИ-ассистент" }, "chat": { "new_chat": "Новый чат", @@ -116,10 +116,10 @@ "clear_search": "Очистить", "types": { "other": "Другое", - "lab_result": "Анализы", - "consultation": "Консультация", - "prescription": "Рецепт", - "imaging": "Снимки" + "report": "Отчёт", + "contract": "Договор", + "receipt": "Квитанция", + "certificate": "Сертификат" }, "status": { "pending": "Ожидание", @@ -131,7 +131,7 @@ "memory": { "create": "Добавить запись", "edit": "Редактировать запись", - "no_entries": "Записей пока нет. ИИ будет сохранять важную информацию о здоровье здесь.", + "no_entries": "Записей пока нет. ИИ будет сохранять важную информацию здесь.", "category": "Категория", "importance": "Важность", "title_field": "Заголовок", @@ -139,10 +139,10 @@ "content_field": "Содержание", "content_placeholder": "Подробная информация...", "categories": { - "condition": "Заболевание", - "medication": "Лекарство", - "allergy": "Аллергия", - "vital": "Показатели", + "health": "Здоровье", + "finance": "Финансы", + "personal": "Личное", + "work": "Работа", "document_summary": "Сводка документа", "other": "Другое" }, diff --git a/frontend/src/components/documents/upload-dialog.tsx b/frontend/src/components/documents/upload-dialog.tsx index eca195f..a390c3e 100644 --- a/frontend/src/components/documents/upload-dialog.tsx +++ b/frontend/src/components/documents/upload-dialog.tsx @@ -9,7 +9,7 @@ interface UploadDialogProps { onClose: () => void; } -const DOC_TYPES = ["other", "lab_result", "consultation", "prescription", "imaging"]; +const DOC_TYPES = ["other", "report", "contract", "receipt", "certificate"]; export function UploadDialog({ open, onClose }: UploadDialogProps) { const { t } = useTranslation(); diff --git a/frontend/src/components/memory/memory-editor.tsx b/frontend/src/components/memory/memory-editor.tsx index 80ec17b..0ff5460 100644 --- a/frontend/src/components/memory/memory-editor.tsx +++ b/frontend/src/components/memory/memory-editor.tsx @@ -2,7 +2,7 @@ import { useState, useEffect } from "react"; import { useTranslation } from "react-i18next"; import type { MemoryEntry } from "@/api/memory"; -const CATEGORIES = ["condition", "medication", "allergy", "vital", "document_summary", "other"]; +const CATEGORIES = ["health", "finance", "personal", "work", "document_summary", "other"]; const IMPORTANCE_LEVELS = ["critical", "high", "medium", "low"]; interface MemoryEditorProps {