PDO::ERRMODE_EXCEPTION]); $pdo->exec("CREATE DATABASE `{$database}` CHARACTER SET utf8mb4"); $pdo->exec("USE `{$database}`"); new think\App(); $manager = new think\DbManager(); $manager->setConfig(['default' => 'mysql', 'connections' => ['mysql' => ['type' => 'mysql', 'hostname' => '127.0.0.1', 'hostport' => $port, 'database' => $database, 'username' => 'root', 'password' => $password, 'charset' => 'utf8mb4', 'prefix' => 'zyt_', 'fields_strict' => true, 'trigger_sql' => false]]]); Container::getInstance()->instance('think\DbManager', $manager); $config = new think\Config(); Container::getInstance()->instance('config', $config); $private = '/private/tmp/' . $database; mkdir($private, 0700); $config->set(array_replace(['enabled' => true, 'audio_verified' => true, 'verified_profiles' => ['qwen'], 'encryption_key' => bin2hex(random_bytes(32)), 'lease_seconds' => 60, 'concurrency' => 1, 'retention_days' => 90, 'private_dir' => $private], $settings), 'followup_audio'); $providers = $settings['providers'] ?? ['qwen' => ['driver' => 'asr_then_llm', 'label' => 'Synthetic', 'asr' => ['base_url' => 'https://asr.invalid/v1', 'api_key' => 'synthetic-asr', 'model' => 'synthetic-asr'], 'extraction' => ['base_url' => 'https://llm.invalid/v1', 'api_key' => 'synthetic-llm', 'model' => 'synthetic-llm']]]; $config->set(['providers' => $providers], 'followup_audio'); // Test-only verification flags in this isolated process/database. Never modify a deployment config. foreach (array_keys($providers) as $profile) { $providers[$profile]['verified_fingerprint'] = Provider::resolve($profile)['fingerprint']; } $config->set(['providers' => $providers], 'followup_audio'); $pdo->exec('CREATE TABLE zyt_admin(id INT PRIMARY KEY,name VARCHAR(50),root INT,disable INT,delete_time INT NULL)'); $pdo->exec("INSERT INTO zyt_admin VALUES(1,'Synthetic Root',1,0,NULL),(2,'Synthetic Assistant',0,0,NULL),(3,'Synthetic Limited',0,0,NULL)"); $pdo->exec('CREATE TABLE zyt_admin_role(admin_id INT,role_id INT)'); $pdo->exec('INSERT INTO zyt_admin_role VALUES(2,2),(3,5)'); $pdo->exec('CREATE TABLE zyt_admin_dept(admin_id INT,dept_id INT)'); $pdo->exec('CREATE TABLE zyt_admin_jobs(admin_id INT,jobs_id INT)'); $pdo->exec('CREATE TABLE zyt_system_menu(id INT PRIMARY KEY,perms VARCHAR(100),is_disable INT)'); $pdo->exec("INSERT INTO zyt_system_menu VALUES(1,'tcm.diagnosis/edit',0),(2,'tcm.diagnosis/dailyRecord',0),(3,'firstvisit.wecomPromotion/overview',0)"); $pdo->exec('CREATE TABLE zyt_system_role_menu(role_id INT,menu_id INT)'); $pdo->exec('INSERT INTO zyt_system_role_menu VALUES(2,1),(2,2),(2,3),(5,1),(5,3)'); $pdo->exec('CREATE TABLE zyt_doctor_appointment(id INT PRIMARY KEY,patient_id INT,doctor_id INT,status INT)'); $pdo->exec('CREATE TABLE zyt_dict_data(id INT PRIMARY KEY AUTO_INCREMENT,name VARCHAR(50),value VARCHAR(50),type_value VARCHAR(50),status INT,sort INT)'); $pdo->exec("INSERT INTO zyt_dict_data(name,value,type_value,status,sort) VALUES('Synthetic Enabled','dry','appetite',1,1),('Synthetic Disabled','bad','appetite',0,2)"); $pdo->exec('CREATE TABLE zyt_tcm_diagnosis(id INT PRIMARY KEY,patient_id INT,assistant_id INT DEFAULT 2,status INT DEFAULT 1,delete_time INT NULL,update_time INT DEFAULT 0) ENGINE=InnoDB'); foreach (Fields::diagnosisKeys() as $key) { $pdo->exec("ALTER TABLE zyt_tcm_diagnosis ADD `{$key}` TEXT NULL"); } $pdo->exec("INSERT INTO zyt_tcm_diagnosis(id,patient_id,patient_name,assistant_id,symptoms) VALUES(1,101,'Synthetic A',2,''),(2,202,'Synthetic B',3,''),(3,303,'Synthetic C',2,'')"); $pdo->exec('CREATE TABLE zyt_tcm_prescription_order(id INT PRIMARY KEY,diagnosis_id INT,creator_id INT,create_time INT,fulfillment_status INT,delete_time INT NULL,KEY idx_diagnosis(diagnosis_id)) ENGINE=InnoDB'); $pdo->exec('CREATE TABLE zyt_tcm_blood_record(id INT PRIMARY KEY AUTO_INCREMENT,diagnosis_id INT,patient_id INT,record_date INT,record_time VARCHAR(10),source INT DEFAULT 0,create_time INT DEFAULT 0,update_time INT DEFAULT 0,delete_time INT NULL,KEY idx_day(diagnosis_id,record_date)) ENGINE=InnoDB'); $pdo->exec('CREATE TABLE zyt_patient_diet_record(id INT PRIMARY KEY AUTO_INCREMENT,diagnosis_id INT,patient_id INT,record_date INT,create_time INT DEFAULT 0,update_time INT DEFAULT 0,delete_time INT NULL,KEY idx_day(diagnosis_id,record_date)) ENGINE=InnoDB'); $pdo->exec('CREATE TABLE zyt_patient_exercise_record(id INT PRIMARY KEY AUTO_INCREMENT,diagnosis_id INT,patient_id INT,record_date INT,create_time INT DEFAULT 0,update_time INT DEFAULT 0,delete_time INT NULL,KEY idx_day(diagnosis_id,record_date)) ENGINE=InnoDB'); foreach (['blood' => 'tcm_blood_record', 'diet' => 'patient_diet_record', 'exercise' => 'patient_exercise_record'] as $kind => $table) { $keys = $kind === 'diet' ? ['breakfast_foods', 'lunch_foods', 'dinner_foods', 'note', 'breakfast_images', 'lunch_images', 'dinner_images'] : Fields::keys($kind); foreach ($keys as $key) { $pdo->exec("ALTER TABLE zyt_{$table} ADD `{$key}` TEXT NULL"); } } $pdo->exec('CREATE TABLE zyt_tracking_note(id INT PRIMARY KEY AUTO_INCREMENT,diagnosis_id INT,admin_id INT,note_date DATE,content TEXT,create_time INT DEFAULT 0,update_time INT DEFAULT 0,delete_time INT NULL,UNIQUE KEY uk_day(diagnosis_id,note_date)) ENGINE=InnoDB'); $migration = preg_replace('/^--.*$/m', '', file_get_contents(dirname(__DIR__, 3) . '/sql/2026_09_29_followup_audio.sql')); for ($i = 0; $i < 2; $i++) { foreach (explode(';', $migration) as $statement) { if (trim($statement) !== '') { $stmt = $pdo->query($statement); $stmt->closeCursor(); } } } return ['pdo' => $pdo, 'database' => $database, 'private' => $private, 'config' => $config, 'actor' => ['root' => 1, 'admin_id' => 1, 'id' => 1, 'name' => 'Synthetic'], 'diagnosis_id' => 1]; } /** Actual private Upload API, chunk assembly, hash and ffprobe checks; caller chooses only the isolated test diagnosis. */ function followupAudioTestUpload(array $fixture, string $source, string $sampleName): array { $session = Upload::createSession(1, $sampleName, (int) filesize($source), 1, $fixture['actor']); $input = fopen($source, 'rb'); $index = 0; $part = $fixture['private'] . '/input-chunk'; try { while (!feof($input)) { $bytes = fread($input, $session['chunk_bytes']); if ($bytes === '') { break; } file_put_contents($part, $bytes); chmod($part, 0600); Upload::putChunk($session['upload_id'], $index++, $part, 1, $fixture['actor']); } } finally { fclose($input); if (is_file($part)) { unlink($part); } } Upload::complete($session['upload_id'], 1, $fixture['actor']); return Upload::session($session['upload_id']); } function followupAudioTestDatabaseCleanup(array $fixture): void { if (!preg_match('/^fa_pipeline_[a-f0-9]{12}$/D', $fixture['database']) || $fixture['private'] !== '/private/tmp/' . $fixture['database']) { throw new RuntimeException('Disposable cleanup identity mismatch'); } $fixture['pdo']->exec('DROP DATABASE `' . $fixture['database'] . '`'); $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($fixture['private'], FilesystemIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST); foreach ($iterator as $entry) { $entry->isDir() && !$entry->isLink() ? rmdir($entry->getPathname()) : unlink($entry->getPathname()); } rmdir($fixture['private']); }