import { getToken } from '../context/AuthContext' let _profileId = null export function setProfileId(id) { _profileId = id } const BASE = '/api' /** * FastAPI-Fehler: `detail` kann String, Objekt oder Validierungs-Array sein. */ export function formatFastApiDetail(detail, fallback = '') { if (detail == null || detail === '') { return fallback || 'Anfrage fehlgeschlagen' } if (typeof detail === 'string') { return detail } if (Array.isArray(detail)) { const parts = detail.map((e) => { if (typeof e === 'string') return e if (e && typeof e === 'object') { const loc = Array.isArray(e.loc) ? e.loc.filter((x) => x != null && x !== '').join('.') : '' const msg = e.msg || e.message || '' if (loc && msg) return `${loc}: ${msg}` return msg || loc || '' } return String(e) }).filter(Boolean) return parts.length ? parts.join(' · ') : fallback || 'Validierungsfehler' } if (typeof detail === 'object') { if (Array.isArray(detail.errors) && detail.errors.length > 0) { const parts = detail.errors .map((e) => { if (e && typeof e === 'object') return e.message || e.msg || '' return String(e) }) .filter(Boolean) if (parts.length) return parts.join(' · ') } if (typeof detail.msg === 'string') return detail.msg if (typeof detail.message === 'string') return detail.message } return fallback || 'Anfrage fehlgeschlagen' } function hdrs(extra={}) { const h = {...extra} if (_profileId) h['X-Profile-Id'] = _profileId const token = getToken() if (token) h['X-Auth-Token'] = token return h } async function req(path, opts={}) { const res = await fetch(BASE+path, {...opts, headers:hdrs(opts.headers||{})}) if (!res.ok) { const errText = await res.text() let parsed = null try { parsed = JSON.parse(errText) } catch { throw new Error(errText.trim() || `HTTP ${res.status}`) } throw new Error(formatFastApiDetail(parsed.detail, errText.trim() || `HTTP ${res.status}`)) } return res.json() } const json=(d)=>({method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(d)}) const jput=(d)=>({method:'PUT', headers:{'Content-Type':'application/json'},body:JSON.stringify(d)}) export const api = { // Profiles getActiveProfile: () => req('/profile'), listProfiles: () => req('/profiles'), createProfile: (d) => req('/profiles', json(d)), updateProfile: (id,d) => req(`/profiles/${id}`, jput(d)), deleteProfile: (id) => req(`/profiles/${id}`, {method:'DELETE'}), getProfile: () => req('/profile'), updateActiveProfile:(d)=> req('/profile', jput(d)), // App-Bereich: Dashboard-Lab (Layout JSON, Issue #65) + Widget-Katalog getAppWidgetsCatalog: () => req('/app/widgets/catalog'), getAppDashboardLayout: () => req('/app/dashboard-layout'), putAppDashboardLayout: (layout) => req('/app/dashboard-layout', jput(layout)), resetAppDashboardLayout: () => req('/app/dashboard-layout/reset', { method: 'POST' }), adminGetWidgetsCatalogFull: () => req('/admin/widgets/catalog-full'), adminGetDashboardProductDefault: () => req('/admin/dashboard-product-default'), adminPutDashboardProductDefault: (layout) => req('/admin/dashboard-product-default', jput(layout)), adminDeleteDashboardProductDefault: () => req('/admin/dashboard-product-default', { method: 'DELETE' }), adminGetWidgetFeatureAssignments: () => req('/admin/widget-feature-assignments'), adminPutWidgetFeatureAssignment: (widgetId, body) => req(`/admin/widget-feature-assignments/${encodeURIComponent(widgetId)}`, jput(body)), // Persönliche Referenzwerte (Profil, historisch) listReferenceValueTypes: () => req('/reference-value-types'), listReferenceValueMetaEnums: () => req('/reference-value-meta/enums'), listProfileReferenceValues: (typeKey) => req(`/profile-reference-values?type_key=${encodeURIComponent(typeKey)}`), listProfileReferenceValuesSummary: () => req('/profile-reference-values/summary'), createProfileReferenceValue: (d) => req('/profile-reference-values', json(d)), updateProfileReferenceValue: (id, d) => req(`/profile-reference-values/${id}`, jput(d)), deleteProfileReferenceValue: (id) => req(`/profile-reference-values/${id}`, { method: 'DELETE' }), // Admin: Referenzwert-Typen (Katalog) adminListReferenceValueTypes: () => req('/admin/reference-value-types'), adminCreateReferenceValueType: (d) => req('/admin/reference-value-types', json(d)), adminUpdateReferenceValueType: (id, d) => req(`/admin/reference-value-types/${id}`, jput(d)), adminDeleteReferenceValueType: (id) => req(`/admin/reference-value-types/${id}`, { method: 'DELETE' }), adminReorderReferenceValueTypes: (orderedIds) => req('/admin/reference-value-types/reorder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ ordered_ids: orderedIds }), }), // Weight listWeight: (l=365) => req(`/weight?limit=${l}`), upsertWeight: (date,weight,note='') => req('/weight',json({date,weight,note})), updateWeight: (id,date,weight,note='') => req(`/weight/${id}`,jput({date,weight,note})), deleteWeight: (id) => req(`/weight/${id}`,{method:'DELETE'}), weightStats: () => req('/weight/stats'), // Circumferences listCirc: (l=100) => req(`/circumferences?limit=${l}`), upsertCirc: (d) => req('/circumferences',json(d)), updateCirc: (id,d) => req(`/circumferences/${id}`,jput(d)), deleteCirc: (id) => req(`/circumferences/${id}`,{method:'DELETE'}), // Caliper listCaliper: (l=100) => req(`/caliper?limit=${l}`), upsertCaliper: (d) => req('/caliper',json(d)), updateCaliper: (id,d) => req(`/caliper/${id}`,jput(d)), deleteCaliper: (id) => req(`/caliper/${id}`,{method:'DELETE'}), // Activity /** @param {number} [limit=200] @param {number} [days] nur Einträge ab HEUTE−days (Kalendertage), backend-filtert */ listActivity: (limit=200, days)=> { const q = new URLSearchParams({ limit: String(limit) }) if (days != null && days !== '') q.set('days', String(days)) return req(`/activity?${q}`) }, createActivity: (d) => req('/activity',json(d)), updateActivity: (id,d) => req(`/activity/${id}`,jput(d)), deleteActivity: (id) => req(`/activity/${id}`,{method:'DELETE'}), activityStats: () => req('/activity/stats'), listUncategorizedActivities: () => req('/activity/uncategorized'), bulkCategorizeActivities: (d) => req('/activity/bulk-categorize', json(d)), importActivityCsv: async(file)=>{ const fd=new FormData();fd.append('file',file) const r=await fetch(`${BASE}/activity/import-csv`,{method:'POST',body:fd,headers:hdrs()}) const d=await r.json();if(!r.ok)throw new Error(formatFastApiDetail(d.detail, JSON.stringify(d)));return d }, // Photos uploadPhoto: (file,date='')=>{ const fd=new FormData();fd.append('file',file);fd.append('date',date) return fetch(`${BASE}/photos`,{method:'POST',body:fd,headers:hdrs()}).then(r=>r.json()) }, listPhotos: () => req('/photos'), photoUrl: (pid) => { const token = getToken() return `${BASE}/photos/${pid}${token ? `?token=${token}` : ''}` }, // Nutrition importCsv: async(file)=>{ const fd=new FormData();fd.append('file',file) const r=await fetch(`${BASE}/nutrition/import-csv`,{method:'POST',body:fd,headers:hdrs()}) const d=await r.json();if(!r.ok)throw new Error(formatFastApiDetail(d.detail, JSON.stringify(d)));return d }, listNutrition: (l=365) => req(`/nutrition?limit=${l}`), nutritionCorrelations: () => req('/nutrition/correlations'), nutritionWeekly: (w=16) => req(`/nutrition/weekly?weeks=${w}`), nutritionImportHistory: () => req('/nutrition/import-history'), getNutritionByDate: (date) => req(`/nutrition/by-date/${date}`), createNutrition: (date,kcal,protein,fat,carbs) => req(`/nutrition?date=${date}&kcal=${kcal}&protein_g=${protein}&fat_g=${fat}&carbs_g=${carbs}`,{method:'POST'}), updateNutrition: (id,kcal,protein,fat,carbs) => req(`/nutrition/${id}?kcal=${kcal}&protein_g=${protein}&fat_g=${fat}&carbs_g=${carbs}`,{method:'PUT'}), deleteNutrition: (id) => req(`/nutrition/${id}`,{method:'DELETE'}), // Stats & AI getStats: () => req('/stats'), insightTrend: () => req('/insights/trend',{method:'POST'}), listPrompts: () => req('/prompts'), runInsight: (slug) => req(`/insights/run/${slug}`,{method:'POST'}), insightPipeline: () => req('/insights/pipeline',{method:'POST'}), listInsights: () => req('/insights'), latestInsights: () => req('/insights/latest'), deleteInsight: (id) => req(`/insights/${id}`, {method:'DELETE'}), exportZip: async () => { const res = await fetch(`${BASE}/export/zip`, {headers: hdrs()}) if (!res.ok) throw new Error('Export failed') const blob = await res.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `mitai-export-${new Date().toISOString().split('T')[0]}.zip` document.body.appendChild(a) a.click() document.body.removeChild(a) window.URL.revokeObjectURL(url) }, exportJson: async () => { const res = await fetch(`${BASE}/export/json`, {headers: hdrs()}) if (!res.ok) throw new Error('Export failed') const blob = await res.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `mitai-export-${new Date().toISOString().split('T')[0]}.json` document.body.appendChild(a) a.click() document.body.removeChild(a) window.URL.revokeObjectURL(url) }, exportCsv: async () => { const res = await fetch(`${BASE}/export/csv`, {headers: hdrs()}) if (!res.ok) throw new Error('Export failed') const blob = await res.blob() const url = window.URL.createObjectURL(blob) const a = document.createElement('a') a.href = url a.download = `mitai-export-${new Date().toISOString().split('T')[0]}.csv` document.body.appendChild(a) a.click() document.body.removeChild(a) window.URL.revokeObjectURL(url) }, // Admin adminListProfiles: () => req('/admin/profiles'), adminCreateProfile: (d) => req('/admin/profiles',json(d)), adminDeleteProfile: (id) => req(`/admin/profiles/${id}`,{method:'DELETE'}), adminSetPermissions: (id,d) => req(`/admin/profiles/${id}/permissions`,jput(d)), changePin: (pin) => req('/auth/pin',json({pin})), register: (name,email,password) => req('/auth/register',json({name,email,password})), verifyEmail: (token) => req(`/auth/verify/${token}`), resendVerification: (email) => req('/auth/resend-verification',json({email})), // v9c Subscription System // User-facing getMySubscription: () => req('/subscription/me'), getMyUsage: () => req('/subscription/usage'), getMyLimits: () => req('/subscription/limits'), redeemCoupon: (code) => req('/coupons/redeem',json({code})), getFeatureUsage: () => req('/features/usage'), // Phase 3: Usage overview // Admin: Features listFeatures: () => req('/features'), createFeature: (d) => req('/features',json(d)), updateFeature: (id,d) => req(`/features/${id}`,jput(d)), deleteFeature: (id) => req(`/features/${id}`,{method:'DELETE'}), // Admin: Tiers listTiers: () => req('/tiers'), createTier: (d) => req('/tiers',json(d)), updateTier: (id,d) => req(`/tiers/${id}`,jput(d)), deleteTier: (id) => req(`/tiers/${id}`,{method:'DELETE'}), // Admin: Tier Limits (Matrix) getTierLimitsMatrix: () => req('/tier-limits'), updateTierLimit: (d) => req('/tier-limits',jput(d)), updateTierLimitsBatch:(updates) => req('/tier-limits/batch',jput({updates})), // Admin: User Restrictions listUserRestrictions: (pid) => req(`/user-restrictions${pid?'?profile_id='+pid:''}`), createUserRestriction:(d) => req('/user-restrictions',json(d)), updateUserRestriction:(id,d) => req(`/user-restrictions/${id}`,jput(d)), deleteUserRestriction:(id) => req(`/user-restrictions/${id}`,{method:'DELETE'}), // Admin: Coupons listCoupons: () => req('/coupons'), createCoupon: (d) => req('/coupons',json(d)), updateCoupon: (id,d) => req(`/coupons/${id}`,jput(d)), deleteCoupon: (id) => req(`/coupons/${id}`,{method:'DELETE'}), getCouponRedemptions: (id) => req(`/coupons/${id}/redemptions`), // Admin: Access Grants listAccessGrants: (pid,active)=> req(`/access-grants${pid?'?profile_id='+pid:''}${active?'&active_only=true':''}`), createAccessGrant: (d) => req('/access-grants',json(d)), updateAccessGrant: (id,d) => req(`/access-grants/${id}`,jput(d)), revokeAccessGrant: (id) => req(`/access-grants/${id}`,{method:'DELETE'}), // v9d: Training Types listTrainingTypes: () => req('/training-types'), // Grouped by category listTrainingTypesFlat:() => req('/training-types/flat'), // Flat list getTrainingCategories:() => req('/training-types/categories'), // Category metadata // Admin: Training Types (v9d Phase 1b) adminListTrainingTypes: () => req('/admin/training-types'), adminGetTrainingType: (id) => req(`/admin/training-types/${id}`), adminCreateTrainingType: (d) => req('/admin/training-types', json(d)), adminUpdateTrainingType: (id,d) => req(`/admin/training-types/${id}`, jput(d)), adminDeleteTrainingType: (id) => req(`/admin/training-types/${id}`, {method:'DELETE'}), getAbilitiesTaxonomy: () => req('/admin/training-types/taxonomy/abilities'), // Admin: Training Type Profiles (Phase 2 #15) getProfileStats: () => req('/admin/training-types/profiles/stats'), getProfileTemplates: () => req('/admin/training-types/profiles/templates'), getProfileTemplate: (key) => req(`/admin/training-types/profiles/templates/${key}`), applyProfileTemplate: (id,templateKey) => req(`/admin/training-types/${id}/profile/apply-template`, json({template_key: templateKey})), getTrainingParameters: () => req('/evaluation/parameters'), batchEvaluateActivities: () => req('/evaluation/batch', {method:'POST'}), // Admin: Activity Type Mappings (v9d Phase 1b - Learnable System) adminListActivityMappings: (profileId, globalOnly) => req(`/admin/activity-mappings${profileId?'?profile_id='+profileId:''}${globalOnly?'?global_only=true':''}`), adminGetActivityMapping: (id) => req(`/admin/activity-mappings/${id}`), adminCreateActivityMapping: (d) => req('/admin/activity-mappings', json(d)), adminUpdateActivityMapping: (id,d) => req(`/admin/activity-mappings/${id}`, jput(d)), adminDeleteActivityMapping: (id) => req(`/admin/activity-mappings/${id}`, {method:'DELETE'}), adminGetMappingCoverage: () => req('/admin/activity-mappings/stats/coverage'), // Admin: Training session metrics (EAV) & attribute profiles (Migration 054) adminListTrainingParameters: (includeInactive = false) => req(`/admin/training-parameters${includeInactive ? '?include_inactive=true' : ''}`), adminCreateTrainingParameter: (d) => req('/admin/training-parameters', json(d)), adminUpdateTrainingParameter: (id, d) => req(`/admin/training-parameters/${id}`, jput(d)), adminDeleteTrainingParameter: (id) => req(`/admin/training-parameters/${id}`, { method: 'DELETE' }), adminListTrainingCategoryParameters: (category = '') => req( `/admin/training-category-parameters${category ? `?category=${encodeURIComponent(category)}` : ''}`, ), adminAddTrainingCategoryParameter: (d) => req('/admin/training-category-parameters', json(d)), adminUpdateTrainingCategoryParameter: (id, d) => req(`/admin/training-category-parameters/${id}`, jput(d)), adminDeleteTrainingCategoryParameter: (id) => req(`/admin/training-category-parameters/${id}`, { method: 'DELETE' }), adminListTrainingTypeParameters: (trainingTypeId) => req(`/admin/training-type-parameters?training_type_id=${encodeURIComponent(trainingTypeId)}`), adminAddTrainingTypeParameter: (d) => req('/admin/training-type-parameters', json(d)), adminUpdateTrainingTypeParameter: (id, d) => req(`/admin/training-type-parameters/${id}`, jput(d)), adminDeleteTrainingTypeParameter: (id) => req(`/admin/training-type-parameters/${id}`, { method: 'DELETE' }), getActivitySession: (id) => req(`/activity/${encodeURIComponent(id)}`), putActivityMetrics: (id, body) => req(`/activity/${encodeURIComponent(id)}/metrics`, jput(body)), // Sleep Module (v9d Phase 2b) listSleep: (l=90) => req(`/sleep?limit=${l}`), getSleepByDate: (date) => req(`/sleep/by-date/${date}`), createSleep: (d) => req('/sleep', json(d)), updateSleep: (id,d) => req(`/sleep/${id}`, jput(d)), deleteSleep: (id) => req(`/sleep/${id}`, {method:'DELETE'}), getSleepStats: (days=7) => req(`/sleep/stats?days=${days}`), getSleepDebt: (days=14) => req(`/sleep/debt?days=${days}`), getSleepTrend: (days=30) => req(`/sleep/trend?days=${days}`), getSleepPhases: (days=30) => req(`/sleep/phases?days=${days}`), // Sleep Import (v9d Phase 2c) importAppleHealthSleep: (file) => { const fd = new FormData() fd.append('file', file) return req('/sleep/import/apple-health', {method:'POST', body:fd}) }, // Rest Days (v9d Phase 2a) listRestDays: (l=90) => req(`/rest-days?limit=${l}`), createRestDay: (d) => req('/rest-days', json(d)), getRestDay: (id) => req(`/rest-days/${id}`), updateRestDay: (id,d) => req(`/rest-days/${id}`, jput(d)), deleteRestDay: (id) => req(`/rest-days/${id}`, {method:'DELETE'}), getRestDaysStats: (weeks=4) => req(`/rest-days/stats?weeks=${weeks}`), validateActivity: (date, activityType) => req('/rest-days/validate-activity', json({date, activity_type: activityType})), // Vitals Baseline (v9d Phase 2d Refactored - once daily, morning) listBaseline: (l=90) => req(`/vitals/baseline?limit=${l}`), getBaselineByDate: (date) => req(`/vitals/baseline/by-date/${date}`), createBaseline: (d) => req('/vitals/baseline', json(d)), updateBaseline: (id,d) => req(`/vitals/baseline/${id}`, jput(d)), deleteBaseline: (id) => req(`/vitals/baseline/${id}`, {method:'DELETE'}), getBaselineStats: (days=30) => req(`/vitals/baseline/stats?days=${days}`), importBaselineAppleHealth: (file) => { const fd = new FormData() fd.append('file', file) return req('/vitals/baseline/import/apple-health', {method:'POST', body:fd}) }, // Blood Pressure (v9d Phase 2d Refactored - multiple daily, context-aware) listBloodPressure: (l=90) => req(`/blood-pressure?limit=${l}`), getBPByDate: (date) => req(`/blood-pressure/by-date/${date}`), createBloodPressure:(d) => req('/blood-pressure', json(d)), updateBloodPressure:(id,d) => req(`/blood-pressure/${id}`, jput(d)), deleteBloodPressure:(id) => req(`/blood-pressure/${id}`, {method:'DELETE'}), getBPStats: (days=30) => req(`/blood-pressure/stats?days=${days}`), importBPOmron: (file) => { const fd = new FormData() fd.append('file', file) return req('/blood-pressure/import/omron', {method:'POST', body:fd}) }, // AI Prompts Management (Issue #28) listAdminPrompts: () => req('/prompts'), getPrompt: (id) => req(`/prompts/${id}`), createPrompt: (d) => req('/prompts', json(d)), updatePrompt: (id,d) => req(`/prompts/${id}`, jput(d)), deletePrompt: (id) => req(`/prompts/${id}`, {method:'DELETE'}), duplicatePrompt: (id) => req(`/prompts/${id}/duplicate`, json({})), reorderPrompts: (order) => req('/prompts/reorder', jput(order)), previewPrompt: (tpl) => req('/prompts/preview', json({template:tpl})), generatePrompt: (d) => req('/prompts/generate', json(d)), optimizePrompt: (id) => req(`/prompts/${id}/optimize`, json({})), listPlaceholders: () => req('/prompts/placeholders'), resetPromptToDefault: (id) => req(`/prompts/${id}/reset-to-default`, json({})), // Pipeline Configs Management (Issue #28 Phase 2) listPipelineConfigs: () => req('/prompts/pipeline-configs'), createPipelineConfig: (d) => req('/prompts/pipeline-configs', json(d)), updatePipelineConfig: (id,d) => req(`/prompts/pipeline-configs/${id}`, jput(d)), deletePipelineConfig: (id) => req(`/prompts/pipeline-configs/${id}`, {method:'DELETE'}), setDefaultPipelineConfig: (id) => req(`/prompts/pipeline-configs/${id}/set-default`, json({})), // Pipeline Execution (Issue #28 Phase 2) executePipeline: (configId=null) => req('/insights/pipeline' + (configId ? `?config_id=${configId}` : ''), json({})), // Unified Prompt System (Issue #28 Phase 2) executeUnifiedPrompt: (slug, modules=null, timeframes=null, debug=false, save=false) => { const params = new URLSearchParams({ prompt_slug: slug }) if (debug) params.append('debug', 'true') if (save) params.append('save', 'true') const body = {} if (modules) body.modules = modules if (timeframes) body.timeframes = timeframes return req('/prompts/execute?' + params, json(body)) }, // NEW: SSE-based execution for long-running workflows executeUnifiedPromptStream: (slug, modules=null, timeframes=null, debug=false, save=false, onProgress=null) => { const params = new URLSearchParams({ prompt_slug: slug }) if (debug) params.append('debug', 'true') if (save) params.append('save', 'true') // TODO: Security improvement - use session cookie instead of token in URL // For now, send token as query param since EventSource doesn't support custom headers const token = localStorage.getItem('token') if (token) params.append('token', token) if (modules) { Object.entries(modules).forEach(([k, v]) => params.append(`modules[${k}]`, v)) } if (timeframes) { Object.entries(timeframes).forEach(([k, v]) => params.append(`timeframes[${k}]`, v)) } // Return a Promise that resolves with final result return new Promise((resolve, reject) => { const url = `${BASE_URL}/prompts/execute-stream?${params}` const eventSource = new EventSource(url) let finalResult = null eventSource.onmessage = (event) => { try { const data = JSON.parse(event.data) // Call progress callback if provided if (onProgress) { onProgress(data) } // Check for final result if (data.type === 'execution_complete') { // Transform SSE result to match regular execute format finalResult = { type: 'workflow', execution_id: data.execution_id, status: data.status, aggregated_result: data.aggregated_result, debug: { node_states: [] // TODO: collect from progress events if needed } } eventSource.close() resolve(finalResult) } else if (data.type === 'execution_failed') { eventSource.close() reject(new Error(data.error || 'Workflow execution failed')) } } catch (e) { console.error('Error parsing SSE event:', e) } } eventSource.onerror = (error) => { console.error('SSE connection error:', error) eventSource.close() reject(new Error('Connection to server lost')) } }) }, // Workflow Execution (Part 2: Frontend Execute Integration) executeWorkflow: (slug, variables=null, debug=true, save=false) => { const params = new URLSearchParams({ prompt_slug: slug }) if (debug) params.append('debug', 'true') if (save) params.append('save', 'true') const body = variables ? { variables } : {} return req('/prompts/execute?' + params, json(body)) }, createUnifiedPrompt: (d) => req('/prompts/unified', json(d)), updateUnifiedPrompt: (id,d) => req(`/prompts/unified/${id}`, jput(d)), // Batch Import/Export exportAllPrompts: () => req('/prompts/export-all'), importPrompts: (data, overwrite=false) => { const params = new URLSearchParams() if (overwrite) params.append('overwrite', 'true') return req(`/prompts/import?${params}`, json(data)) }, // Placeholder Export exportPlaceholderValues: () => req('/prompts/placeholders/export-values'), // v9e: Goals System (Strategic + Tactical) getGoalMode: () => req('/goals/mode'), updateGoalMode: (mode) => req('/goals/mode', jput({goal_mode: mode})), // Focus Areas (v2.0) getFocusAreas: () => req('/goals/focus-areas'), updateFocusAreas: (d) => req('/goals/focus-areas', jput(d)), listGoals: () => req('/goals/list'), listGoalsGrouped: () => req('/goals/grouped'), createGoal: (d) => req('/goals/create', json(d)), updateGoal: (id,d) => req(`/goals/${id}`, jput(d)), deleteGoal: (id) => req(`/goals/${id}`, {method:'DELETE'}), // Goal Progress (v2.1) listGoalProgress: (id) => req(`/goals/${id}/progress`), createGoalProgress: (id,d) => req(`/goals/${id}/progress`, json(d)), deleteGoalProgress: (gid,pid) => req(`/goals/${gid}/progress/${pid}`, {method:'DELETE'}), // Goal Type Definitions (Phase 1.5) listGoalTypeDefinitions: () => req('/goals/goal-types'), createGoalType: (d) => req('/goals/goal-types', json(d)), updateGoalType: (id,d) => req(`/goals/goal-types/${id}`, jput(d)), deleteGoalType: (id) => req(`/goals/goal-types/${id}`, {method:'DELETE'}), getSchemaInfo: () => req('/goals/schema-info'), // Training Phases listTrainingPhases: () => req('/goals/phases'), createTrainingPhase: (d) => req('/goals/phases', json(d)), updatePhaseStatus: (id,status) => req(`/goals/phases/${id}/status?status=${status}`, jput({})), // Fitness Tests listFitnessTests: () => req('/goals/tests'), createFitnessTest: (d) => req('/goals/tests', json(d)), // Focus Areas (v2.0) listFocusAreaDefinitions: (includeInactive=false) => req(`/focus-areas/definitions?include_inactive=${includeInactive}`), createFocusAreaDefinition: (d) => req('/focus-areas/definitions', json(d)), updateFocusAreaDefinition: (id,d) => req(`/focus-areas/definitions/${id}`, jput(d)), deleteFocusAreaDefinition: (id) => req(`/focus-areas/definitions/${id}`, {method:'DELETE'}), getUserFocusPreferences: () => req('/focus-areas/user-preferences'), updateUserFocusPreferences: (d) => req('/focus-areas/user-preferences', jput(d)), getFocusAreaStats: () => req('/focus-areas/stats'), listFocusAreaUsageTypes: () => req('/focus-areas/usage-types'), setFocusAreaUsageTypes: (id, usageTypeKeys) => req(`/focus-areas/definitions/${id}/usage-types`, jput({ usage_type_keys: usageTypeKeys })), // Chart Endpoints (Phase 0c - Phase 1: Nutrition + Recovery) // Nutrition Charts (E1-E5) getEnergyBalanceChart: (days=28) => req(`/charts/energy-balance?days=${days}`), getProteinAdequacyChart: (days=28) => req(`/charts/protein-adequacy?days=${days}`), getNutritionConsistencyChart: (days=28) => req(`/charts/nutrition-consistency?days=${days}`), getWeeklyMacroDistributionChart: (weeks=12) => req(`/charts/weekly-macro-distribution?weeks=${weeks}`), getNutritionAdherenceScore: (days=28) => req(`/charts/nutrition-adherence-score?days=${days}`), getEnergyAvailabilityWarning: (days=14) => req(`/charts/energy-availability-warning?days=${days}`), // Recovery Charts (R1-R5) getRecoveryScoreChart: (days=28) => req(`/charts/recovery-score?days=${days}`), getHrvRhrBaselineChart: (days=28) => req(`/charts/hrv-rhr-baseline?days=${days}`), getSleepDurationQualityChart: (days=28) => req(`/charts/sleep-duration-quality?days=${days}`), getSleepDebtChart: (days=28) => req(`/charts/sleep-debt?days=${days}`), getVitalSignsMatrixChart: (days=7) => req(`/charts/vital-signs-matrix?days=${days}`), // Placeholder Metadata Export (v1.0) exportPlaceholdersExtendedJson: () => req('/prompts/placeholders/export-values-extended'), // Universal CSV Import (Issue #21) getCsvModules: () => req('/csv/modules'), getCsvLimits: () => req('/csv/limits'), getCsvMappings: (module = null) => req(module ? `/csv/mappings?module=${encodeURIComponent(module)}` : '/csv/mappings'), copyCsvMapping: (mappingId, body = null) => req(`/csv/mappings/${mappingId}/copy`, body ? json(body) : { method: 'POST' }), /** Universal-CSV (Issue #21): Zielmodul steckt in der Vorlage; nur file + mapping_id */ /** Import-Diagnose: keine Datenbank-Schreibung, erste Zeilen + Mapping-Auflösung */ diagnoseUniversalCsv: async (file, mappingId, module = null) => { const fd = new FormData() fd.append('file', file) fd.append('mapping_id', String(mappingId)) if (module) fd.append('module', module) const res = await fetch(BASE + '/csv/import-diagnose', { method: 'POST', headers: hdrs(), body: fd }) if (!res.ok) { const errText = await res.text() let parsed = null try { parsed = JSON.parse(errText) } catch { /* ignore */ } throw new Error(formatFastApiDetail(parsed?.detail, errText.trim() || `HTTP ${res.status}`)) } return res.json() }, importUniversalCsv: async (file, mappingId) => { const fd = new FormData() fd.append('file', file) fd.append('mapping_id', String(mappingId)) const res = await fetch(BASE + '/csv/import', { method: 'POST', headers: hdrs(), body: fd }) if (!res.ok) { const errText = await res.text() let parsed = null try { parsed = JSON.parse(errText) } catch { /* ignore */ } throw new Error(formatFastApiDetail(parsed?.detail, errText.trim() || `HTTP ${res.status}`)) } return res.json() }, /** optional module = nur diese Vorlagen-Gruppe (Abwärtskompatibilität); ohne = globale Erkennung */ analyzeCsv: async (file, module = null, delimiter = null) => { const fd = new FormData() fd.append('file', file) if (module) fd.append('module', module) if (delimiter) fd.append('delimiter', delimiter) const res = await fetch(BASE + '/csv/analyze', { method: 'POST', headers: hdrs(), body: fd }) if (!res.ok) { const errText = await res.text() let parsed = null try { parsed = JSON.parse(errText) } catch { /* ignore */ } throw new Error(formatFastApiDetail(parsed?.detail, errText.trim() || `HTTP ${res.status}`)) } return res.json() }, adminListCsvTemplates: (module = null) => req(module ? `/admin/csv-templates?module=${encodeURIComponent(module)}` : '/admin/csv-templates'), /** CSV für Admin-Editor analysieren (Vorschlags-Mappings, wie Nutzer-Import) */ adminAnalyzeCsvTemplate: async (file, module, delimiter = null, seedTemplateId = null) => { const fd = new FormData() fd.append('file', file) fd.append('module', module) if (delimiter) fd.append('delimiter', delimiter) if (seedTemplateId != null && seedTemplateId !== '') fd.append('seed_template_id', String(seedTemplateId)) const res = await fetch(BASE + '/admin/csv-templates/analyze-upload', { method: 'POST', headers: hdrs(), body: fd }) const j = await res.json().catch(() => ({})) if (!res.ok) throw new Error(j.detail || res.statusText || 'Analyse fehlgeschlagen') return j }, adminGetCsvTemplate: (id) => req(`/admin/csv-templates/${id}`), /** Formatprüfung (ohne Speichern); body wie Create, import_row_processing optional */ adminValidateCsvTemplate: (d) => req('/admin/csv-templates/validate', json(d)), adminCreateCsvTemplate: (d) => req('/admin/csv-templates', json(d)), adminUpdateCsvTemplate: (id, d) => req(`/admin/csv-templates/${id}`, jput(d)), adminDeleteCsvTemplate: (id) => req(`/admin/csv-templates/${id}`, { method: 'DELETE' }), adminGetCsvImportLimits: () => req('/admin/csv-templates/import-limits'), adminPutCsvImportLimits: (d) => req('/admin/csv-templates/import-limits', jput(d)), }