-- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -- Longevix Database Schema, Supabase PostgreSQL -- ืืคืขื ืงืื ืื ื-SQL Editor ืฉื Supabase -- ืื ืืืื ืืืื ืช ื-Row Level Security (RLS) -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -- ืืคืขื ืืจืืืืช ื ืืจืฉืืช CREATE EXTENSION IF NOT EXISTS pgcrypto; -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -- ืืืื 1: profiles, ืคืจืื ืืฉืชืืฉ ืืกืืกืืื -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ CREATE TABLE profiles ( id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, full_name TEXT, -- ืฉื ืืื age INTEGER, -- ืืื gender TEXT CHECK (gender IN ('male','female','other')), weight_kg DECIMAL(5,1), -- ืืฉืงื ืืง"ื height_cm INTEGER, -- ืืืื ืืก"ื vitality_score INTEGER DEFAULT 0, -- ืฆืืื Longevity (0-100) active_protocol TEXT, -- ืฉื ืืคืจืืืืงืื ืืคืขืื stripe_customer_id TEXT, -- ืืืื ืืงืื ื-Stripe subscription_status TEXT DEFAULT 'free', -- ืกืืืืก ืื ืื: free/active/cancelled gdpr_consent BOOLEAN DEFAULT FALSE, -- ืืืฉืืจ GDPR, ืืืื! created_at TIMESTAMPTZ DEFAULT NOW() ); -- ืืื ืช RLS, ืืืฉืชืืฉ ืจืืื ืจืง ืืช ืขืฆืื ALTER TABLE profiles ENABLE ROW LEVEL SECURITY; CREATE POLICY "users_own_profile" ON profiles FOR ALL USING (auth.uid() = id); -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -- ืืืื 2: biomarker_logs, ืืืืงืืช ืื -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ CREATE TABLE biomarker_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, marker_name TEXT NOT NULL, -- ืฉื ืืืื (ืืืืืื D, Ferritin...) value DECIMAL(10,3) NOT NULL, -- ืืขืจื ืืืืื unit TEXT NOT NULL, -- ืืืืืืช (ng/ml, %...) normal_min DECIMAL(10,3), -- ืขืจื ืืื ืืืื ืชืงืื normal_max DECIMAL(10,3), -- ืขืจื ืืงืกืืืื ืชืงืื status TEXT GENERATED ALWAYS AS ( -- ืืืืฉื ืืืืืืืืช! CASE WHEN value < normal_min THEN 'low' WHEN value > normal_max THEN 'high' ELSE 'normal' END ) STORED, is_critical BOOLEAN DEFAULT FALSE, -- ืืื ืืืื, ืขืจื ืืกืืื test_date DATE DEFAULT CURRENT_DATE, -- ืชืืจืื ืืืืืงื created_at TIMESTAMPTZ DEFAULT NOW() ); ALTER TABLE biomarker_logs ENABLE ROW LEVEL SECURITY; CREATE POLICY "users_own_biomarkers" ON biomarker_logs FOR ALL USING (auth.uid() = user_id); -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -- ืืืื 3: quiz_responses, ืชืฉืืืืช ืฉืืืื -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ CREATE TABLE quiz_responses ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, answers JSONB NOT NULL, -- ืื ืืชืฉืืืืช ื-JSON primary_goal TEXT, -- ืืืจื ืขืืงืจืืช energy_level INTEGER CHECK (energy_level BETWEEN 1 AND 10), sleep_quality INTEGER CHECK (sleep_quality BETWEEN 1 AND 10), stress_level INTEGER CHECK (stress_level BETWEEN 1 AND 10), focus_level INTEGER CHECK (focus_level BETWEEN 1 AND 10), vitality_score INTEGER, -- ืฆืืื ืฉืืืฉื ืืืืจ ืฉืืืื version INTEGER DEFAULT 1, -- ืืจืกืช ืืฉืืืื created_at TIMESTAMPTZ DEFAULT NOW() ); ALTER TABLE quiz_responses ENABLE ROW LEVEL SECURITY; CREATE POLICY "users_own_quiz" ON quiz_responses FOR ALL USING (auth.uid() = user_id); -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -- ืืืื 4: evidence_library, ืืืืจ ืืืงืจืื -- ืืืื ืฆืืืืจืืช, ืืืืช ืืืกืืคื ืืื ืืืงืจืื ืืืฉืื -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ CREATE TABLE evidence_library ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), ingredient_name TEXT NOT NULL UNIQUE, -- ืฉื ืืจืืื (Magnesium Glycinate) name_hebrew TEXT, -- ืฉื ืืขืืจืืช indication TEXT[], -- ืืื ืืืงืฆืืืช [sleep, stress, energy] mechanism_he TEXT, -- ืืกืืจ ืื ืื ืื ืืขืืจืืช pubmed_id TEXT, -- ืืกืคืจ PMID evidence_url TEXT, -- ืงืืฉืืจ ื-PubMed evidence_grade TEXT CHECK (evidence_grade IN ('A','B','C','D')), dose_default INTEGER, -- ืืื ืื ืืจืืจืช ืืืื (mg) dose_max INTEGER, -- ืืื ืื ืืงืกืืืื ืืืื timing TEXT, -- ืืื ื ืืืื: morning/afternoon/evening galit_approved BOOLEAN DEFAULT FALSE, -- ืืืฉืจ ืข"ื ืืืืช ืคืจืฅ active BOOLEAN DEFAULT TRUE, -- ืืื ืืจืืื ืคืขืื ืืคืืืคืืจืื updated_at TIMESTAMPTZ DEFAULT NOW() ); -- ืืืื ืืืืืื ืืงืจืื, ืจืง admins ืืืืืื ืืืชืื ALTER TABLE evidence_library ENABLE ROW LEVEL SECURITY; CREATE POLICY "public_read_evidence" ON evidence_library FOR SELECT USING (true); -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -- ืืืื 5: daily_logs, ืฆ'ืง-ืืื ืืืื -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ CREATE TABLE daily_logs ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE, log_date DATE DEFAULT CURRENT_DATE, energy INTEGER CHECK (energy BETWEEN 1 AND 5), -- 1-5 sleep_quality INTEGER CHECK (sleep_quality BETWEEN 1 AND 5), -- 1-5 focus INTEGER CHECK (focus BETWEEN 1 AND 5), -- 1-5 mood INTEGER CHECK (mood BETWEEN 1 AND 5), -- 1-5 protocol_adherence INTEGER CHECK (protocol_adherence BETWEEN 0 AND 100), -- % ืขืืืื supplement_ratings JSONB, -- ืืืจืื ืื ืชืืกืฃ {mag: 4, coq10: 5} notes TEXT, -- ืืขืจืืช ืืืคืฉืืืช vitality_score INTEGER, -- ืฆืืื ืืืื ืืืืฉื UNIQUE(user_id, log_date) -- ืจืง ืจืฉืืื ืืืช ืืืื ); ALTER TABLE daily_logs ENABLE ROW LEVEL SECURITY; CREATE POLICY "users_own_logs" ON daily_logs FOR ALL USING (auth.uid() = user_id); -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ -- ืืื ืืงืกืื ืืืืฆืืขืื ืืืืื -- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ CREATE INDEX idx_biomarker_user_date ON biomarker_logs(user_id, test_date); CREATE INDEX idx_daily_user_date ON daily_logs(user_id, log_date); CREATE INDEX idx_evidence_indication ON evidence_library USING GIN(indication);
/** * โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ * Longevix Evidence Engine, ืืื ืฉื ืืคืืืคืืจืื * ืืืืจ: ื ืชืื ื ืฉืืืื + ืืืืงืืช ืื โ ืคืจืืืืงืื ืืืืกืก ืืืงืจ * * ืฉืืืืฉ: * const protocol = await calculateProtocol(quizData, biomarkers); * console.log(protocol.recommendations); * โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ */ // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // ืืืืจ ืืจืืืืช ืืืืขืืืช, ืื ืชืืกืฃ ืขื ืืืืื ืงืืื ืืช // ืื ืืืืข ื-Supabase ืืคืจืืืงืฉื; ืืื ืืืืืื ืืืื // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ const EVIDENCE_LIBRARY = { "magnesium_glycinate": { name: "Magnesium Glycinate", nameHe: "ืืื ืืืื ืืืืฆืื ืื", // ืืื ืืืงืฆืืืช: ืืชื ืืืืืฆืื ืขื ืชืืกืฃ ืื indications: ["sleep", "stress", "anxiety", "muscle_recovery"], // ืกืคื ืืคืขืื: ืื ืฆืจืื ืืืืืช ื ืืื/ืืืื ืืื ืืืืืืฅ thresholds: { sleep: <= 5, stress: >= 6 }, // ืืกืืจ ืืื ืื ืื ืืขืืจืืช, ืืืคืืข ืืืฉืชืืฉ mechanismHe: "ืืื ืืืื ืืคืขืื ืงืืืื ื GABA, ืืืฉืืจ ืืขืฆืื ืืจืืฉื ืืืจืืขื. ืืคืืืช ืืื ืืืจืืืืช ื-17 ืืงืืช ืืืืืฆืข.", stat: "ืืคืืชืช ืืื ืืืจืืืืช ื-17 ืืงืืช", pmid: "33600070", pubmedUrl: "https://pubmed.ncbi.nlm.nih.gov/33600070", evidenceGrade: "A", dose: { default: 400, highStress: 500, max: 600 }, timing: "evening", timingHe: "ืขืจื, 30 ืืงืืช ืืคื ื ืฉืื ื" }, "omega3": { name: "Omega-3 EPA/DHA", nameHe: "ืืืืื-3", indications: ["energy", "focus", "inflammation", "cardiovascular", "longevity"], thresholds: { focus: <= 5, biomarker_omega3_index: < 8 }, mechanismHe: "EPA/DHA ืืื ืื ืคืืกืคืืืืคืืืื ืืงืจืื ืชื ืืืื ืืืฉืคืืขืื ืขื ืกืจืืืื ืื ืืืืคืืื.", stat: "ืืคืืชืช 25% ืกืืืื ืงืจืืื-ืืกืงืืืจื", pmid: "30415628", pubmedUrl: "https://pubmed.ncbi.nlm.nih.gov/30415628", evidenceGrade: "A", dose: { default: 2000, athlete: 3000, max: 4000 }, timing: "morning", timingHe: "ืืืงืจ, ืขื ืืจืืื ืฉืืื ืืช" }, "vitamin_d3": { name: "Vitamin D3 + K2", nameHe: "ืืืืืื D3 + K2", indications: ["energy", "immunity", "mood", "longevity"], thresholds: { biomarker_vitamin_d: < 30 }, // ng/ml mechanismHe: "D3 ืืืืกืช ืืืฆืืจ ืกืจืืืื ืื ืืืคืขืื 900+ ืื ืื. ืืกืจ ื-85% ืืืงืจื ืขืืืคืืช ืืจืื ืืช.", stat: "ืงืฉืจ ื-85% ืืืงืจื ืขืืืคืืช ืืจืื ืืช", pmid: "29343734", pubmedUrl: "https://pubmed.ncbi.nlm.nih.gov/29343734", evidenceGrade: "A", dose: { default: 2000, deficient: 4000, severe_deficient: 5000, max: 10000 }, timing: "morning", timingHe: "ืืืงืจ, ืขื ืืจืืืช ืฉืื ืื" }, "ashwagandha": { name: "Ashwagandha KSM-66", nameHe: "ืืฉืืืื ืื KSM-66", indications: ["stress", "anxiety", "energy", "sleep"], thresholds: { stress: >= 6 }, mechanismHe: "Withanolides ืืืืกืชืื ืฆืืจ HPA, ืืคืืืชืื ืงืืจืืืืื ื-27.9% ืชืื 8 ืฉืืืขืืช.", stat: "ืืคืืชืช ืงืืจืืืืื ื-27.9%", pmid: "23439798", pubmedUrl: "https://pubmed.ncbi.nlm.nih.gov/23439798", evidenceGrade: "A", dose: { default: 300, highStress: 600, max: 600 }, timing: "morning", timingHe: "ืืืงืจ, ืขื ืืจืืื" }, "coq10": { name: "CoQ10 Ubiquinol", nameHe: "ืงื-ืื ืืื Q10", indications: ["energy", "longevity", "mitochondria"], thresholds: { energy: <= 5, age: >= 35 }, mechanismHe: "ืืืื ื ืืฉืจืฉืจืช ืื ืฉืืื ืืืืืืืื ืืจืืืืืช. ืืืจื ื-49% ืขื ืืื 60.", stat: "ืืจืืื ืฉื 49% ืืืืฃ ืขื ืืื 60", pmid: "25625898", pubmedUrl: "https://pubmed.ncbi.nlm.nih.gov/25625898", evidenceGrade: "A", dose: { default: 200, over50: 300, over60: 400, max: 600 }, timing: "afternoon", timingHe: "ืืืจ ืืฆืืจืืื, ืขื ืืจืืื" } }; /** * calculateProtocol, ืืคืื ืงืฆืื ืืจืืฉืืช * * @param {Object} quizData - ืชืฉืืืืช ืืฉืืืื * { energy: 1-10, sleep: 1-10, stress: 1-10, focus: 1-10, * goal: string, age: number, activity: string } * @param {Array} biomarkers - ืืืืงืืช ืื * [{ marker_name: string, value: number, status: string }] * @returns {Object} - ืืืืฆืืช ืืืชืืืืช ืืืฉืืช */ async function calculateProtocol(quizData, biomarkers = []) { // ืฉืื 1: ืืฆืืจืช ืืคืช ืืืืงืืช ืื ืืืืคืืฉ ืืืืจ const biomarkerMap = {}; biomarkers.forEach(bm => { // ืืคืชื ืื ืืจืื: "Vitamin D" โ "vitamin_d" const key = bm.marker_name.toLowerCase().replace(/\s+/g, '_'); biomarkerMap[key] = bm.value; }); // ืฉืื 2: ืืคืขืืช ืืืืืจืืชื ืื ืืงืื ืืื ืชืืกืฃ const scored = []; for (const [id, ingredient] of Object.entries(EVIDENCE_LIBRARY)) { let score = 0; const triggers = []; // ืื ืืคืขืื ืืช ืืืืืฆื // ืืืืงื 1: ืืื ืืืืจื ืชืืืืช ืืืื ืืืงืฆืื if (ingredient.indications.includes(quizData.goal)) { score += 3; triggers.push({ type: 'goal', text: `ืชืืื ืืืืจืช "${quizData.goal}"` }); } // ืืืืงื 2: ืฆืืืื ืขื ืกืคื ืืฉืืืื for (const [metric, threshold] of Object.entries(ingredient.thresholds || {})) { if (metric.startsWith('biomarker_')) { // โโโ ืกืคื ืืืืงืืช ืื โโโ const bmKey = metric.replace('biomarker_', ''); const bmValue = biomarkerMap[bmKey]; if (bmValue !== undefined && bmValue < threshold) { score += 3; triggers.push({ type: 'biomarker', text: `${bmKey} ื ืืื: ${bmValue} (ืกืฃ: ${threshold})` }); } } else { // โโโ ืกืคื ืฉืืืื โโโ const val = quizData[metric]; if (val !== undefined) { const triggered = (metric === 'stress' || metric === 'age') ? val >= threshold : val <= threshold; if (triggered) { score += 2; triggers.push({ type: 'quiz', text: `${metric}: ${val}` }); } } } } // ืืื ืืืื ืฆืืื ืืืื ืกื ืืคืจืืืืงืื if (score >= 2) { scored.push({ id, ingredient, score, triggers }); } } // ืฉืื 3: ืืืื ืืคื ืฆืืื โ top 6 scored.sort((a, b) => b.score - a.score); const selected = scored.slice(0, 6); // ืฉืื 4: ืื ืืืช ืืืืืืงื ืืืืืฆืืช ืืกืืคื const recommendations = selected.map(({ id, ingredient, triggers }) => { // ืืืฉืื ืืื ืื ืืื ืื ืืคื ืืื ืืจืืช ืกืืจืก let dose = ingredient.dose.default; if (quizData.stress >= 7 && ingredient.dose.highStress) dose = ingredient.dose.highStress; if (quizData.age >= 60 && ingredient.dose.over60) dose = ingredient.dose.over60; if (quizData.age >= 50 && ingredient.dose.over50) dose = ingredient.dose.over50; // ืื ืืืช ืืกืืจ ืืืฉื ืืขืืจืืช const triggerTexts = triggers.map(t => { if (t.type === 'quiz') return `ืฆืืื ืช ${t.text}`; if (t.type === 'biomarker') return `ืืืืงืช ืืื ืืจืืชื ${t.text}`; return t.text; }).join(' ื-'); const personalJustification = `ืืืกืคื ื ${ingredient.nameHe} ืื ${triggerTexts}. ` + ingredient.mechanismHe + ` ืืืงืจืื ืืจืืื: ${ingredient.stat}.`; return { id, name: ingredient.name, nameHe: ingredient.nameHe, dose: `${dose}mg`, timing: ingredient.timing, timingHe: ingredient.timingHe, justificationHe: personalJustification, // โ ืื ืื ืฉืืืฆื ืืืฉืชืืฉ pmid: ingredient.pmid, pubmedUrl: ingredient.pubmedUrl, evidenceGrade: ingredient.evidenceGrade, triggers }; }); // ืฉืื 5: ืืืฉืื Vitality Score const vitalityScore = calculateVitalityScore(quizData, biomarkers); // ืืืืจืช ืืืืืืืงื ืืกืืคื return { recommendations, // ืืืืฆืืช ืขื ืืกืืจ ืืืฉื vitalityScore, // ืฆืืื 0-100 protocolName: getProtocolName(quizData.goal), generatedAt: new Date().toISOString(), totalIngredients: recommendations.length }; } // โโโ ืคืื ืงืฆืืืช ืขืืจ: ืืืฉืื Vitality Score โโโ function calculateVitalityScore(quiz, biomarkers) { let score = 55; if (quiz.energy >= 7) score += 8; else if (quiz.energy <= 3) score -= 6; if (quiz.sleep >= 7) score += 7; else if (quiz.sleep <= 3) score -= 8; if (quiz.stress <= 3) score += 8; else if (quiz.stress >= 8) score -= 10; if (quiz.focus >= 7) score += 6; if (quiz.activity === 'athlete') score += 8; else if (quiz.activity === 'sedentary') score -= 5; biomarkers.forEach(bm => { if (bm.status === 'low') score -= 4; if (bm.is_critical) score -= 10; }); return Math.min(Math.max(score, 20), 95); } function getProtocolName(goal) { const names = { energy: 'Energy & Focus Protocol', sleep: 'Sleep Recovery Protocol', stress: 'Calm & Resilience Protocol', longevity: 'Longevity Core Protocol', weight: 'Metabolic Balance Protocol' }; return names[goal] || 'Personal Health Protocol'; } // โโโ ืืืฆืื (ืืฉืืืืฉ ื-React/Next.js) โโโ export { calculateProtocol, calculateVitalityScore, EVIDENCE_LIBRARY };
/** * BiomarkerEntry.jsx, ืจืืื ืืื ืช ืืืืงืืช ืื * Mobile-First, ืืงืืืช ื ืืืจืืช, ืืืืืฆืื ืืืืืืืืช * ืืืืืจ ื-Supabase ืขื ืืฆืคื ื */ import { useState, useCallback } from 'react'; import { supabase } from '../lib/supabase'; // โโโ ืืืืจ ืืืืืื ืืจืคืืืืื ืขื ืืืืื ื ืืจืื โโโ const BIOMARKERS_DB = [ { name: 'Vitamin D', nameHe: 'ืืืืืื D', unit: 'ng/ml', min: 30, max: 100 }, { name: 'Ferritin', nameHe: 'ืคืจืืืื', unit: 'ng/ml', min: 30, max: 300 }, { name: 'Hemoglobin', nameHe: 'ืืืืืืืืื', unit: 'g/dL', min: 12, max: 17 }, { name: 'TSH', nameHe: 'TSH ืชืืจืืืื', unit: 'mU/L', min: 0.4, max: 4 }, { name: 'HbA1c', nameHe: 'ืกืืืจ ืืืฆืื', unit: '%', min: 0, max: 5.6 }, { name: 'Glucose', nameHe: 'ืืืืงืื', unit: 'mg/dL', min: 70, max: 99 }, { name: 'Cholesterol', nameHe: 'ืืืืกืืจืื', unit: 'mg/dL', min: 0, max: 200 }, { name: 'Testosterone', nameHe: 'ืืกืืืกืืจืื', unit: 'ng/dL', min: 350, max: 1000 }, { name: 'Omega-3 Index',nameHe: 'ืืื ืืงืก ืืืืื',unit: '%', min: 8, max: 12 }, { name: 'CRP', nameHe: 'CRP ืืืงืช', unit: 'mg/L', min: 0, max: 1 }, ]; // โโโ ืคืื ืงืฆืืืช ืขืืจ: ืงืืืขืช ืกืืืืก ืืขืจื โโโ const getStatus = (value, min, max) => { if (value < min * 0.7) return { label: 'ื ืืื ืืืื โ ๏ธ', color: '#ef4444', critical: true }; if (value < min) return { label: 'ื ืืื ืืื ืืจืื', color: '#f59e0b', critical: false }; if (value > max * 1.3) return { label: 'ืืืื ืืืื โ ๏ธ', color: '#ef4444', critical: true }; if (value > max) return { label: 'ืืืื ืืื ืืจืื', color: '#f59e0b', critical: false }; return { label: 'ืชืงืื โ', color: '#10b981', critical: false }; }; export default function BiomarkerEntry({ userId, onSaved }) { const [selectedMarker, setSelectedMarker] = useState(null); const [value, setValue] = useState(''); const [searchQuery, setSearchQuery] = useState(''); const [saving, setSaving] = useState(false); // โโโ AutoComplete: ืืืคืืฉ ืืืืื โโโ const filteredMarkers = BIOMARKERS_DB.filter(m => m.name.toLowerCase().includes(searchQuery.toLowerCase()) || m.nameHe.includes(searchQuery) ); // โโโ ืืืฉืื ืกืืืืก ืืขืจื ืื ืืืื โโโ const currentValue = parseFloat(value); const status = selectedMarker && !isNaN(currentValue) ? getStatus(currentValue, selectedMarker.min, selectedMarker.max) : null; // โโโ ืฉืืืจื ืืืฆืคื ืช ื-Supabase โโโ const handleSave = async () => { if (!selectedMarker || !value) return; setSaving(true); try { // Supabase ืืฆืคืื ืืืืืืืืช ืืชืขืืืจื (TLS) // ืืฆืคื ืช ืฉืืืช ืกืคืฆืืคืืื ืืจืืช DB, ืจืื platform.html const { error } = await supabase .from('biomarker_logs') .insert({ user_id: userId, marker_name: selectedMarker.name, value: currentValue, unit: selectedMarker.unit, normal_min: selectedMarker.min, normal_max: selectedMarker.max, is_critical: status?.critical || false, test_date: new Date().toISOString().split('T')[0] }); if (error) throw error; // ืื ืขืจื ืงืจืืื, ืฉืื ืืชืจืื ืืจืืคื (Safety Layer) if (status?.critical) { await triggerSafetyAlert(userId, selectedMarker.name, currentValue); } onSaved?.(); // callback ืืืืจื setValue(''); setSelectedMarker(null); } catch (err) { console.error('ืฉืืืื ืืฉืืืจื:', err); } finally { setSaving(false); } }; return ( <div className="biomarker-entry"> {/* โโโ ืืืคืืฉ ืืื โโโ */} <input value={searchQuery} onChange={e => setSearchQuery(e.target.value)} placeholder="ืืคืฉ ืืื: ืืืืืื D, ืืจืื..." inputMode="text" /> {/* โโโ Autocomplete โโโ */} {searchQuery && filteredMarkers.map(m => ( <div key={m.name} onClick={() => { setSelectedMarker(m); setSearchQuery(m.nameHe); }}> <span>{m.nameHe}</span><span>{m.min}-{m.max} {m.unit}</span> </div> ))} {/* โโโ ืงืื ื ืืืจื โโโ */} <input value={value} readOnly inputMode="numeric" placeholder="ืืื ืขืจื" /> {/* โโโ ืกืงืืื ืืืืืืืืช โโโ */} {status && ( <div style={{ color: status.color }}> {status.critical && <span>โ ๏ธ ืคื ื ืืจืืคื</span>} <span>{status.label}</span> </div> )} <button onClick={handleSave} disabled={saving || !selectedMarker || !value}> {saving ? 'ืฉืืืจ...' : '๐ ืฉืืืจ ืืืฆืคื'} </button> </div> ); }
/** * โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ * Longevix Safety Layer, ืฉืืืช ืืืืืืืช * โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ * * โ ๏ธ CRITICAL: ืงืื ืื ืืืจืื ืขื ืืืืืืช ืืืฉืชืืฉืื * ืื ืฉืื ืื ืืืื ืืขืืืจ ืืืืงื ืฉื ืืืืช ืคืจืฅ! * * ืืืืืืงื: * 1. ืกืืจืง ืื ืขืจืื ืืืืงืืช ืืื * 2. ืืฉืืื ืืขืจืื ืงืืฆืื ืืกืืื ืื * 3. ืขืืฆืจ ืคืจืืืืงืื / ืฉืืื ืืชืจืืืช ืืคื ืืืืจื */ // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // ืกืฃ ืืขืจืืื ืืงืจืืืืื, ืื ืขืจื ืืืืฅ ืืืืื = ืขืฆืืจื ืืืื // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ const CRITICAL_THRESHOLDS = { 'Glucose': { min: 54, max: 400, reason: 'ืืืคืืืืืงืืื / ืืืคืจืืืืงืืื ืงืืฆืื ืืช' }, 'Hemoglobin': { min: 7, max: 20, reason: 'ืื ืืื ืงืฉื / ืคืืืืฆืืชืืื' }, 'TSH': { min: 0.1, max: 10, reason: 'ืชืช/ืืชืจ-ืคืขืืืืช ืืืืืช ืชืจืืก ืงืืฆืื ืืช' }, 'Potassium': { min: 3.0, max: 6.0, reason: 'ืืคืจืขืช ืืืงืืจืืืืืื ืืกืื ืช ืืืื' }, 'Sodium': { min: 125, max: 155, reason: 'ืืืคื/ืืืคืจ ื ืืจืืื ืงืืฆืื ืืช' }, 'Creatinine': { min: 0, max: 4, reason: 'ืื-ืกืคืืงืช ืืืืืช' }, 'Calcium': { min: 7.5, max: 12, reason: 'ืืคืจืขืช ืกืืื ืงืืฆืื ืืช' }, 'Testosterone': { min: 50, max: 2000, reason: 'ืจืืืช ืงืืฆืื ืืืช, ืืืืงื ื ืืจืฉืช' }, }; // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ // ืขืจืื ืืืืจื (warning) ืคืจืืืืงืื ืืืคืง ืื ืขื ืืขืจื // โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ const WARNING_THRESHOLDS = { 'Vitamin D': { min: 20, max: 150, reason: 'ืืกืจ ืืืืืื D, ืืื ืื ืืืื ื ืืจืฉ' }, 'Ferritin': { min: 15, max: 400, reason: 'ืืืกืืจ ืืจืื' }, 'HbA1c': { min: 0, max: 6.5, reason: 'ืคืจื-ืกืืืจืช / ืกืืืจืช' }, 'Cholesterol': { min: 0, max: 240, reason: 'ืืืืกืืจืื ืืืื' }, 'CRP': { min: 0, max: 3, reason: 'ืืืงืช ืืืืืจืช' }, }; /** * scanBiomarkersForAlerts, ืืกืืจืง ืืจืืฉื * * @param {Array} biomarkers - ืืขืจื ืืืืงืืช ืื * @returns {Object} { canProceed, level, alerts, blockers } */ function scanBiomarkersForAlerts(biomarkers) { const criticalAlerts = []; const warnings = []; for (const bm of biomarkers) { // โโโ ืืืืงืช ืขืจืืื ืงืจืืืืื โโโ const critThreshold = CRITICAL_THRESHOLDS[bm.marker_name]; if (critThreshold) { if (bm.value < critThreshold.min || bm.value > critThreshold.max) { criticalAlerts.push({ marker: bm.marker_name, value: bm.value, threshold: critThreshold, reason: critThreshold.reason, // ืืืืขื ืฉืชืืฆื ืืืฉืชืืฉ userMessage: `ื ื ืืคื ืืช ืืจืืคื, ืขืจื ${bm.marker_name} (${bm.value}) ืืืจื ืืืืืื ืืืืื` }); } } // โโโ ืืืืงืช ืขืจืื ืืืืจื โโโ const warnThreshold = WARNING_THRESHOLDS[bm.marker_name]; if (warnThreshold) { if (bm.value < warnThreshold.min || bm.value > warnThreshold.max) { warnings.push({ marker: bm.marker_name, value: bm.value, reason: warnThreshold.reason, userMessage: `${bm.marker_name}: ${warnThreshold.reason} ืืืืืฅ ืืืชืืืขืฅ ืขื ืจืืคื` }); } } } return { canProceed: criticalAlerts.length === 0, // false = ืขืฆืืจ ืืื! level: criticalAlerts.length > 0 ? 'CRITICAL' : warnings.length > 0 ? 'WARNING' : 'SAFE', criticalAlerts, warnings }; } /** * generateSafeProtocol, ืืขืืคืช ืืืืื ื-calculateProtocol * โโโ ืชืืื ืืืฉืชืืฉ ืืื, ืื ื-calculateProtocol ืืฉืืจืืช! โโโ */ async function generateSafeProtocol(userId, quizData, biomarkers) { // ืฉืื 1: ืกืจืืงืช ืืืืืืช, ืืืื ืืคื ื ืื ืืืจ const safetyResult = scanBiomarkersForAlerts(biomarkers); // ืฉืื 2: ืขืจืืื ืงืจืืืืื, ืขืฆืืจ ืืื! if (!safetyResult.canProceed) { // ืฉืื ืืชืจืื ืืจืืคื ืืจื Supabase await notifyDoctor(userId, safetyResult.criticalAlerts); // ืืืืจ ืฉืืืื, ืื ืชืืฉืื ืืคืจืืืืงืื! return { success: false, blocked: true, level: 'CRITICAL', userMessage: `โ ๏ธ ืื ื ืืชื ืืืคืืง ืคืจืืืืงืื. ` + safetyResult.criticalAlerts.map(a => a.userMessage).join('. '), doctorAlerted: true }; } // ืฉืื 3: ืืฆืืจืช ืคืจืืืืงืื (ืจืง ืื ืขืืจ ืืืืืืช) const protocol = await calculateProtocol(quizData, biomarkers); // ืฉืื 4: ืืืกืคืช ืืืืจืืช ืืคืจืืืืงืื ืื ืืฉ if (safetyResult.warnings.length > 0) { protocol.warnings = safetyResult.warnings; protocol.disclaimer = "โ ๏ธ ืืืื ืขืจืืื ืืืืืืื, ืืืืืฅ ืืืชืืืขืฅ ืขื ืจืืคื ืืคื ื ืืชืืืช ืืคืจืืืืงืื"; } return { success: true, blocked: false, level: safetyResult.level, protocol }; } // โโโ ืฉืืืืช ืืชืจืื ืืจืืคื ืืจื Supabase โโโ async function notifyDoctor(userId, criticalAlerts) { await supabase.from('doctor_alerts').insert({ user_id: userId, alert_type: 'CRITICAL_BIOMARKER', alerts: criticalAlerts, created_at: new Date().toISOString(), resolved: false }); // ืฉืื ืื ืืืืืื/SMS ืืจืืคื ืืจื Supabase Edge Function } export { generateSafeProtocol, scanBiomarkersForAlerts, CRITICAL_THRESHOLDS };