- Implemented the symptom database as a full searchable reference area, parallel to terminology.

Added:
New medical_symptoms SQLite catalog with 242 seeded symptom records, 
categories, body systems, tracking notes, urgency-context notes, search 
terms, and MedlinePlus links: [app/bootstrap.php (line 
186)](/Users/tyemeclifford/Documents/GH/workout/app/bootstrap.php:186), 
[seed logic (line 
635)](/Users/tyemeclifford/Documents/GH/workout/app/bootstrap.php:635)
New searchable symptom directory: [public/symptoms.php (line 
1)](/Users/tyemeclifford/Documents/GH/workout/public/symptoms.php:1)
New dynamic per-symptom page, e.g. /symptom.php?id=24: 
[public/symptom.php (line 
1)](/Users/tyemeclifford/Documents/GH/workout/public/symptom.php:1)
Symptom helper/link lookup functions: [app/bootstrap.php (line 
1473)](/Users/tyemeclifford/Documents/GH/workout/app/bootstrap.php:1473)
medicalSymptoms added to the combined library search/API: 
[app/bootstrap.php (line 
2024)](/Users/tyemeclifford/Documents/GH/workout/app/bootstrap.php:2024)
Symptoms added to nav, homepage, and combined medical search: 
[site-header.php (line 
27)](/Users/tyemeclifford/Documents/GH/workout/public/partials/site-header.php:27), 
[index.php (line 
11)](/Users/tyemeclifford/Documents/GH/workout/public/index.php:11), 
[medical.php (line 
13)](/Users/tyemeclifford/Documents/GH/workout/public/medical.php:13)
This commit is contained in:
Ty Clifford
2026-06-30 16:19:09 -04:00
parent b8045eb068
commit 0d492451c7
14 changed files with 638 additions and 352 deletions
+292 -50
View File
@@ -183,6 +183,20 @@ CREATE TABLE IF NOT EXISTS medical_conditions (
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS medical_symptoms (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
category TEXT NOT NULL,
body_system TEXT DEFAULT '',
plain_language_description TEXT NOT NULL,
common_tracking TEXT DEFAULT '',
urgency_note TEXT DEFAULT '',
source_name TEXT NOT NULL,
source_url TEXT NOT NULL,
search_terms TEXT DEFAULT '',
created_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS medication_catalog (
id INTEGER PRIMARY KEY AUTOINCREMENT,
generic_name TEXT NOT NULL,
@@ -338,6 +352,8 @@ CREATE INDEX IF NOT EXISTS idx_allergy_records_user ON allergy_records(user_id,
CREATE INDEX IF NOT EXISTS idx_appointments_user_date ON appointments(user_id, appointment_on);
CREATE INDEX IF NOT EXISTS idx_immunization_records_user_date ON immunization_records(user_id, administered_on);
CREATE INDEX IF NOT EXISTS idx_medical_terms_term ON medical_terms(term);
CREATE INDEX IF NOT EXISTS idx_medical_symptoms_name ON medical_symptoms(name);
CREATE INDEX IF NOT EXISTS idx_medical_symptoms_category ON medical_symptoms(category);
CREATE INDEX IF NOT EXISTS idx_medication_catalog_generic ON medication_catalog(generic_name);
CREATE INDEX IF NOT EXISTS idx_medication_source_updates_created ON medication_source_updates(created_at);
SQL);
@@ -564,6 +580,8 @@ function seed_medical_library(PDO $pdo): void
}
}
seed_medical_symptom_catalog($pdo, $created);
$medicineCount = (int) $pdo->query('SELECT COUNT(*) FROM medication_catalog')->fetchColumn();
if ($medicineCount === 0) {
$medicines = [
@@ -614,6 +632,231 @@ function seed_medical_library(PDO $pdo): void
}
}
function seed_medical_symptom_catalog(PDO $pdo, string $created): void
{
$rows = symptom_catalog_rows();
$existing = (int) db_value($pdo, 'SELECT COUNT(*) FROM medical_symptoms');
if ($existing >= count($rows)) {
return;
}
$stmt = $pdo->prepare(
"INSERT INTO medical_symptoms
(name, category, body_system, plain_language_description, common_tracking, urgency_note,
source_name, source_url, search_terms, created_at)
SELECT ?, ?, ?, ?, ?, ?, ?, ?, ?, ?
WHERE NOT EXISTS (SELECT 1 FROM medical_symptoms WHERE lower(name) = lower(?))"
);
$startedTransaction = !$pdo->inTransaction();
if ($startedTransaction) {
$pdo->beginTransaction();
}
try {
foreach ($rows as $row) {
$stmt->execute([...$row, $created, $row[0]]);
}
if ($startedTransaction) {
$pdo->commit();
}
} catch (Throwable $exception) {
if ($startedTransaction && $pdo->inTransaction()) {
$pdo->rollBack();
}
throw $exception;
}
}
function symptom_catalog_rows(): array
{
$groups = [
[
'General / systemic',
'Whole body',
'{symptom} is a body-wide symptom or change that can be useful to track alongside temperature, hydration, sleep, medicines, and recent exposures.',
'Record onset, duration, severity, temperature if relevant, hydration, recent illness exposure, medicines taken, and what improves or worsens it.',
['Fever', 'Chills', 'Fatigue', 'Malaise', 'General weakness', 'Unintentional weight loss', 'Unintentional weight gain', 'Night sweats', 'Excessive sweating', 'Dehydration', 'Excessive thirst', 'Loss of appetite', 'Increased appetite', 'Heat intolerance', 'Cold intolerance', 'Swelling', 'Edema', 'Swollen lymph nodes', 'Body aches'],
],
[
'Pain',
'Pain / sensory',
'{symptom} is a pain or sensory complaint that is often tracked by location, intensity, timing, triggers, and response to treatment.',
'Record pain score, location, character, onset, duration, triggers, injuries, activity limits, medicines used, and associated symptoms.',
['Pain', 'Chronic pain', 'Severe pain', 'Abdominal pain', 'Chest pain', 'Chest pressure', 'Back pain', 'Neck pain', 'Joint pain', 'Muscle pain', 'Pelvic pain', 'Flank pain', 'Ear pain', 'Eye pain', 'Sore throat', 'Headache', 'Severe headache', 'Toothache', 'Facial pain', 'Sciatica', 'Cramps', 'Tenderness', 'Burning pain', 'Numbness', 'Tingling'],
],
[
'Respiratory',
'Lungs / airways',
'{symptom} is a breathing, airway, nose, throat, or lung-related symptom that may be tracked with oxygen saturation, triggers, and medication use when relevant.',
'Record breathing effort, oxygen saturation if available, cough character, sputum, fever, triggers, inhaler or medicine use, activity tolerance, and exposure history.',
['Cough', 'Dry cough', 'Productive cough', 'Shortness of breath', 'Trouble breathing', 'Wheezing', 'Chest tightness', 'Rapid breathing', 'Shallow breathing', 'Coughing up blood', 'Noisy breathing', 'Snoring', 'Sleep apnea symptoms', 'Nasal congestion', 'Runny nose', 'Sneezing', 'Postnasal drip', 'Sinus pressure', 'Hoarseness', 'Sputum changes'],
],
[
'Cardiovascular',
'Heart / circulation',
'{symptom} is a heart, blood vessel, or circulation-related symptom that may be useful to track with pulse, blood pressure, swelling, exertion, and medication timing.',
'Record pulse, blood pressure if available, activity at onset, duration, swelling location, shortness of breath, chest symptoms, medicines, and whether symptoms occur at rest or with exertion.',
['Palpitations', 'Fainting', 'Near-fainting', 'Dizziness', 'Lightheadedness', 'Leg swelling', 'Blue lips', 'Rapid heartbeat', 'Slow heartbeat', 'Orthopnea', 'Exercise intolerance', 'Cold hands and feet', 'Clammy skin'],
],
[
'Digestive',
'Gastrointestinal',
'{symptom} is a digestive or abdominal symptom that may be tracked with meals, hydration, bowel pattern, medicines, and associated pain.',
'Record meals, hydration, bowel frequency, stool appearance, vomiting episodes, abdominal location, fever, weight changes, medicines, and possible food or infection exposures.',
['Nausea', 'Vomiting', 'Vomiting blood', 'Diarrhea', 'Constipation', 'Heartburn', 'Indigestion', 'Gas', 'Bloating', 'Belching', 'Abdominal cramps', 'Blood in stool', 'Black stools', 'Rectal bleeding', 'Difficulty swallowing', 'Loss of bowel control', 'Bowel urgency', 'Mucus in stool', 'Jaundice', 'Hiccups', 'Early fullness', 'Loss of taste for food'],
],
[
'Neurologic',
'Brain / nerves',
'{symptom} is a nervous-system symptom that may be important to track with timing, location, mental status, movement changes, and related medicines or exposures.',
'Record onset time, duration, affected body areas, speech or vision changes, balance, weakness, headache, injury, seizure-like activity, medicines, and glucose if relevant.',
['Confusion', 'Memory problems', 'Seizure', 'Tremor', 'Migraine aura', 'Vision loss', 'Sudden vision loss', 'Double vision', 'Slurred speech', 'Difficulty speaking', 'Difficulty walking', 'Balance problems', 'Facial droop', 'Weakness on one side', 'Drowsiness', 'Vertigo', 'Loss of consciousness', 'Restless legs', 'Involuntary movements', 'Poor coordination', 'Light sensitivity', 'Sound sensitivity'],
],
[
'Skin / hair / nails',
'Skin',
'{symptom} is a skin, hair, nail, or visible tissue change that can be tracked with appearance, spread, triggers, itching, pain, and exposures.',
'Record appearance, size, color, location, spread, itch or pain, fever, new medicines, foods, soaps, insect exposures, wounds, and photos if useful.',
['Rash', 'Itching', 'Hives', 'Bruises', 'Easy bruising', 'Bleeding', 'Wound', 'Blister', 'Skin redness', 'Skin warmth', 'Dry skin', 'Peeling skin', 'Skin color change', 'Cyanosis', 'Hair loss', 'Nail changes', 'Skin ulcer', 'Mole changes', 'Insect bite reaction', 'Pus or drainage', 'Cracked skin', 'Excessive hair growth'],
],
[
'Eye / ear / nose / throat',
'Head and neck',
'{symptom} is an eye, ear, nose, mouth, or throat symptom that may be tracked with pain, discharge, hearing or vision changes, and infection or allergy context.',
'Record side affected, pain, discharge, fever, vision or hearing changes, allergy triggers, recent infections, injuries, dental symptoms, and medicines used.',
['Eye redness', 'Eye discharge', 'Watery eyes', 'Blurred vision', 'Floaters', 'Ear discharge', 'Hearing loss', 'Tinnitus', 'Ear fullness', 'Nosebleed', 'Mouth sores', 'Dry mouth', 'Bad breath', 'Swollen gums', 'Gum bleeding', 'Taste changes', 'Smell changes', 'Jaw pain', 'Tongue swelling', 'Difficulty opening mouth'],
],
[
'Urinary / reproductive',
'Genitourinary',
'{symptom} is a urinary, pelvic, breast, menstrual, or reproductive symptom that may be tracked with timing, pain, bleeding, discharge, and fluid intake.',
'Record urination pattern, pain, color, bleeding, discharge, pelvic or flank pain, cycle timing, pregnancy possibility if relevant, sexual exposure concerns, fever, and medicines.',
['Painful urination', 'Frequent urination', 'Urgent urination', 'Blood in urine', 'Cloudy urine', 'Urinary incontinence', 'Urinary retention', 'Nighttime urination', 'Decreased urination', 'Vaginal bleeding', 'Vaginal discharge', 'Missed period', 'Heavy menstrual bleeding', 'Painful periods', 'Hot flashes', 'Breast pain', 'Breast lump', 'Nipple discharge', 'Testicular pain', 'Scrotal swelling', 'Erectile dysfunction', 'Pain with sex'],
],
[
'Musculoskeletal',
'Bones / joints / muscles',
'{symptom} is a muscle, joint, bone, or movement-related symptom that may be tracked with location, activity, injury, swelling, stiffness, and function.',
'Record affected area, injury or overuse, swelling, redness, stiffness, range of motion, weakness, pain score, walking changes, falls, medicines, and functional limits.',
['Joint swelling', 'Joint stiffness', 'Limited range of motion', 'Muscle weakness', 'Muscle cramps', 'Muscle twitching', 'Back stiffness', 'Gait change', 'Falls', 'Bone pain', 'Shoulder pain', 'Hip pain', 'Knee pain', 'Ankle swelling', 'Hand pain', 'Foot pain', 'Neck stiffness', 'Limping'],
],
[
'Mental health / sleep',
'Mood / sleep / behavior',
'{symptom} is a mood, behavior, thinking, or sleep-related symptom that can be tracked with timing, stressors, sleep, medicines, substance use, and safety concerns.',
'Record mood, sleep hours, triggers, stressors, medicines, substance use, appetite, energy, concentration, safety concerns, and support or care contacts.',
['Anxiety', 'Panic attack', 'Depressed mood', 'Mood swings', 'Irritability', 'Insomnia', 'Excessive sleepiness', 'Nightmares', 'Hallucinations', 'Delusions', 'Suicidal thoughts', 'Self-harm thoughts', 'Trouble concentrating', 'Behavior changes', 'Agitation', 'Social withdrawal', 'Low motivation', 'Racing thoughts'],
],
[
'Metabolic / endocrine',
'Hormones / metabolism',
'{symptom} is a metabolism, hormone, or blood-sugar-related symptom that may be tracked with food intake, glucose readings, medicines, hydration, and energy changes.',
'Record meals, hydration, glucose readings if available, medicines, sweating, shaking, urination, thirst, weight changes, energy, and timing around exercise or illness.',
['High blood sugar symptoms', 'Low blood sugar symptoms', 'Increased thirst', 'Frequent hunger', 'Excessive urination', 'Shakiness', 'Sweating episodes', 'Unintentional weight changes', 'Feeling overheated', 'Feeling cold', 'Salt craving'],
],
[
'Medication / allergy',
'Medication safety',
'{symptom} is a possible medicine, injection, allergy, or exposure-related symptom that is useful to record with timing, dose, route, and product details.',
'Record medicine or exposure name, dose, timing, route, new products, rash or swelling, breathing symptoms, dizziness, stomach symptoms, bleeding, and whether the medicine was stopped or changed.',
['Allergic reaction', 'Severe allergic reaction', 'Medication side effects', 'Injection-site reaction', 'Dizziness after medication', 'Nausea after medication', 'Rash after medication', 'Swelling after medication', 'Bleeding after medication', 'Sleepiness after medication'],
],
];
$urgentNames = array_fill_keys(array_map('strtolower', [
'Chest pain',
'Chest pressure',
'Shortness of breath',
'Trouble breathing',
'Coughing up blood',
'Blue lips',
'Fainting',
'Loss of consciousness',
'Seizure',
'Confusion',
'Facial droop',
'Weakness on one side',
'Slurred speech',
'Difficulty speaking',
'Sudden vision loss',
'Severe headache',
'Vomiting blood',
'Blood in stool',
'Black stools',
'Rectal bleeding',
'Severe allergic reaction',
'Suicidal thoughts',
'Self-harm thoughts',
]), true);
$rows = [];
foreach ($groups as [$category, $bodySystem, $descriptionTemplate, $tracking, $names]) {
foreach ($names as $name) {
$urgency = isset($urgentNames[strtolower($name)])
? 'Potential urgent symptom: seek emergency care for sudden, severe, new, or worsening symptoms, or when this appears with breathing trouble, chest symptoms, fainting, neurologic changes, severe bleeding, or safety concerns.'
: 'Track patterns and contact a clinician for persistent, worsening, unexplained, recurrent, or concerning symptoms.';
$searchTerms = strtolower($name . ' ' . $category . ' ' . $bodySystem . ' symptom sign medical tracking medlineplus');
$rows[] = [
$name,
$category,
$bodySystem,
str_replace('{symptom}', $name, $descriptionTemplate),
$tracking,
$urgency,
'MedlinePlus',
symptom_source_url($name),
$searchTerms,
];
}
}
return $rows;
}
function symptom_source_url(string $name): string
{
$map = [
'Abdominal pain' => 'https://medlineplus.gov/abdominalpain.html',
'Anxiety' => 'https://medlineplus.gov/anxiety.html',
'Back pain' => 'https://medlineplus.gov/backpain.html',
'Chest pain' => 'https://medlineplus.gov/chestpain.html',
'Constipation' => 'https://medlineplus.gov/constipation.html',
'Cough' => 'https://medlineplus.gov/cough.html',
'Dehydration' => 'https://medlineplus.gov/dehydration.html',
'Depressed mood' => 'https://medlineplus.gov/depression.html',
'Diarrhea' => 'https://medlineplus.gov/diarrhea.html',
'Dizziness' => 'https://medlineplus.gov/dizzinessandvertigo.html',
'Ear pain' => 'https://medlineplus.gov/earinfections.html',
'Fatigue' => 'https://medlineplus.gov/fatigue.html',
'Fever' => 'https://medlineplus.gov/fever.html',
'Hair loss' => 'https://medlineplus.gov/hairloss.html',
'Headache' => 'https://medlineplus.gov/headache.html',
'Heartburn' => 'https://medlineplus.gov/heartburn.html',
'Hives' => 'https://medlineplus.gov/hives.html',
'Insomnia' => 'https://medlineplus.gov/insomnia.html',
'Itching' => 'https://medlineplus.gov/itching.html',
'Jaundice' => 'https://medlineplus.gov/jaundice.html',
'Joint pain' => 'https://medlineplus.gov/jointdisorders.html',
'Nausea' => 'https://medlineplus.gov/nauseaandvomiting.html',
'Neck pain' => 'https://medlineplus.gov/neckinjuriesanddisorders.html',
'Pain' => 'https://medlineplus.gov/pain.html',
'Palpitations' => 'https://medlineplus.gov/heartdiseases.html',
'Rash' => 'https://medlineplus.gov/rashes.html',
'Shortness of breath' => 'https://medlineplus.gov/breathingproblems.html',
'Sore throat' => 'https://medlineplus.gov/sorethroat.html',
'Tinnitus' => 'https://medlineplus.gov/tinnitus.html',
'Tremor' => 'https://medlineplus.gov/tremor.html',
'Urinary incontinence' => 'https://medlineplus.gov/urinaryincontinence.html',
'Vertigo' => 'https://medlineplus.gov/dizzinessandvertigo.html',
'Vomiting' => 'https://medlineplus.gov/nauseaandvomiting.html',
'Wheezing' => 'https://medlineplus.gov/breathingproblems.html',
];
return $map[$name] ?? 'https://medlineplus.gov/search/?query=' . rawurlencode($name);
}
function medication_source_url(string $base, string $query): string
{
return $base . rawurlencode($query);
@@ -1227,6 +1470,11 @@ function term_detail_path(array $term): string
return '/term.php?id=' . rawurlencode((string) ($term['id'] ?? ''));
}
function symptom_detail_path(array $symptom): string
{
return '/symptom.php?id=' . rawurlencode((string) ($symptom['id'] ?? ''));
}
function find_medication_catalog_item(PDO $pdo, array $query): ?array
{
$id = filter_var($query['id'] ?? null, FILTER_VALIDATE_INT);
@@ -1281,6 +1529,29 @@ function find_medical_term_item(PDO $pdo, array $query): ?array
);
}
function find_medical_symptom_item(PDO $pdo, array $query): ?array
{
$id = filter_var($query['id'] ?? null, FILTER_VALIDATE_INT);
if ($id !== false && $id > 0) {
return db_one($pdo, 'SELECT * FROM medical_symptoms WHERE id = ?', [$id]);
}
$name = trim((string) ($query['name'] ?? $query['symptom'] ?? $query['q'] ?? ''));
if ($name === '') {
return null;
}
return db_one(
$pdo,
"SELECT *
FROM medical_symptoms
WHERE lower(name) = lower(?)
ORDER BY name
LIMIT 1",
[$name]
);
}
function medicine_reference_links(array $medicine): array
{
$links = [
@@ -1633,6 +1904,7 @@ function state_payload(array $query): array
);
$medicalTerms = db_all($pdo, 'SELECT * FROM medical_terms ORDER BY category, term');
$medicalConditions = db_all($pdo, 'SELECT * FROM medical_conditions ORDER BY category, name');
$medicalSymptoms = db_all($pdo, 'SELECT * FROM medical_symptoms ORDER BY category, name');
$medicationCatalog = db_all($pdo, 'SELECT * FROM medication_catalog ORDER BY generic_name');
$researchStudies = db_all($pdo, 'SELECT * FROM research_studies ORDER BY category, related_medicine, title');
$medicationLogs = db_all(
@@ -1719,6 +1991,7 @@ function state_payload(array $query): array
'latestMetric' => $latestMetric,
'medicalTerms' => $medicalTerms,
'medicalConditions' => $medicalConditions,
'medicalSymptoms' => $medicalSymptoms,
'medicationCatalog' => $medicationCatalog,
'researchStudies' => $researchStudies,
'medicationLogs' => $medicationLogs,
@@ -1748,6 +2021,7 @@ function library_search_payload(array $query): array
'query' => '',
'medicalTerms' => db_all($pdo, 'SELECT * FROM medical_terms ORDER BY category, term LIMIT ?', [$limit]),
'medicalConditions' => db_all($pdo, 'SELECT * FROM medical_conditions ORDER BY category, name LIMIT ?', [$limit]),
'medicalSymptoms' => db_all($pdo, 'SELECT * FROM medical_symptoms ORDER BY category, name LIMIT ?', [$limit]),
'medicationCatalog' => db_all($pdo, 'SELECT * FROM medication_catalog ORDER BY generic_name LIMIT ?', [$limit]),
'researchStudies' => db_all($pdo, 'SELECT * FROM research_studies ORDER BY category, related_medicine, title LIMIT ?', [$limit]),
];
@@ -1756,6 +2030,7 @@ function library_search_payload(array $query): array
$like = '%' . $search . '%';
$termParams = array_fill(0, 5, $like);
$conditionParams = array_fill(0, 4, $like);
$symptomParams = [...array_fill(0, 8, $like), $search, $search . '%', $like];
$medicineParams = array_fill(0, 10, $like);
$studyParams = array_fill(0, 6, $like);
@@ -1778,6 +2053,23 @@ function library_search_payload(array $query): array
LIMIT {$limit}",
$conditionParams
),
'medicalSymptoms' => db_all(
$pdo,
"SELECT * FROM medical_symptoms
WHERE name LIKE ? OR category LIKE ? OR body_system LIKE ?
OR plain_language_description LIKE ? OR common_tracking LIKE ?
OR urgency_note LIKE ? OR source_name LIKE ? OR search_terms LIKE ?
ORDER BY
CASE
WHEN lower(name) = lower(?) THEN 0
WHEN name LIKE ? THEN 1
WHEN search_terms LIKE ? THEN 2
ELSE 3
END,
category, name
LIMIT {$limit}",
$symptomParams
),
'medicationCatalog' => db_all(
$pdo,
"SELECT * FROM medication_catalog
@@ -2161,56 +2453,6 @@ function create_user(array $data): array
return ['user' => db_one($pdo, 'SELECT * FROM users WHERE id = ?', [$userId])];
}
function public_signup(array $data): array
{
$pdo = app_db();
$name = text_value($data, 'name');
if ($name === '') {
throw new InvalidArgumentException('Name is required.');
}
$userCount = (int) db_value($pdo, 'SELECT COUNT(*) FROM users');
$role = $userCount === 0 ? 'admin' : 'member';
$created = app_now();
$mode = clamp_choice(text_value($data, 'theme_mode', 'dark'), ['dark', 'light'], 'dark');
$accent = clamp_choice(text_value($data, 'theme_accent', 'green'), ['green', 'blue', 'red', 'pink', 'orange'], 'green');
$stmt = $pdo->prepare(
"INSERT INTO users
(name, email, fitness_focus, role, status, phone, birthday, height_in, training_level,
emergency_contact, theme_mode, theme_accent, updated_at, created_at)
VALUES (?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?, ?)"
);
$stmt->execute([
$name,
text_value($data, 'email'),
text_value($data, 'fitness_focus'),
$role,
text_value($data, 'phone'),
text_value($data, 'birthday'),
number_value($data, 'height_in'),
text_value($data, 'training_level'),
text_value($data, 'emergency_contact'),
$mode,
$accent,
$created,
$created,
]);
$userId = (int) $pdo->lastInsertId();
$goalStmt = $pdo->prepare(
"INSERT INTO nutrition_goals
(user_id, label, scope, calories, protein_g, carbs_g, fat_g, water_ml, start_date, created_at)
VALUES (?, 'Starter day', 'daily', 2400, 150, 260, 75, 3000, ?, ?)"
);
$goalStmt->execute([$userId, app_date(new DateTimeImmutable('today')), $created]);
return [
'user' => db_one($pdo, 'SELECT * FROM users WHERE id = ?', [$userId]),
'first_admin' => $role === 'admin',
];
}
function update_user(array $data): array
{
$pdo = app_db();