# code_chunks_vec_auxiliary

value00: app/Models/PatVisit/PatVisitModel.php
value01:
			// patdiag
			$exist = $modelPD->where('InternalPVID',$InternalPVID)->find();
			if($exist) {
				if( !empty($input['PatDiag']) && ( !empty($input['PatDiag']['DiagCode']) || !empty($input['PatDiag']['Diagnosis']) ) )  { 
					$tmp = $modelPD->where('InternalPVID',$InternalPVID)->set($input['PatDiag'])->update();
				} else { $tmp = $modelPD->delete($InternalPVID); }
			} else {
				if( !empty($input['PatDiag']) && ( !empty($input['PatDiag']['DiagCode']) || !empty($input['PatDiag']['Diagnosis']) ) )  { 
					$input['PatDiag']['InternalPVID'] = $InternalPVID;
					$tmp = $modelPD->insert($input['PatDiag']);
				} 
			}
			if (isset($tmp) && $tmp === false) { 
			$error = $db->error();
			throw new \Exception("Failed to update PatDiag record. ". $error['message']); 
		}


value00: app/Models/PatVisit/PatVisitModel.php
value01:
$this->select("*, patvisit.InternalPID, patvisit.CreateDate as PVCreateDate, patdiag.CreateDate as PDCreateDate, patvisitadt.CreateDate as PVACreateDate")
			->join('patdiag', 'patdiag.InternalPVID=patvisit.InternalPVID and patdiag.DelDate is null', 'left')
			->join('(SELECT a1.*
          FROM patvisitadt a1
          INNER JOIN (
              SELECT InternalPVID, MAX(PVADTID) AS MaxID
              FROM patvisitadt
              GROUP BY InternalPVID
          ) a2 ON a1.InternalPVID = a2.InternalPVID AND a1.PVADTID = a2.MaxID
        ) AS patvisitadt',
        'patvisitadt.InternalPVID = patvisit.InternalPVID',
        'left')
			->join('location', 'location.LocationID=patvisitadt.LocationID', 'left')
			->where('patvisit.InternalPID',$InternalPID)
			->


value00: app/Models/PatVisit/PatVisitModel.php
value01:
				$tmp = $modelPD->insert($input['PatDiag']);
				if ($tmp === false) { throw new \Exception("Failed to insert PatDiag record."); }
			}
			if( !empty($input['PatVisitADT']) ) {
				$input['PatVisitADT']['InternalPVID'] = $InternalPVID;
				$tmp = $modelPVA->insert($input['PatVisitADT']); 
				if ($tmp === false) {
					throw new \Exception("Failed to insert PatVisitADT record.");
				}
			}
			
      if ($db->transStatus() === FALSE) {
				$db->transRollback();
				return false;
			} else {
				$db->transCommit();
				$data = [ "PVID" => $input['PVID'], "InternalPVID" => $InternalPVID ];
				return $data;
			}
    } catch (\Exception $e) {
      $db->transRollback();
      throw $e; 
    }
	}


value00: app/Models/RefRange/RefTxtModel.php
value01:
($testSiteID)
  {
      $rows = $this->getActiveByTestSiteID($testSiteID);
      
      return array_map(function ($r) {
          return [
              'RefTxtID'       => $r['RefTxtID'],
              'TxtRefType'     => $r['TxtRefType'],
              'TxtRefTypeLabel'=> $r['TxtRefType'] ? \App\Libraries\ValueSet::getLabel('text_ref_type', $r['TxtRefType']) : '',
              'Sex'            => $r['Sex'],
              'SexLabel'       => $r['Sex'] ? \App\Libraries\ValueSet::getLabel('gender', $r['Sex']) : '',
              'AgeStart'       => (int) $r['AgeStart'],
              'AgeEnd'         => (int) $r['AgeEnd'],
              'RefTxt'         => $r['RefTxt'],
              'Flag'           => $r['Flag'],
          ];
      }, $rows ?? []);
  }


value00: app/Models/RefRange/RefTxtModel.php
value01:
  /**
   * Disable all text reference ranges for a test
   *
   * @param int $testSiteID
   * @return void
   */
  public function disableByTestSiteID($testSiteID)
  {
      $this->where('TestSiteID', $testSiteID)
          ->set('EndDate', date('Y-m-d H:i:s'))
          ->update();
  }

  /**
   * Batch insert text reference ranges
   *
   * @param int $testSiteID
   * @param int $siteID
   * @param array $ranges
   * @return void
   */


value00: app/Models/RefRange/RefTxtModel.php
value01:
  public function batchInsert($testSiteID, $siteID, $ranges)
  {
        foreach ($ranges as $range) {
            $this->insert([
                'TestSiteID' => $testSiteID,
                'SiteID'     => $siteID,
                'SpcType'    => $range['SpcType'] ?? 'GEN',
                'TxtRefType' => $range['TxtRefType'],
                'Sex'        => $range['Sex'],
                'AgeStart'   => (int) ($range['AgeStart'] ?? 0),
                'AgeEnd'     => (int) ($range['AgeEnd'] ?? 150),
                'RefTxt'     => $range['RefTxt'] ?? '',
                'Flag'       => $range['Flag'] ?? null,
                'CreateDate' => date('Y-m-d H:i:s'),
            ]);
        }
    }
}


value00: app/Models/RefRange/RefTxtModel.php
value01:
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField = "EndDate";

  /**
   * Get active text reference ranges for a test
   */
  public function getActiveByTestSiteID($testSiteID) {
    return $this->where('TestSiteID', $testSiteID)
      ->where('EndDate', null)
      ->orderBy('RefTxtID', 'ASC')
      ->findAll();
  }


  /**
   * Get formatted text reference ranges with labels
   *
   * @param int $testSiteID
   * @return array
   */
  public function getFormattedByTestSiteID($testSiteID)


value00: app/Models/RefRange/RefTxtModel.php
value01:
<?php

namespace App\Models\RefRange;

use App\Models\BaseModel;

class RefTxtModel extends BaseModel
{
  protected $table = 'reftxt';
  protected $primaryKey = 'RefTxtID';
  protected $allowedFields = [
    'SiteID',
    'TestSiteID',
    'SpcType',
    'Sex',
    'Criteria',
    'AgeStart',
    'AgeEnd',
    'TxtRefType',
    'RefTxt',
    'Flag',
    'Notes',
    'CreateDate',
    'StartDate',
    'EndDate'
  ];

  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';


value00: app/Models/Specimen/ContainerDefModel.php
value01:
  public function getContainer($ConDefID) {
    $row = $this->select('containerdef.*')
      ->where('ConDefID', $ConDefID)->first();
    
    if (!$row) return null;
    
    $row = ValueSet::transformLabels([$row], [
      'Color' => 'container_cap_color',
      'ConClass' => 'container_class',
      'Additive' => 'additive',
    ])[0];
    
    return $row;
  }
}


value00: app/Models/Specimen/ContainerDefModel.php
value01:
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField  = 'EndDate';
	

	public function getContainers($filter = []) {
		$builder = $this->select('containerdef.*');

		if (!empty($filter['ConCode'])) {
			$builder->like('containerdef.ConCode', $filter['ConCode'], 'both');
		}
		if (!empty($filter['ConName'])) {
			$builder->like('containerdef.ConName', $filter['ConName'], 'both');
		}

		$rows = $builder->findAll();
    $rows = ValueSet::transformLabels($rows, [
			'Color' => 'container_cap_color',
			'ConClass' => 'container_class',
			'Additive' => 'additive',
		]);
		return $rows;
	}


value00: app/Models/Specimen/ContainerDefModel.php
value01:
<?php
namespace App\Models\Specimen;

use App\Models\BaseModel;
use App\Libraries\ValueSet;

class ContainerDefModel extends BaseModel {
  protected $table      = 'containerdef';
  protected $primaryKey = 'ConDefID';
  protected $allowedFields = ['SiteID', 'ConCode', 'ConName', 'ConDesc', 'Additive', 'ConClass', 'Color', 'CreateDate', 'EndDate']; 
	
	protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField  = 'EndDate';


value00: app/Models/Specimen/SpecimenLogModel.php
value01:
<?php
namespace App\Models\Specimen;

use App\Models\BaseModel;

class SpecimenPrepModel extends BaseModel {
  protected $table      = 'specimenprep';
  protected $primaryKey = 'SpcPrpID';
  protected $allowedFields = ['SpcStaID', 'Description', 'Method', 'Additive', 'AddQty', 'AddUnit', 'PrepStart', 'PrepEnd', 'CreateDate', 'EndDate', 'ArchiveDate']; 
	
	protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField  = 'EndDate';
}


value00: app/Models/Specimen/SpecimenPrepModel.php
value01:
<?php
namespace App\Models\Specimen;

use App\Models\BaseModel;

class SpecimenPrepModel extends BaseModel {
  protected $table      = 'specimenprep';
  protected $primaryKey = 'SpcPrpID';
  protected $allowedFields = ['SpcStaID', 'Description', 'Method', 'Additive', 'AddQty', 'AddUnit', 'PrepStart', 'PrepEnd', 'CreateDate', 'EndDate', 'ArchiveDate']; 
	
	protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField  = 'EndDate';
	
}


value00: app/Models/Specimen/SpecimenCollectionModel.php
value01:
<?php
namespace App\Models\Specimen;

use App\Models\BaseModel;

class SpecimenCollectionModel extends BaseModel {
  protected $table      = 'specimencollection';
  protected $primaryKey = 'SpcColID';
  protected $allowedFields = ['SpcColID', 'SpcStaID', 'SpRole', 'ColMethod', 'BodySite', 'CntSize', 'FastingVolume', 
    'ColStart', 'ColEnd', 'CreateDate', 'EndDate', 'ArchiveDate']; 
	
	protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField  = 'EndDate';
	
}


value00: app/Models/Specimen/SpecimenModel.php
value01:
<?php
namespace App\Models\Specimen;

use App\Models\BaseModel;

class SpecimenModel extends BaseModel {
  protected $table      = 'specimen';
  protected $primaryKey = 'InternalSID';
  protected $allowedFields = ['SID', 'SiteID', 'OrderID', 'ConDefID', 'Parent', 'Qty', 'Unit', 'GenerateBy',
    'SchDateTime','CreateDate', 'EndDate', 'ArchiveDate']; 
	
	protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField  = 'EndDate';
	
}


value00: app/Models/Test/TestDefCalModel.php
value01:
<?php

namespace App\Models\Test;

use App\Models\BaseModel;

class TestDefCalModel extends BaseModel {
  protected $table      = 'testdefcal';
  protected $primaryKey = 'TestCalID';
  protected $allowedFields = [
    'TestSiteID',
    'DisciplineID', 
    'DepartmentID',
    'FormulaCode', 
    'RefType',
    'Unit1', 
    'Factor', 
    'Unit2', 
    'Decimal',
    'Method',
    'CreateDate', 
    'EndDate'
  ];  
  
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';


value00: app/Models/Test/TestDefCalModel.php
value01:
  /**
   * Check if calculation exists for a test
   */
  public function existsByTestSiteID($testSiteID) {
    return $this->db->table('testdefcal')
      ->where('TestSiteID', $testSiteID)
      ->where('EndDate IS NULL')
      ->get()->getRowArray();
  }

  /**
   * Find an active calculation by TestSiteCode or TestSiteName (case-insensitive).
   */


value00: app/Models/Test/TestDefCalModel.php
value01:
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField = "EndDate";

  /**
   * Get calculation details for a test with relations
   */
  public function getByTestSiteID($testSiteID) {
    return $this->db->table('testdefcal')
      ->select('testdefcal.*, d.DisciplineName, dept.DepartmentName')
      ->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left')
      ->join('department dept', 'dept.DepartmentID=testdefcal.DepartmentID', 'left')
      ->where('testdefcal.TestSiteID', $testSiteID)
      ->where('testdefcal.EndDate IS NULL')
      ->get()->getResultArray();
  }

  /**
   * Check if calculation exists for a test
   */


value00: app/Models/Test/TestDefCalModel.php
value01:
  public function findActiveByCodeOrName(string $codeOrName): ?array
  {
    $identifier = mb_strtolower(trim($codeOrName));

    if ($identifier === '') {
      return null;
    }

    $builder = $this->db->table('testdefcal cal')
      ->select('cal.*, site.TestSiteCode, site.TestSiteName')
      ->join('testdefsite site', 'site.TestSiteID=cal.TestSiteID', 'left')
      ->where('cal.EndDate IS NULL')
      ->where('site.EndDate IS NULL')
      ->groupStart()
        ->where('LOWER(site.TestSiteCode)', $identifier)
        ->orWhere('LOWER(site.TestSiteName)', $identifier)
      ->groupEnd()
      ->orderBy('cal.CreateDate', 'DESC')
      ->limit(1);

    $row = $builder->get()->getRowArray();

    return $row ?: null;
  }


value00: app/Models/Test/TestDefCalModel.php
value01:
  /**
   * Get calculated tests by department
   */
  public function getCalcsByDepartment($departmentID, $siteID = null) {
    $builder = $this->select('testdefcal.*, testdefsite.TestSiteCode, testdefsite.TestSiteName')
        ->join('testdefsite', 'testdefsite.TestSiteID=testdefcal.TestSiteID', 'left')
        ->where('testdefcal.DepartmentID', $departmentID)
        ->where('testdefcal.EndDate IS NULL');
    
    if ($siteID) {
        $builder->where('testdefsite.SiteID', $siteID);
    }
    
    return $builder->findAll();
  }
}


value00: app/Models/Test/TestDefCalModel.php
value01:
  /**
   * Disable calculation by TestSiteID
   */
  public function disableByTestSiteID($testSiteID) {
    $this->db->table('testdefcal')
      ->where('TestSiteID', $testSiteID)
      ->update(['EndDate' => date('Y-m-d H:i:s')]);
  }

  /**
   * Get calculated tests by discipline
   */
  public function getCalcsByDiscipline($disciplineID, $siteID = null) {
    $builder = $this->select('testdefcal.*, testdefsite.TestSiteCode, testdefsite.TestSiteName')
        ->join('testdefsite', 'testdefsite.TestSiteID=testdefcal.TestSiteID', 'left')
        ->where('testdefcal.DisciplineID', $disciplineID)
        ->where('testdefcal.EndDate IS NULL');
    
    if ($siteID) {
        $builder->where('testdefsite.SiteID', $siteID);
    }
    
    return $builder->findAll();
  }


value00: app/Models/Rule/RuleDefModel.php
value01:
    public function getLinkedTests(int $ruleID): array
    {
        $db = \Config\Database::connect();
        $result = $db->table('testrule')
            ->where('RuleID', $ruleID)
            ->where('EndDate IS NULL')
            ->select('TestSiteID')
            ->get()
            ->getResultArray();
        
        return array_map('intval', array_column($result, 'TestSiteID'));
    }

    /**
     * Link a rule to a test
     *
     * @param int $ruleID The rule ID
     * @param int $testSiteID The test site ID
     * @return bool Success status
     */
    public function linkTest(int $ruleID, int $testSiteID): bool
    {
        $db = \Config\Database::connect();
        
        // Check if already linked (and not soft deleted)


value00: app/Models/Rule/RuleDefModel.php
value01:
    {
        $db = \Config\Database::connect();
        
        // Check if already linked (and not soft deleted)
        $existing = $db->table('testrule')
            ->where('RuleID', $ruleID)
            ->where('TestSiteID', $testSiteID)
            ->where('EndDate IS NULL')
            ->get()
            ->getRowArray();
        
        if ($existing) {
            return true; // Already linked
        }
        
        // Check if soft deleted - restore it


value00: app/Models/Rule/RuleDefModel.php
value01:
        $rawExpr = $rule['ConditionExpr'] ?? '';
        if (empty($rawExpr)) {
            return false;
        }

        try {
            $compiled = $this->compileExpression($rawExpr);
            return $this->update($ruleID, [
                'ConditionExprCompiled' => json_encode($compiled),
            ]);
        } catch (\Throwable $e) {
            log_message('error', 'Auto-compile failed for RuleID=' . $ruleID . ': ' . $e->getMessage());
            return false;
        }
    }

    /**
     * Compile all rules that need recompilation
     *
     * @return array ['success' => int, 'failed' => int]
     */


value00: app/Models/Rule/RuleDefModel.php
value01:
    public function compileAllPending(): array
    {
        $rules = $this->where('EndDate IS NULL')->findAll();
        $success = 0;
        $failed = 0;

        foreach ($rules as $rule) {
            $ruleID = (int) ($rule['RuleID'] ?? 0);
            if ($ruleID === 0) {
                continue;
            }

            if ($this->needsRecompilation($ruleID)) {
                if ($this->autoCompile($ruleID)) {
                    $success++;
                } else {
                    $failed++;
                }
            }
        }

        return [
            'success' => $success,
            'failed' => $failed,
        ];
    }
}


value00: app/Models/Rule/RuleDefModel.php
value01:
    public function compileExpression(string $expr): array
    {
        $service = new \App\Services\RuleExpressionService();
        return $service->compile($expr);
    }

    /**
     * Check if a rule's expression needs recompilation
     * Returns true if the raw expression differs from the compiled version
     *
     * @param int $ruleID The rule ID to check
     * @return bool True if recompilation is needed
     */
    public function needsRecompilation(int $ruleID): bool


value00: app/Models/Rule/RuleDefModel.php
value01:
    /**
     * Unlink a rule from a test (soft delete)
     *
     * @param int $ruleID The rule ID
     * @param int $testSiteID The test site ID
     * @return bool Success status
     */
    public function unlinkTest(int $ruleID, int $testSiteID): bool
    {
        $db = \Config\Database::connect();
        
        return $db->table('testrule')
            ->where('RuleID', $ruleID)
            ->where('TestSiteID', $testSiteID)
            ->where('EndDate IS NULL')
            ->update(['EndDate' => date('Y-m-d H:i:s')]);
    }

    /**
     * Validate that a DSL expression compiles successfully
     *
     * @param string $expr The DSL expression to validate
     * @return array ['valid' => bool, 'error' => string|null, 'compiled' => array|null]
     */


value00: app/Models/Rule/RuleDefModel.php
value01:
    /**
     * Automatically compile expression if needed and update the rule
     *
     * @param int $ruleID The rule ID
     * @return bool Success status
     */
    public function autoCompile(int $ruleID): bool
    {
        if (!$this->needsRecompilation($ruleID)) {
            return true;
        }

        $rule = $this->find($ruleID);
        if (!$rule) {
            return false;
        }

        $rawExpr = $rule['ConditionExpr'] ?? '';
        if (empty($rawExpr)) {
            return false;
        }


value00: app/Models/Rule/RuleDefModel.php
value01:
    protected $updatedField = 'StartDate';

    protected $useSoftDeletes = true;
    protected $deletedField = 'EndDate';

    /**
     * Fetch active rules for an event scoped by TestSiteID.
     *
     * Rules are standalone and only apply when explicitly linked to a test
     * via the testrule mapping table.
     *
     * @param string $eventCode The event code to filter by
     * @param int|null $testSiteID The test site ID to filter by
     * @return array Array of matching rules
     */


value00: app/Models/Rule/RuleDefModel.php
value01:
        // Check if soft deleted - restore it
        $softDeleted = $db->table('testrule')
            ->where('RuleID', $ruleID)
            ->where('TestSiteID', $testSiteID)
            ->where('EndDate IS NOT NULL')
            ->get()
            ->getRowArray();
        
        if ($softDeleted) {
            return $db->table('testrule')
                ->where('TestRuleID', $softDeleted['TestRuleID'])
                ->update(['EndDate' => null]);
        }
        
        // Create new link
        return $db->table('testrule')->insert([
            'RuleID' => $ruleID,
            'TestSiteID' => $testSiteID,
            'CreateDate' => date('Y-m-d H:i:s'),
        ]);
    }


value00: app/Models/Rule/RuleDefModel.php
value01:
function needsRecompilation(int $ruleID): bool
    {
        $rule = $this->find($ruleID);
        if (!$rule) {
            return false;
        }

        $rawExpr = $rule['ConditionExpr'] ?? '';
        $compiledJson = $rule['ConditionExprCompiled'] ?? '';

        if (empty($rawExpr)) {
            return false;
        }

        if (empty($compiledJson)) {
            return true;
        }

        // Try to compile current expression and compare with stored version
        try {
            $compiled = $this->compileExpression($rawExpr);
            $stored = json_decode($compiledJson, true);
            
            return json_encode($compiled) !== json_encode($stored);
        } catch (\Throwable $e) {
            return true;
        }
    }


value00: app/Models/Rule/RuleDefModel.php
value01:
<?php

namespace App\Models\Rule;

use App\Models\BaseModel;

/**
 * RuleDef Model
 * 
 * Rule definitions that can be linked to multiple tests via testrule mapping table.
 */
class RuleDefModel extends BaseModel
{
    protected $table = 'ruledef';
    protected $primaryKey = 'RuleID';
    protected $allowedFields = [
        'RuleCode',
        'RuleName',
        'Description',
        'EventCode',
        'ConditionExpr',
        'ConditionExprCompiled',
        'CreateDate',
        'StartDate',
        'EndDate',
    ];

    protected $useTimestamps = true;
    protected $createdField = 'CreateDate';
    protected $updatedField = 'StartDate';

    protected $useSoftDeletes = true;
    protected $deletedField = 'EndDate';


value00: app/Models/Rule/RuleDefModel.php
value01:
    public function getActiveByEvent(string $eventCode, ?int $testSiteID = null): array
    {
        if ($testSiteID === null) {
            return [];
        }

        return $this->select('ruledef.*')
            ->join('testrule', 'testrule.RuleID = ruledef.RuleID', 'inner')
            ->where('ruledef.EventCode', $eventCode)
            ->where('ruledef.EndDate IS NULL')
            ->where('testrule.TestSiteID', $testSiteID)
            ->where('testrule.EndDate IS NULL')
            ->orderBy('ruledef.RuleID', 'ASC')
            ->findAll();
    }

    /**
     * Get all tests linked to a rule
     *
     * @param int $ruleID The rule ID
     * @return array Array of test site IDs
     */


value00: app/Models/Rule/RuleDefModel.php
value01:
    public function validateExpression(string $expr): array
    {
        $service = new \App\Services\RuleExpressionService();
        
        try {
            $compiled = $service->compile($expr);
            return [
                'valid' => true,
                'error' => null,
                'compiled' => $compiled,
            ];
        } catch (\Throwable $e) {
            return [
                'valid' => false,
                'error' => $e->getMessage(),
                'compiled' => null,
            ];
        }
    }

    /**
     * Compile a DSL expression to JSON
     *
     * @param string $expr The DSL expression to compile
     * @return array The compiled structure
     * @throws \InvalidArgumentException If compilation fails
     */


value00: app/Services/CalculatorService.php
value01:
  public function extractVariables(string $formula): array {
    preg_match_all('/\{([^}]+)\}/', $formula, $matches);
    return array_unique($matches[1]);
  }

  /**
   * Prepare expression by replacing placeholders with values
   */
  protected function prepareExpression(string $formula, array $variables): string


value00: app/Services/CalculatorService.php
value01:
(string $formula, array $variables): string {
    $expression = $formula;

    foreach ($variables as $key => $value) {
      $placeholder = '{' . $key . '}';
      
      // Handle gender specially
      if ($key === 'gender') {
        $value = $this->normalizeGender($value);
      }
      
      // Ensure numeric value
      if (!is_numeric($value)) {
        throw new \Exception("Variable '{$key}' must be numeric, got: " . var_export($value, true));
      }
      
      $expression = str_replace($placeholder, (float) $value, $expression);
    }

    // Check for unreplaced placeholders
    if (preg_match('/\{([^}]+)\}/', $expression, $unreplaced)) {
      throw new \Exception("Missing variable value for: {$unreplaced[1]}");
    }

    return $expression;
  }


value00: app/Services/CalculatorService.php
value01:
<?php

namespace App\Services;

use MathParser\StdMathParser;
use MathParser\Interpreting\Evaluator;
use MathParser\Exceptions\MathParserException;

class CalculatorService {
  protected StdMathParser $parser;
  protected Evaluator $evaluator;

  /**
   * Gender mapping for calculations
   * 0 = Unknown, 1 = Female, 2 = Male
   */
  protected const GENDER_MAP = [
    'unknown' => 0,
    'female' => 1,
    'male' => 2,
    '0' => 0,
    '1' => 1,
    '2' => 2,
  ];

  public function __construct() {
    $this->parser = new StdMathParser();
    $this->evaluator = new Evaluator();
  }


value00: app/Services/CalculatorService.php
value01:
  public function __construct() {
    $this->parser = new StdMathParser();
    $this->evaluator = new Evaluator();
  }

  /**
   * Calculate formula with variables
   *
   * @param string $formula Formula with placeholders like {result}, {factor}, {gender}
   * @param array $variables Array of variable values
   * @return float|null Calculated result or null on error
   * @throws \Exception
   */
  public function calculate(string $formula, array $variables = []): ?float


value00: app/Services/CalculatorService.php
value01:
    if (empty($formula)) {
      throw new \Exception('No formula defined');
    }

    // Build variables array
    $variables = [
      'result' => $testValues['result'] ?? 0,
      'factor' => $calcDef['Factor'] ?? 1,
    ];


value00: app/Services/CalculatorService.php
value01:
(string $formula, array $variables = []): ?float {
    try {
      $normalizedFormula = $this->normalizeFormulaVariables($formula, $variables);
      $expression = $this->prepareExpression($normalizedFormula, $variables);
      
      // Parse the expression
      $ast = $this->parser->parse($expression);
      
      // Evaluate
      $result = $ast->accept($this->evaluator);
      
      return (float) $result;
    } catch (MathParserException $e) {
      log_message('error', 'MathParser error: ' . $e->getMessage() . ' | Formula: ' . $formula);
      throw new \Exception('Invalid formula: ' . $e->getMessage());
    } catch (\Exception $e) {
      log_message('error', 'Calculator error: ' . $e->getMessage() . ' | Formula: ' . $formula);
      throw $e;
    }
  }


value00: app/Services/CalculatorService.php
value01:
    $variables = [
      'result' => $testValues['result'] ?? 0,
      'factor' => $calcDef['Factor'] ?? 1,
    ];

    // Add optional variables
    if (isset($testValues['gender'])) {
      $variables['gender'] = $testValues['gender'];
    }
    if (isset($testValues['age'])) {
      $variables['age'] = $testValues['age'];
    }
    if (isset($testValues['ref_low'])) {
      $variables['ref_low'] = $testValues['ref_low'];
    }
    if (isset($testValues['ref_high'])) {
      $variables['ref_high'] = $testValues['ref_high'];
    }

    // Merge any additional test values
    $variables = array_merge($variables, $testValues);

    return $this->calculate($formula, $variables);
  }
}


value00: app/Services/CalculatorService.php
value01:
  /**
   * Normalize gender value to numeric (0, 1, or 2)
   */
  protected function normalizeGender($gender): int {
    if (is_numeric($gender)) {
      $num = (int) $gender;
      return in_array($num, [0, 1, 2], true) ? $num : 0;
    }
    
    $genderLower = strtolower((string) $gender);
    return self::GENDER_MAP[$genderLower] ?? 0;
  }

  /**
   * Calculate from TestDefCal record
   *
   * @param array $calcDef Test calculation definition
   * @param array $testValues Test result values
   * @return float|null
   */
  public function calculateFromDefinition(array $calcDef, array $testValues): ?float {
    $formula = $calcDef['FormulaCode'] ?? '';
    
    if (empty($formula)) {
      throw new \Exception('No formula defined');
    }


value00: app/Services/CalculatorService.php
value01:
  /**
   * Validate formula syntax
   *
   * @param string $formula Formula to validate
   * @return array ['valid' => bool, 'error' => string|null]
   */
  public function validate(string $formula): array {
    try {
      // Replace placeholders with dummy values for validation
      $testExpression = preg_replace('/\{([^}]+)\}/', '1', $formula);
      $this->parser->parse($testExpression);
      return ['valid' => true, 'error' => null];
    } catch (MathParserException $e) {
      return ['valid' => false, 'error' => $e->getMessage()];
    }
  }

  /**
   * Extract variable names from formula
   *
   * @param string $formula Formula with placeholders
   * @return array List of variable names
   */


value00: app/Services/CalculatorService.php
value01:
  /**
   * Normalize formulas that reference raw variable names instead of placeholders.
   */
  protected function normalizeFormulaVariables(string $formula, array $variables): string
  {
    if (str_contains($formula, '{')) {
      return $formula;
    }

    if (empty($variables)) {
      return $formula;
    }

    $keys = array_keys($variables);
    usort($keys, fn($a, $b) => mb_strlen($b) <=> mb_strlen($a));

    foreach ($keys as $key) {
      $escaped = preg_quote($key, '/');
      $formula = preg_replace_callback('/\b' . $escaped . '\b/i', function () use ($key) {
        return '{' . $key . '}';
      }, $formula);
    }

    return $formula;
  }

  /**
   * Normalize gender value to numeric (0, 1, or 2)
   */


value00: app/Models/Specimen/SpecimenStatusModel.php
value01:
<?php
namespace App\Models\Specimen;

use App\Models\BaseModel;

class SpecimenStatusModel extends BaseModel {
  protected $table      = 'specimenstatus';
  protected $primaryKey = 'SpcStaID';
  protected $allowedFields = ['SID', 'OrderID', 'SpcAct', 'ActRes', 'SpcStatus', 'Qty', 'Unit', 'SpcCon', 'Comment', 'CurrSiteID', 'CurrLocID', 'Origin', 
    'GeoLocationSystem', 'GeoLocationData', 'DIDType', 'DID', 'UserID', 'CreateDate', 'EndDate', 'ArchiveDate']; 
	
	protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField  = 'EndDate';
	
}


value00: app/Models/Rule/TestRuleModel.php
value01:
    protected $updatedField = '';

    protected $useSoftDeletes = true;
    protected $deletedField = 'EndDate';

    /**
     * Get all active test site mappings for a rule
     *
     * @param int $ruleID The rule ID
     * @return array Array of mappings with test site details
     */
    public function getByRuleID(int $ruleID): array
    {
        return $this->where('RuleID', $ruleID)
            ->where('EndDate IS NULL')
            ->findAll();
    }

    /**
     * Get all active rules mapped to a test site
     *
     * @param int $testSiteID The test site ID
     * @return array Array of mappings
     */


value00: app/Models/Rule/TestRuleModel.php
value01:
    public function getByTestSiteID(int $testSiteID): array
    {
        return $this->where('TestSiteID', $testSiteID)
            ->where('EndDate IS NULL')
            ->findAll();
    }

    /**
     * Check if a rule is linked to a test site
     *
     * @param int $ruleID The rule ID
     * @param int $testSiteID The test site ID
     * @return bool True if linked and active
     */
    public function isLinked(int $ruleID, int $testSiteID): bool
    {
        return $this->where('RuleID', $ruleID)
            ->where('TestSiteID', $testSiteID)
            ->where('EndDate IS NULL')
            ->countAllResults() > 0;
    }
}


value00: app/Models/Rule/TestRuleModel.php
value01:
<?php

namespace App\Models\Rule;

use App\Models\BaseModel;

/**
 * TestRule Model
 * 
 * Mapping table linking ruledef to test sites.
 * Represents which tests a rule is linked to.
 */
class TestRuleModel extends BaseModel
{
    protected $table = 'testrule';
    protected $primaryKey = 'TestRuleID';
    protected $allowedFields = [
        'RuleID',
        'TestSiteID',
        'CreateDate',
        'EndDate',
    ];

    protected $useTimestamps = true;
    protected $createdField = 'CreateDate';
    protected $updatedField = '';

    protected $useSoftDeletes = true;
    protected $deletedField = 'EndDate';


value00: app/Models/Test/TestDefSiteModel.php
value01:
{
  protected $table      = 'testdefsite';
  protected $primaryKey = 'TestSiteID';
  protected $allowedFields = [
    'SiteID', 
    'TestSiteCode', 'TestSiteName', 'TestType', 'Description', 
    'DisciplineID', 'DepartmentID',
    'ResultType', 'RefType', 'Vset',
    'Unit1', 'Factor', 'Unit2', 'Decimal',
    'ReqQty', 'ReqQtyUnit', 'CollReq', 'Method', 'ExpectedTAT',
    'SeqScr', 'SeqRpt', 'IndentLeft', 'FontStyle', 'VisibleScr', 'VisibleRpt', 
    'CountStat', 'Level', 'Requestable',
    'CreateDate', 'StartDate','EndDate'
  ];
   
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = 'StartDate';
  protected $useSoftDeletes = true;
  protected $deletedField = "EndDate";


value00: app/Models/Test/TestDefSiteModel.php
value01:
    $totalBuilder = clone $builder;
    $total = $totalBuilder->countAllResults();
    
    // Apply pagination
    $offset = ($page - 1) * $perPage;
    $rows = $builder->orderBy('testdefsite.SeqScr', 'ASC')
        ->limit($perPage, $offset)
        ->findAll();
    
    $rows = ValueSet::transformLabels($rows, [
      'TestType' => 'test_type',
    ]);
    
    return [
        'data' => $rows,
        'pagination' => [
            'total' => $total
        ]
    ];
  }


value00: app/Models/Test/TestDefSiteModel.php
value01:
  /**
   * Get tests list with discipline and department info
   */
  public function getTestsWithRelations($filters = []) {
    $builder = $this->db->table('testdefsite')
      ->select(
        "testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType, 
        testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt,
        testdefsite.CountStat, testdefsite.StartDate, testdefsite.EndDate,
        COALESCE(testdefsite.DisciplineID, cal.DisciplineID) as DisciplineID,
        COALESCE(testdefsite.DepartmentID, cal.DepartmentID) as DepartmentID,
        d.DisciplineName, dept.DepartmentName"
      )
      ->join('testdefcal cal', 'cal.TestSiteID = testdefsite.TestSiteID AND cal.EndDate IS NULL', 'left')


value00: app/Models/Test/TestDefSiteModel.php
value01:
    return $row;
  }

  /**
   * Validate test type combination
   *
   * @param string $testType
   * @param string $resultType
   * @param string $refType
   * @return array ['valid' => bool, 'error' => string|null]
   */
  public function validateTypes(string $testType, string $resultType, string $refType): array
  {
      return TestValidationService::validate($testType, $resultType, $refType);
  }

  /**
   * Check if test needs reference ranges
   *
   * @param string $resultType
   * @return bool
   */
  public function needsReferenceRanges(string $resultType): bool
  {
      return TestValidationService::needsReferenceRanges($resultType);
  }


value00: app/Models/Test/TestDefSiteModel.php
value01:
    if (TestValidationService::isCalc($typeCode)) {
        $row['testdefcal'] = $db->table('testdefcal')
            ->select('testdefcal.*, d.DisciplineName, dept.DepartmentName')
            ->join('discipline d', 'd.DisciplineID=testdefcal.DisciplineID', 'left')
            ->join('department dept', 'dept.DepartmentID=testdefcal.DepartmentID', 'left')
            ->where('testdefcal.TestSiteID', $TestSiteID)
            ->where('testdefcal.EndDate IS NULL')
            ->get()->getResultArray();
        
        $testMapModel = new \App\Models\Test\TestMapModel();
        $row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
        
    }


value00: app/Models/Test/TestDefSiteModel.php
value01:
{
    $builder = $this->select("testdefsite.TestSiteID, testdefsite.TestSiteCode, testdefsite.TestSiteName, testdefsite.TestType, 
                  testdefsite.SeqScr, testdefsite.SeqRpt, testdefsite.VisibleScr, testdefsite.VisibleRpt,
                  testdefsite.CountStat, testdefsite.StartDate, testdefsite.EndDate")
      ->where('testdefsite.EndDate IS NULL');
    
    if ($siteId) {
        $builder->where('testdefsite.SiteID', $siteId);
    }
    
    if ($testType) {
        $builder->where('testdefsite.TestType', $testType);
    }
    
    if ($visibleScr !== null) {
        $builder->where('testdefsite.VisibleScr', $visibleScr);
    }
    
    if ($visibleRpt !== null) {
        $builder->where('testdefsite.VisibleRpt', $visibleRpt);
    }


value00: app/Models/Test/TestDefSiteModel.php
value01:
elseif (TestValidationService::isTechnicalTest($typeCode)) {
        // Technical details are now flattened into the main row
        if ($row['DisciplineID']) {
            $discipline = $db->table('discipline')->where('DisciplineID', $row['DisciplineID'])->get()->getRowArray();
            $row['DisciplineName'] = $discipline['DisciplineName'] ?? null;
        }
        if ($row['DepartmentID']) {
            $department = $db->table('department')->where('DepartmentID', $row['DepartmentID'])->get()->getRowArray();
            $row['DepartmentName'] = $department['DepartmentName'] ?? null;
        }
        
        $testMapModel = new \App\Models\Test\TestMapModel();
        $row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
    }


value00: app/Models/Test/TestDefSiteModel.php
value01:
  /**
   * Get technical test with discipline and department relations
   *
   * @param int $testSiteID
   * @return array
   */
  public function getTestTechWithRelations($testSiteID)
  {
      return $this->db->table('testdefsite')
          ->select('testdefsite.*, d.DisciplineName, dept.DepartmentName')
          ->join('discipline d', 'd.DisciplineID=testdefsite.DisciplineID', 'left')
          ->join('department dept', 'dept.DepartmentID=testdefsite.DepartmentID', 'left')
          ->where('testdefsite.TestSiteID', $testSiteID)
          ->where('testdefsite.EndDate IS NULL')
          ->get()
          ->getResultArray();
  }
}


value00: app/Models/Test/TestMapDetailModel.php
value01:
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField = "EndDate";

  /**
   * Get all details for a test map
   */
  public function getDetailsByTestMap($testMapID) {
    return $this->where('TestMapID', $testMapID)
        ->where('EndDate IS NULL')
        ->findAll();
  }

  /**
   * Get test map detail by host test code
   */
  public function getDetailsByHostCode($hostTestCode) {
    return $this->where('HostTestCode', $hostTestCode)
        ->where('EndDate IS NULL')
        ->findAll();
  }

  /**
   * Get test map detail by client test code
   */


value00: app/Models/Test/TestMapDetailModel.php
value01:
  /**
   * Get test map detail by client test code
   */
  public function getDetailsByClientCode($clientTestCode) {
    return $this->where('ClientTestCode', $clientTestCode)
        ->where('EndDate IS NULL')
        ->findAll();
  }


  /**
   * Disable all details for a test map
   *
   * @param int $testMapID
   * @return void
   */
  public function disableByTestMapID($testMapID)
  {
      $this->where('TestMapID', $testMapID)
          ->where('EndDate', null)
          ->set('EndDate', date('Y-m-d H:i:s'))
          ->update();
  }
}


value00: app/Models/Test/TestDefSiteModel.php
value01:
  public function getTest($TestSiteID) {
    $db = \Config\Database::connect();
    
    $row = $this->select("testdefsite.*")
      ->where("testdefsite.TestSiteID", $TestSiteID)
      ->find($TestSiteID);
    
    if (!$row) return null;

    $row = ValueSet::transformLabels([$row], [
      'TestType' => 'test_type',
    ])[0];

    $typeCode = $row['TestType'] ?? '';


value00: app/Traits/ResponseTrait.php
value01:
<?php

namespace App\Traits;

use CodeIgniter\API\ResponseTrait as BaseResponseTrait;
use CodeIgniter\HTTP\ResponseInterface;

trait ResponseTrait
{
    use BaseResponseTrait {
        respond as baseRespond;
    }

    /**
     * Send a response with automatic conversion of empty strings to null
     * 
     * @param mixed $data
     * @param int $status
     * @param string $message
     * @return ResponseInterface
     */
    public function respond($data = null, int $status = 200, string $message = '')


value00: app/Traits/ResponseTrait.php
value01:
    public function respond($data = null, int $status = 200, string $message = '')
    {
        // Convert empty strings to null in the data
        if ($data !== null && (is_array($data) || is_object($data))) {
            $data = $this->convertEmptyStringsToNull($data);
        }
        
        return $this->baseRespond($data, $status, $message);
    }

    /**
     * Recursively convert empty strings to null in arrays or objects
     * 
     * @param mixed $data The data to process (array, object, or scalar)
     * @return mixed The processed data with empty strings converted to null
     */
    private function convertEmptyStringsToNull($data)
    {


value00: app/Traits/ResponseTrait.php
value01:
        if (is_array($data)) {
            foreach ($data as $key => $value) {
                if (is_array($value)) {
                    $data[$key] = $this->convertEmptyStringsToNull($value);
                } elseif (is_object($value)) {
                    $data[$key] = $this->convertEmptyStringsToNull($value);
                } elseif ($value === '') {
                    $data[$key] = null;
                }
            }
        }


value00: app/Models/Test/TestDefSiteModel.php
value01:
    if ($visibleRpt !== null) {
        $builder->where('testdefsite.VisibleRpt', $visibleRpt);
    }
    
    if ($search) {
        $builder->groupStart()
            ->like('testdefsite.TestSiteCode', $search)
            ->orLike('testdefsite.TestSiteName', $search)
            ->groupEnd();
    }
    
    // Get total count before pagination
    $totalBuilder = clone $builder;
    $total = $totalBuilder->countAllResults();


value00: app/Models/Test/TestDefSiteModel.php
value01:
  /**
   * Get test by ID with ValueSet transformation
   *
   * @param int $id
   * @return array|null
   */
  public function getTestById($id)
  {
      $row = $this->select('testdefsite.*')
          ->where('testdefsite.TestSiteID', $id)
          ->find($id);
      
      if (!$row) {
          return null;
      }
      
      $rows = ValueSet::transformLabels([$row], [
          'TestType' => 'test_type',
      ]);
      
      return $rows[0];
  }

  /**
   * Get technical test with discipline and department relations
   *
   * @param int $testSiteID
   * @return array
   */


value00: app/Models/Test/TestDefSiteModel.php
value01:
    if (!empty($filters['TestSiteCode'])) {
      $builder->like('testdefsite.TestSiteCode', $filters['TestSiteCode']);
    }

    if (!empty($filters['search'])) {
      $builder->groupStart()
        ->like('testdefsite.TestSiteCode', $filters['search'])
        ->orLike('testdefsite.TestSiteName', $filters['search'])
        ->groupEnd();
    }

    return $builder->orderBy('testdefsite.SeqScr', 'ASC')->get()->getResultArray();
  }


value00: app/Models/Test/TestDefSiteModel.php
value01:
  /**
   * Get reference table name
   *
   * @param string $resultType
   * @param string $refType
   * @return string|null
   */
  public function getReferenceTable(string $resultType, string $refType): ?string
  {
      return TestValidationService::getReferenceTable($resultType, $refType);
  }

  /**
   * Get test by ID with ValueSet transformation
   *
   * @param int $id
   * @return array|null
   */


value00: app/Models/Test/TestDefSiteModel.php
value01:
  protected $updatedField = 'StartDate';
  protected $useSoftDeletes = true;
  protected $deletedField = "EndDate";

  public function getTests($siteId = null, $testType = null, $visibleScr = null, $visibleRpt = null, $search = null, $page = 1, $perPage = 20)


value00: app/Models/Test/TestMapDetailModel.php
value01:
<?php

namespace App\Models\Test;

use App\Models\BaseModel;

class TestMapDetailModel extends BaseModel {
  protected $table      = 'testmapdetail';
  protected $primaryKey = 'TestMapDetailID';
  protected $allowedFields = [
    'TestMapID',
    'HostTestCode',
    'HostTestName',
    'ConDefID',
    'ClientTestCode',
    'ClientTestName',
    'CreateDate',
    'EndDate'
  ];
  
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';


value00: app/Models/Test/TestDefSiteModel.php
value01:
<?php

namespace App\Models\Test;

use App\Models\BaseModel;
use App\Libraries\ValueSet;
use App\Libraries\TestValidationService;

class TestDefSiteModel extends BaseModel {
  protected $table      = 'testdefsite';
  protected $primaryKey = 'TestSiteID';


value00: app/Traits/ResponseTrait.php
value01:
elseif (is_object($data)) {
            foreach ($data as $key => $value) {
                if (is_array($value)) {
                    $data->$key = $this->convertEmptyStringsToNull($value);
                } elseif (is_object($value)) {
                    $data->$key = $this->convertEmptyStringsToNull($value);
                } elseif ($value === '') {
                    $data->$key = null;
                }
            }
        }
        return $data;
    }
}


value00: app/Models/Test/TestDefSiteModel.php
value01:
    if (!empty($filters['SiteID'])) {
      $builder->where('testdefsite.SiteID', $filters['SiteID']);
    }

    if (!empty($filters['TestType'])) {
      $builder->where('testdefsite.TestType', $filters['TestType']);
    }

    if (isset($filters['VisibleScr'])) {
      $builder->where('testdefsite.VisibleScr', $filters['VisibleScr']);
    }

    if (isset($filters['VisibleRpt'])) {
      $builder->where('testdefsite.VisibleRpt', $filters['VisibleRpt']);
    }

    if (!empty($filters['TestSiteName'])) {
      $builder->like('testdefsite.TestSiteName', $filters['TestSiteName']);
    }

    if (!empty($filters['TestSiteCode'])) {
      $builder->like('testdefsite.TestSiteCode', $filters['TestSiteCode']);
    }


value00: app/Models/Test/TestDefSiteModel.php
value01:
      ->join('testdefcal cal', 'cal.TestSiteID = testdefsite.TestSiteID AND cal.EndDate IS NULL', 'left')
      ->join('discipline d', 'd.DisciplineID = COALESCE(testdefsite.DisciplineID, cal.DisciplineID)', 'left')
      ->join('department dept', 'dept.DepartmentID = COALESCE(testdefsite.DepartmentID, cal.DepartmentID)', 'left')
      ->where('testdefsite.EndDate IS NULL');


value00: app/Models/Test/TestDefSiteModel.php
value01:
elseif (TestValidationService::isGroup($typeCode)) {
        $testDefGrpModel = new \App\Models\Test\TestDefGrpModel();
        $row['testdefgrp'] = $testDefGrpModel->getGroupMembers($TestSiteID);
        
        $testMapModel = new \App\Models\Test\TestMapModel();
        $row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
        
    } elseif (TestValidationService::isTitle($typeCode)) {
        $testMapModel = new \App\Models\Test\TestMapModel();
        $row['testmap'] = $testMapModel->getMappingsByTestCode($row['TestSiteCode']);
        
    }


value00: app/Models/Test/TestDefGrpModel.php
value01:
  public function getGroupsContainingTest($memberTestSiteID) {
    return $this->select('testdefgrp.*, t.TestSiteCode, t.TestSiteName')
        ->join('testdefsite t', 't.TestSiteID=testdefgrp.TestSiteID', 'left')
        ->where('testdefgrp.Member', $memberTestSiteID)
        ->where('testdefgrp.EndDate IS NULL')
        ->findAll();
  }


  /**
   * Disable group members by TestSiteID
   */
  public function disableByTestSiteID($testSiteID) {
    $this->db->table('testdefgrp')
      ->where('TestSiteID', $testSiteID)
      ->update(['EndDate' => date('Y-m-d H:i:s')]);
  }
}


value00: app/Models/Test/TestDefGrpModel.php
value01:
<?php

namespace App\Models\Test;

use App\Models\BaseModel;
use App\Libraries\ValueSet;

class TestDefGrpModel extends BaseModel {
  protected $table      = 'testdefgrp';
  protected $primaryKey = 'TestGrpID';
  protected $allowedFields = [
    'TestSiteID',
    'Member',
    'SeqScr',
    'SeqRpt',
    'CreateDate', 
    'EndDate'
  ]; 
   
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField = "EndDate";


value00: app/Models/ValueSet/ValueSetModel.php
value01:
<?php

namespace App\Models\ValueSet;

use App\Models\BaseModel;

class ValueSetModel extends BaseModel {
  protected $table      = 'valueset';
  protected $primaryKey = 'VID';
  protected $allowedFields = ['SiteID', 'VSetID', 'VOrder', 'VValue', 'VDesc', 'VCategory', 'CreateDate', 'EndDate']; 
	
	protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
	protected $useSoftDeletes = true;


value00: app/Models/User/UserModel.php
value01:
    /**
     * Search users by name, username, or email
     */
    public function search($term)
    {
        return $this->where('DelDate', null)
            ->groupStart()
                ->like('Username', $term)
                ->orLike('Email', $term)
                ->orLike('Name', $term)
            ->groupEnd()
            ->findAll();
    }

    /**
     * Get users by role
     */
    public function getByRole($role)
    {
        return $this->where('Role', $role)
            ->where('DelDate', null)
            ->where('IsActive', true)
            ->findAll();
    }

    /**
     * Get users by department
     */


value00: app/Models/User/UserModel.php
value01:
    /**
     * Get users by department
     */
    public function getByDepartment($department)
    {
        return $this->where('Department', $department)
            ->where('DelDate', null)
            ->where('IsActive', true)
            ->findAll();
    }

    /**
     * Soft delete user
     */
    public function softDelete($id)
    {
        return $this->update($id, [
            'DelDate' => date('Y-m-d H:i:s'),
            'IsActive' => false
        ]);
    }

    /**
     * Restore soft-deleted user
     */
    public function restore($id)
    {
        return $this->update($id, [
            'DelDate' => null,
            'IsActive' => true
        ]);
    }
}


value00: app/Models/User/UserModel.php
value01:
<?php

namespace App\Models\User;

use CodeIgniter\Model;

/**
 * User Model
 * Handles database operations for users
 */
class UserModel extends Model
{
    protected $table = 'users';
    protected $primaryKey = 'UserID';

    // Allow all fields to be mass-assigned
    protected $allowedFields = [
        'Username',
        'Email',
        'Name',
        'Role',
        'Department',
        'IsActive',
        'CreatedAt',
        'UpdatedAt',
        'DelDate'
    ];

    // Use timestamps (disabled, we handle manually for consistency)
    protected $useTimestamps = false;

    // Validation rules


value00: app/Models/User/UserModel.php
value01:
    /**
     * Get active users only
     */
    public function getActive()
    {
        return $this->where('DelDate', null)
            ->where('IsActive', true)
            ->findAll();
    }

    /**
     * Find user by username
     */
    public function findByUsername($username)
    {
        return $this->where('Username', $username)
            ->where('DelDate', null)
            ->first();
    }

    /**
     * Find user by email
     */
    public function findByEmail($email)
    {
        return $this->where('Email', $email)
            ->where('DelDate', null)
            ->first();
    }

    /**
     * Search users by name, username, or email
     */


value00: app/Models/User/UserModel.php
value01:
    // Use timestamps (disabled, we handle manually for consistency)
    protected $useTimestamps = false;

    // Validation rules
    protected $validationRules = [
        'Username' => 'required|min_length[3]|max_length[50]',
        'Email' => 'required|valid_email|max_length[100]',
    ];

    protected $validationMessages = [
        'Username' => [
            'required' => 'Username is required',
            'min_length' => 'Username must be at least 3 characters',
            'max_length' => 'Username cannot exceed 50 characters',
        ],
        'Email' => [
            'required' => 'Email is required',
            'valid_email' => 'Please provide a valid email address',
            'max_length' => 'Email cannot exceed 100 characters',
        ],
    ];


value00: app/Models/Test/TestDefGrpModel.php
value01:
  protected $useSoftDeletes = true;
  protected $deletedField = "EndDate";

  public function getGroupMembers($testSiteID) {
    $db = \Config\Database::connect();
    
    $rows = $db->table('testdefgrp')
        ->select('t.TestSiteID as TestSiteID, t.TestSiteCode, t.TestSiteName, t.TestType, t.SeqScr, t. SeqRpt')
        ->join('testdefsite t', 't.TestSiteID=testdefgrp.Member', 'left')
        ->where('testdefgrp.TestSiteID', $testSiteID)
        ->where('testdefgrp.EndDate IS NULL')
        ->orderBy('t.SeqScr', 'ASC')
        ->orderBy('t.SeqRpt', 'ASC')
        ->orderBy('testdefgrp.TestGrpID', 'ASC')
        ->get()->getResultArray();
    
    $rows = ValueSet::transformLabels($rows, [
      'TestType' => 'test_type',
    ]);
    
    return $rows;
  }


value00: app/Models/ValueSet/ValueSetModel.php
value01:
	public function getValueSet($VID) {
		$rows = $this->select("valueset.*, valuesetdef.VSName")
			->join('valuesetdef', 'valuesetdef.VSetID = valueset.VSetID', 'LEFT')
			->find($VID);

		return $rows;
	}

  public function getValueSetByValueSetDef($VSetID) {
		$rows = $this->where('VSetID', (int) $VSetID)->findAll();
		return $rows;
	}
  

}


value00: app/Models/ValueSet/ValueSetModel.php
value01:
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
	protected $useSoftDeletes = true;
	protected $deletedField = 'EndDate';

	public function getValueSets($param = null, $VSetID = null) {
    $this->select("valueset.*, valuesetdef.VSName as VCategoryName")
      ->join('valuesetdef', 'valueset.VSetID = valuesetdef.VSetID', 'LEFT');

    if ($VSetID !== null) {
        $this->where('valueset.VSetID', $VSetID);
    }

    if ($param !== null) {
        $this->groupStart()
             ->like('valueset.VValue', $param, 'both')
             ->orLike('valueset.VDesc', $param, 'both')
             ->orLike('valuesetdef.VSName', $param, 'both')
             ->groupEnd();
    }

    return $this->findAll();
  }


value00: app/Models/Test/TestMapModel.php
value01:
          WHEN tm.HostType = ? THEN ws.WorkstationName
          WHEN tm.HostType = ? THEN el.InstrumentName
          ELSE tm.HostID
        END as HostName,
        tm.ClientType,
        tm.ClientID,
        CASE 
          WHEN tm.ClientType = ? THEN cs.SiteName
          WHEN tm.ClientType = ? THEN cws.WorkstationName
          WHEN tm.ClientType = ? THEN cel.InstrumentName


value00: app/Models/Test/TestMapModel.php
value01:
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
  protected $useSoftDeletes = true;
  protected $deletedField = "EndDate";

  /**
   * Get all test mappings with names
   */
  public function getUniqueGroupings() {
    $db = $this->db;
    $types = $this->getEntityTypes();
    
    $siteType = $types['SITE'] ?? 'SITE';
    $wsType = $types['WST'] ?? 'WST';
    $instType = $types['INST'] ?? 'INST';
    
    $sql = "
      SELECT 
        tm.TestMapID,
        tm.HostType,
        tm.HostID,
        CASE 
          WHEN tm.HostType = ? THEN s.SiteName
          WHEN tm.HostType = ? THEN ws.WorkstationName
          WHEN tm.HostType = ? THEN el.InstrumentName


value00: app/Models/Test/TestMapModel.php
value01:
<?php

namespace App\Models\Test;

use App\Models\BaseModel;

class TestMapModel extends BaseModel {

  private function getEntityTypes(): array {
    $json = file_get_contents(APPPATH . 'Libraries/Data/entity_type.json');
    $data = json_decode($json, true);
    $types = [];
    foreach ($data['values'] as $item) {
      $types[$item['key']] = $item['key'];
    }
    return $types;
  }
  protected $table      = 'testmap';
  protected $primaryKey = 'TestMapID';
  protected $allowedFields = [
    'HostType',
    'HostID',
    'ClientType',
    'ClientID',
    'CreateDate',
    'EndDate'
  ];
  
  protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';


value00: app/Models/Test/TestMapModel.php
value01:
          WHEN tm.ClientType = ? THEN cws.WorkstationName
          WHEN tm.ClientType = ? THEN cel.InstrumentName
          ELSE tm.ClientID
        END as ClientName
      FROM testmap tm
      LEFT JOIN site s ON tm.HostType = ? AND s.SiteID = tm.HostID
      LEFT JOIN workstation ws ON tm.HostType = ? AND ws.WorkstationID = tm.HostID
      LEFT JOIN equipmentlist el ON tm.HostType = ? AND el.EID = tm.HostID
      LEFT JOIN site cs ON tm.ClientType = ? AND cs.SiteID = tm.ClientID
      LEFT JOIN workstation cws ON tm.ClientType = ? AND cws.WorkstationID = tm.ClientID
      LEFT JOIN equipmentlist cel ON tm.ClientType = ? AND cel.EID = tm.ClientID
      WHERE tm.EndDate IS NULL


value00: app/Models/Test/TestMapModel.php
value01:
  public function getMappingsWithDetailsByTestCode($testCode) {
    return $this->select('testmap.*, testmapdetail.*')
        ->join('testmapdetail', 'testmapdetail.TestMapID = testmap.TestMapID', 'inner')
        ->groupStart()
            ->where('testmapdetail.HostTestCode', $testCode)
            ->orWhere('testmapdetail.ClientTestCode', $testCode)
        ->groupEnd()
        ->where('testmap.EndDate IS NULL')
        ->where('testmapdetail.EndDate IS NULL')
        ->findAll();
  }
}


value00: app/Models/Test/TestMapModel.php
value01:
    ";
    
    return $db->query($sql, [
      $siteType, $wsType, $instType,
      $siteType, $wsType, $instType,
      $siteType, $wsType, $instType,
      $siteType, $wsType, $instType
    ])->getResultArray();
  }

  public function getMappingsByTestCode($testCode) {
    return $this->select('testmap.*')
        ->join('testmapdetail', 'testmapdetail.TestMapID = testmap.TestMapID', 'inner')
        ->groupStart()
            ->where('testmapdetail.HostTestCode', $testCode)
            ->orWhere('testmapdetail.ClientTestCode', $testCode)
        ->groupEnd()
        ->where('testmap.EndDate IS NULL')
        ->where('testmapdetail.EndDate IS NULL')
        ->groupBy('testmap.TestMapID')
        ->findAll();
  }


value00: app/Views/errors/cli/error_404.php
value01:
<?php

use CodeIgniter\CLI\CLI;

CLI::error('ERROR: ' . $code);
CLI::write($message);
CLI::newLine();


value00: app/Services/AuditService.php
value01:
<?php

namespace App\Services;

use CodeIgniter\Database\BaseConnection;

class AuditService {
  protected BaseConnection $db;

  public function __construct() {
    $this->db = \Config\Database::connect();
  }

    public static function logData(
    string $operation,
    string $entityType,
    string $entityId,
    ?string $tableName = null,
    ?string $fieldName = null,
    ?array $previousValue = null,
    ?array $newValue = null,
    ?string $reason = null,
    ?array $context = null
  ): void {
    self::log('data_audit_log', [
      'operation' => $operation,
      'entity_type' => $entityType,
      'entity_id' => $entityId,
      'table_name' => $tableName,
      'field_name' => $fieldName


value00: app/Services/AuditService.php
value01:
      'ip_address' => self::getIpAddress(),
      'port' => $resourceDetails['port'] ?? null,
      'user_id' => 'SYSTEM',
      'reason' => null,
      'context' => self::normalizeAuditValue($context),
      'created_at' => date('Y-m-d H:i:s')
    ]);
  }

  public static function logSecurity(
    string $operation,
    string $entityType,
    string $entityId,
    string $securityClass,
    ?string $eventType = 'SUCCESS',
    ?string $resourcePath = null,
    ?array $previousValue = null,
    ?array $newValue = null,
    ?string $reason = null,
    ?array $context = null
  ): void {
    self::log('security_audit_log', [
      'operation' => $operation,
      'entity_type' => $entityType,
      'entity_id' => $entityId,
      'security_class' => $securityClass


value00: app/Services/AuditService.php
value01:
      'pc_name' => self::getPcName(),
      'ip_address' => self::getIpAddress(),
      'user_id' => self::getUserId(),
      'reason' => $reason,
      'context' => self::normalizeAuditValue($context),
      'created_at' => date('Y-m-d H:i:s')
    ]);
  }

  public static function logService(
    string $operation,
    string $entityType,
    string $entityId,
    string $serviceClass,
    ?string $resourceType = null,
    ?array $resourceDetails = null,
    ?array $previousValue = null,
    ?array $newValue = null,
    ?string $serviceName = null,
    ?array $context = null
  ): void {
    self::log('service_audit_log', [
      'operation' => $operation,
      'entity_type' => $entityType,
      'entity_id' => $entityId,
      'service_class' => $serviceClass


value00: app/Services/AuditService.php
value01:
,
      'session_id' => self::getSessionId() ?: 'service_session',
      'event_type' => strtoupper($serviceClass) . '_' . strtoupper($operation),
      'site_id' => self::getSiteId(),
      'workstation_id' => self::getWorkstationId(),
      'pc_name' => self::getPcName(),
      'ip_address' => self::getIpAddress(),
      'port' => $resourceDetails['port'] ?? null,
      'user_id' => 'SYSTEM',
      'reason' => null


value00: app/Services/RuleEngineService.php
value01:
        $internalOID = (int) $order['InternalOID'];
        $testSiteID = $context['testSiteID'] ?? null;

        if ($testSiteID === null && isset($order['TestSiteID'])) {
            $testSiteID = is_numeric($order['TestSiteID']) ? (int) $order['TestSiteID'] : null;
        }

        $testCode = $action['testCode'] ?? null;
        if ($testCode !== null) {
            $resolvedId = $this->resolveTestSiteIdByCode($testCode);
            if ($resolvedId === null) {
                throw new \Exception('SET_RESULT unknown test code: ' . $testCode);
            }
            $testSiteID = $resolvedId;
        }

        if ($testSiteID === null) {
            throw new \Exception('SET_RESULT requires testSiteID');
        }


value00: app/Services/RuleEngineService.php
value01:
    /**
     * Execute ADD_COMMENT action - Add a comment to the order
     */
    protected function executeAddComment(array $action, array $context): void
    {
        $order = $context['order'] ?? null;
        if (!is_array($order) || empty($order['InternalOID'])) {
            throw new \Exception('ADD_COMMENT requires context.order.InternalOID');
        }

        $internalOID = (int) $order['InternalOID'];
        $comment = $action['comment'] ?? null;

        if (empty($comment)) {
            throw new \Exception('ADD_COMMENT requires comment');
        }

        $db = \Config\Database::connect();

        // Insert comment into ordercom table


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Compile a single action
     */
    private function compileSingleAction(string $action): ?array
    {
        $action = trim($action);

        // nothing - no operation
        if (strcasecmp($action, 'nothing') === 0) {
            return [
                'type' => 'NO_OP',
                'value' => null,
            ];
        }

        // result_set(value) or result_set('CODE', value) [aliases: set_result]
        if (preg_match('/^(result_set|set_result)\s*\(\s*(.+)\s*\)$/is', $action, $m)) {
            $args = $this->splitTopLevel($m[2], ',', 2);
            if (empty($args)) {
                throw new \InvalidArgumentException('result_set requires a value expression');
            }

            $testCode = null;


value00: app/Services/AuditService.php
value01:
[
      'operation' => $operation,
      'entity_type' => $entityType,
      'entity_id' => $entityId,
      'service_class' => $serviceClass,
      'resource_type' => $resourceType,
      'resource_details' => self::normalizeAuditValue($resourceDetails),
      'previous_value' => self::normalizeAuditValue($previousValue),
      'new_value' => self::normalizeAuditValue($newValue),
      'mechanism' => 'AUTOMATIC',
      'application_id' => $serviceName ?? 'SYSTEM-SERVICE',
      'service_name' => $serviceName,
      'session_id' => self::getSessionId() ?: 'service_session',
      'event_type' => strtoupper($serviceClass) . '_' . strtoupper($operation)


value00: app/Services/AuditService.php
value01:
,
      'entity_type' => $entityType,
      'entity_id' => $entityId,
      'error_code' => $errorCode,
      'error_message' => $errorMessage,
      'error_details' => self::normalizeAuditValue($errorDetails),
      'previous_value' => self::normalizeAuditValue($previousValue),
      'new_value' => self::normalizeAuditValue($newValue),
      'mechanism' => 'AUTOMATIC',
      'application_id' => 'CLQMS-WEB',
      'web_page' => self::getUri(),
      'session_id' => self::getSessionId() ?: 'system',
      'event_type' => $eventType,
      'site_id' => self::getSiteId(),
      'workstation_id' => self::getWorkstationId(),
      'pc_name' => self::getPcName(),
      'ip_address' => self::getIpAddress(),
      'user_id' => self::getUserId() ?? 'SYSTEM'


value00: app/Views/errors/cli/production.php
value01:
<?php

// On the CLI, we still want errors in productions
// so just use the exception template.
include __DIR__ . '/error_exception.php';


value00: app/Services/RuleEngineService.php
value01:
<?php

namespace App\Services;

use App\Models\Rule\RuleDefModel;
use App\Models\Test\TestDefSiteModel;

class RuleEngineService
{
    protected RuleDefModel $ruleDefModel;
    protected RuleExpressionService $expr;

    public function __construct()
    {
        $this->ruleDefModel = new RuleDefModel();
        $this->expr = new RuleExpressionService();
    }

    /**
     * Run rules for an event.
     *
     * Expected context keys:
     * - order: array (must include InternalOID)
     * - patient: array (optional, includes Sex, Age, etc.)
     * - tests: array (optional, patres rows)
     *
     * @param string $eventCode The event that triggered rule execution
     * @param array $context Context data for rule evaluation
     * @return void
     */


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Compile multi-action string (separated by :)
     * Returns array of action objects
     */
    private function compileMultiAction(string $actionStr): array
    {
        $actionStr = trim($actionStr);

        // Split by : separator (top-level only)
        $actions = [];

        $parts = $this->splitTopLevel($actionStr, ':');

        foreach ($parts as $part) {
            $action = $this->compileSingleAction(trim($part));
            if ($action !== null) {
                $actions[] = $action;
            }
        }

        return $actions;
    }

    /**
     * Compile a single action
     */


value00: app/Services/RuleExpressionService.php
value01:
            if ($type === 'op' && $val === '||') {
                $out .= ' or ';
                continue;
            }

            if ($type === 'ident') {
                $lower = strtolower($val);

                // sex('M')
                if ($lower === 'sex') {
                    $arg = $this->parseSingleStringArg($tokens, $i + 1);
                    if ($arg !== null) {
                        $out .= 'patient["Sex"] == "' . addslashes($arg['value']) . '"';
                        $i = $arg['endIndex'];
                        continue;
                    }
                }

                // priority('S')
                if ($lower === 'priority') {
                    $arg = $this->parseSingleStringArg($tokens, $i + 1);


value00: app/Services/AuditService.php
value01:
  private static function getUri(): ?string {
    return $_SERVER['REQUEST_URI'] ?? null;
  }

  private static function getSessionId(): ?string {
    $session = session();
    return $session->get('session_id');
  }

  private static function getSiteId(): ?string {
    $session = session();
    return $session->get('site_id');
  }

  private static function getWorkstationId(): ?string {
    $session = session();
    return $session->get('workstation_id');
  }

  private static function getPcName(): ?string {
    return gethostname();
  }

  private static function getIpAddress(): ?string {
    return $_SERVER['REMOTE_ADDR'] ?? null;
  }

  private static function getUserId(): ?string {
    $session = session();
    return $session->get('user_id');
  }
}


value00: app/Models/ValueSet/ValueSetDefModel.php
value01:
  public function getValueSetDefs($param = null) {
    // Get item counts subquery
    $itemCounts = $this->db->table('valueset')
        ->select('VSetID, COUNT(*) as ItemCount')
        ->where('EndDate IS NULL')
        ->groupBy('VSetID');
    
    $builder = $this->db->table('valuesetdef vd');
    $builder->select('vd.*, COALESCE(ic.ItemCount, 0) as ItemCount');
    $builder->join("({$itemCounts->getCompiledSelect()}) ic", 'vd.VSetID = ic.VSetID', 'LEFT');
    $builder->where('vd.EndDate IS NULL');
    
    if ($param !== null) {
      $builder->groupStart()
          ->like('vd.VSName', $param, 'both')
          ->orLike('vd.VSDesc', $param, 'both')
          ->groupEnd();
    }
    
    $rows = $builder->get()->getResultArray();
    return $rows;
  }
  
  
}


value00: app/Services/RuleExpressionService.php
value01:
     * - comment_insert('text')
     * - nothing
     * - Multi-action: result_set(0.5):test_insert('CODE'):comment_insert('text')
     *
     * Backward compatible aliases:
     * - set_result -> result_set
     * - insert -> test_insert
     * - add_comment -> comment_insert
     *
     * @param string $expr The raw DSL expression
     * @return array The compiled structure with actions
     * @throws \InvalidArgumentException If DSL is invalid
     */


value00: app/Services/RuleEngineService.php
value01:
        if ($patres) {
            // Update existing result
            $ok = $db->table('patres')
                ->where('OrderID', $internalOID)
                ->where('TestSiteID', $testSiteID)
                ->where('DelDate', null)
                ->update(['Result' => $value]);
        } else {
            // Insert new result row
            $ok = $db->table('patres')->insert([
                'OrderID' => $internalOID,
                'TestSiteID' => $testSiteID,
                'TestSiteCode' => $testSiteCode,
                'Result' => $value,
                'CreateDate' => date('Y-m-d H:i:s'),
            ]);
        }

        if ($ok === false) {
            throw new \Exception('SET_RESULT update/insert failed');
        }
    }


value00: app/Services/AuditService.php
value01:
,
      'entity_type' => $entityType,
      'entity_id' => $entityId,
      'table_name' => $tableName,
      'field_name' => $fieldName,
      'previous_value' => self::normalizeAuditValue($previousValue),
      'new_value' => self::normalizeAuditValue($newValue),
      'mechanism' => 'MANUAL',
      'application_id' => 'CLQMS-WEB',
      'web_page' => self::getUri(),
      'session_id' => self::getSessionId(),
      'event_type' => strtoupper($entityType) . '_' . strtoupper($operation),
      'site_id' => self::getSiteId(),
      'workstation_id' => self::getWorkstationId(),
      'pc_name' => self::getPcName(),
      'ip_address' => self::getIpAddress(),
      'user_id' => self::getUserId(),
      'reason' => $reason


value00: app/Services/RuleEngineService.php
value01:
    public function run(string $eventCode, array $context = []): void
    {
        $order = $context['order'] ?? null;
        $testSiteID = $context['testSiteID'] ?? null;

        if (is_array($order) && isset($order['TestSiteID']) && $testSiteID === null) {
            $testSiteID = is_numeric($order['TestSiteID']) ? (int) $order['TestSiteID'] : null;
        }

        $rules = $this->ruleDefModel->getActiveByEvent($eventCode, $testSiteID);
        if (empty($rules)) {
            return;
        }

        foreach ($rules as $rule) {
            $rid = (int) ($rule['RuleID'] ?? 0);
            if ($rid === 0) {
                continue;
            }

            try {
                // Rules must have compiled expressions


value00: app/Services/AuditService.php
value01:
[
      'operation' => $operation,
      'entity_type' => $entityType,
      'entity_id' => $entityId,
      'security_class' => $securityClass,
      'resource_path' => $resourcePath,
      'previous_value' => self::normalizeAuditValue($previousValue),
      'new_value' => self::normalizeAuditValue($newValue),
      'mechanism' => 'MANUAL',
      'application_id' => 'CLQMS-WEB',
      'web_page' => self::getUri(),
      'session_id' => self::getSessionId(),
      'event_type' => $eventType,
      'site_id' => self::getSiteId(),
      'workstation_id' => self::getWorkstationId(),
      'pc_name' => self::getPcName(),
      'ip_address' => self::getIpAddress(),
      'user_id' => self::getUserId() ?? 'UNKNOWN',
      'reason' => $reason


value00: app/Services/RuleExpressionService.php
value01:
        $parts = $this->splitTopLevel($inner, ';', 3);
        if (count($parts) !== 3) {
            // Fallback: legacy ternary syntax
            $ternary = $this->convertTopLevelTernaryToSemicolon($inner);
            if ($ternary !== null) {
                $parts = $this->splitTopLevel($ternary, ';', 3);
            }
        }

        if (count($parts) !== 3) {
            throw new \InvalidArgumentException('Invalid DSL: expected exactly 3 parts "condition; then; else"');
        }

        $condition = trim($parts[0]);
        $thenAction = trim($parts[1]);
        $elseAction = trim($parts[2]);

        // Compile condition
        $compiledCondition = $this->compileCondition($condition);

        // Compile actions (supports multi-action with : separator)


value00: app/Services/RuleEngineService.php
value01:
    private function resolveTestSiteIdByCode(string $testSiteCode): ?int
    {
        try {
            $testDefSiteModel = new TestDefSiteModel();
            $row = $testDefSiteModel->where('TestSiteCode', $testSiteCode)->where('EndDate', null)->first();
            if (empty($row['TestSiteID'])) {
                return null;
            }

            return (int) $row['TestSiteID'];
        } catch (\Throwable $e) {
            return null;
        }
    }
}


value00: app/Services/RuleEngineService.php
value01:
        if ($testSiteID === null) {
            throw new \Exception('SET_RESULT requires testSiteID');
        }

        // Get the value
        if (isset($action['valueExpr']) && is_string($action['valueExpr'])) {
            $value = $this->expr->evaluate($action['valueExpr'], $context);
        } else {
            $value = $action['value'] ?? null;
        }

        $testSiteCode = $testCode ?? $this->resolveTestSiteCode($testSiteID);

        $db = \Config\Database::connect();

        // Check if patres row exists
        $patres = $db->table('patres')
            ->where('OrderID', $internalOID)
            ->where('TestSiteID', $testSiteID)
            ->where('DelDate', null)
            ->get()
            ->getRowArray();


value00: app/Services/AuditService.php
value01:
,
      'ip_address' => self::getIpAddress(),
      'user_id' => self::getUserId() ?? 'UNKNOWN',
      'reason' => $reason,
      'context' => self::normalizeAuditValue($context),
      'created_at' => date('Y-m-d H:i:s')
    ]);
  }

  public static function logError(
    string $entityType,
    string $entityId,
    string $errorCode,
    string $errorMessage,
    string $eventType,
    ?array $errorDetails = null,
    ?array $previousValue = null,
    ?array $newValue = null,
    ?string $reason = null,
    ?array $context = null
  ): void {
    self::log('error_audit_log', [
      'operation' => 'ERROR',
      'entity_type' => $entityType,
      'entity_id' => $entityId,
      'error_code' => $errorCode,
      'error_message' => $errorMessage


value00: app/Services/RuleExpressionService.php
value01:
    private function extractIfInner(string $expr): ?string
    {
        $expr = trim($expr);
        if (!preg_match('/^if\s*\(/i', $expr)) {
            return null;
        }

        $pos = stripos($expr, '(');
        if ($pos === false) {
            return null;
        }


value00: app/Models/ValueSet/ValueSetDefModel.php
value01:
<?php

namespace App\Models\ValueSet;

use App\Models\BaseModel;

class ValueSetDefModel extends BaseModel {
  protected $table      = 'valuesetdef';
  protected $primaryKey = 'VSetID';
  protected $allowedFields = ['SiteID', 'VSName', 'VSDesc', 'CreateDate', 'EndDate']; 
	
	protected $useTimestamps = true;
  protected $createdField = 'CreateDate';
  protected $updatedField = '';
	protected $useSoftDeletes = true;
	protected $deletedField = 'EndDate';

  public function getValueSetDefs($param = null)


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Build valueExpr string from array of actions (for backward compatibility)
     */
    private function buildValueExpr(array $actions): string
    {
        if (empty($actions)) {
            return 'null';
        }

        // Use first SET_RESULT action's value, or null


value00: app/Services/RuleEngineService.php
value01:
    /**
     * Execute INSERT_TEST action - Insert a new test into patres
     */
    protected function executeInsertTest(array $action, array $context): void
    {
        $order = $context['order'] ?? null;
        if (!is_array($order) || empty($order['InternalOID'])) {
            throw new \Exception('INSERT_TEST requires context.order.InternalOID');
        }

        $internalOID = (int) $order['InternalOID'];


value00: app/Services/RuleExpressionService.php
value01:
                if ($lower === 'priority') {
                    $arg = $this->parseSingleStringArg($tokens, $i + 1);
                    if ($arg !== null) {
                        $out .= 'order["Priority"] == "' . addslashes($arg['value']) . '"';
                        $i = $arg['endIndex'];
                        continue;
                    }
                }

                // requested('CODE')
                if ($lower === 'requested') {
                    $arg = $this->parseSingleStringArg($tokens, $i + 1);
                    if ($arg !== null) {
                        $out .= 'requested("' . addslashes($arg['value']) . '")';
                        $i = $arg['endIndex'];
                        continue;
                    }
                }


value00: app/Services/RuleExpressionService.php
value01:
            if (count($args) === 2) {
                $testCode = $this->unquoteStringArgument($args[0]);
                if ($testCode === null) {
                    throw new \InvalidArgumentException('result_set test code must be a quoted string');
                }
            }

            $value = trim($args[count($args) - 1]);

            // Check if it's a number
            if (is_numeric($value)) {
                return [
                    'type' => 'RESULT_SET',
                    'value' => strpos($value, '.') !== false ? (float) $value : (int) $value,
                    'valueExpr' => $value,
                    'testCode' => $testCode,
                ];
            }

            // Check if it's a quoted string (single or double quotes)


value00: app/Services/RuleExpressionService.php
value01:
<?php

namespace App\Services;

use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
use Symfony\Component\ExpressionLanguage\ParsedExpression;

class RuleExpressionService
{
    protected ExpressionLanguage $language;

    /** @var array<string, ParsedExpression> */
    protected array $parsedCache = [];

    public function __construct()
    {
        $this->language = new ExpressionLanguage();
    }

    public function evaluate(string $expr, array $context = [])


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Parse and split multi-rule expressions (comma-separated)
     * Returns array of individual rule expressions
     */
    public function parseMultiRule(string $expr): array
    {
        $expr = trim($expr);
        if ($expr === '') {
            return [];
        }

        $rules = [];
        $depth = 0;
        $current = '';
        $len = strlen($expr);


value00: app/Services/RuleExpressionService.php
value01:
    public function compile(string $expr): array
    {
        $expr = trim($expr);
        if ($expr === '') {
            return [];
        }

        $inner = $this->extractIfInner($expr);
        if ($inner === null) {
            // Allow callers to pass without if(...) wrapper
            if (substr_count($expr, ';') >= 2) {
                $inner = $expr;
            } else {
                throw new \InvalidArgumentException('Invalid DSL: expected "if(condition; then; else)" format');
            }
        }

        $parts = $this->splitTopLevel($inner, ';', 3);


value00: app/Services/RuleExpressionService.php
value01:
        $depth = 0;
        $current = '';
        $len = strlen($expr);

        for ($i = 0; $i < $len; $i++) {
            $char = $expr[$i];

            if ($char === '(') {
                $depth++;
            } elseif ($char === ')') {
                $depth--;
            }

            if ($char === ',' && $depth === 0) {
                $trimmed = trim($current);
                if ($trimmed !== '') {
                    $rules[] = $trimmed;
                }
                $current = '';
            } else {
                $current .= $char;
            }
        }

        // Add last rule
        $trimmed = trim($current);
        if ($trimmed !== '') {
            $rules[] = $trimmed;
        }

        return $rules;
    }


value00: app/Services/RuleEngineService.php
value01:
    /**
     * Check if a test was requested for the current order
     */
    private function isTestRequested(string $testCode, array $context): bool
    {
        $order = $context['order'] ?? null;
        if (!is_array($order) || empty($order['InternalOID'])) {
            return false;
        }

        $internalOID = (int) $order['InternalOID'];
        $db = \Config\Database::connect();

        // Query patres to check if test with given code exists


value00: app/Services/RuleEngineService.php
value01:
                // Rules must have compiled expressions
                $compiled = null;
                if (!empty($rule['ConditionExprCompiled'])) {
                    $compiled = json_decode($rule['ConditionExprCompiled'], true);
                }

                if (empty($compiled) || !is_array($compiled)) {
                    log_message('warning', 'Rule ' . $rid . ' has no compiled expression, skipping');
                    continue;
                }

                // Evaluate condition
                $conditionExpr = $compiled['conditionExpr'] ?? 'true';
                $matches = $this->evaluateCondition($conditionExpr, $context);


value00: app/Services/RuleExpressionService.php
value01:
        // comment_insert('text') [aliases: add_comment]
        if (preg_match('/^(comment_insert|add_comment)\s*\(\s*["\'](.+?)["\']\s*\)$/is', $action, $m)) {
            return [
                'type' => 'COMMENT_INSERT',
                'comment' => $m[2],
            ];
        }

        throw new \InvalidArgumentException('Unknown action: ' . $action);
    }


value00: app/Services/RuleExpressionService.php
value01:
        // test_insert('CODE') [aliases: insert]
        if (preg_match('/^(test_insert|insert)\s*\(\s*["\']([^"\']+)["\']\s*\)$/i', $action, $m)) {
            return [
                'type' => 'TEST_INSERT',
                'testCode' => $m[2],
            ];
        }

        // test_delete('CODE')
        if (preg_match('/^test_delete\s*\(\s*["\']([^"\']+)["\']\s*\)$/i', $action, $m)) {
            return [
                'type' => 'TEST_DELETE',
                'testCode' => $m[1],
            ];
        }

        // comment_insert('text') [aliases: add_comment]


value00: app/Services/RuleEngineService.php
value01:
        $db = \Config\Database::connect();

        // Insert comment into ordercom table
        $ok = $db->table('ordercom')->insert([
            'InternalOID' => $internalOID,
            'Comment' => $comment,
            'CreateDate' => date('Y-m-d H:i:s'),
        ]);

        if ($ok === false) {
            throw new \Exception('ADD_COMMENT insert failed');
        }
    }


value00: app/Services/RuleExpressionService.php
value01:
        if (empty($actions)) {
            return 'null';
        }

        // Use first SET_RESULT action's value, or null
        foreach ($actions as $action) {
            if (($action['type'] ?? '') === 'RESULT_SET' || ($action['type'] ?? '') === 'SET_RESULT') {
                if (isset($action['valueExpr'])) {
                    return $action['valueExpr'];
                }
                if (isset($action['value'])) {
                    if (is_string($action['value'])) {
                        return '"' . addslashes($action['value']) . '"';
                    }
                    return json_encode($action['value']);
                }
            }
        }

        return 'null';
    }


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Convert a top-level ternary (condition ? then : else) into semicolon form.
     */
    private function convertTopLevelTernaryToSemicolon(string $s): ?string
    {
        $len = strlen($s);
        $depth = 0;
        $inSingle = false;
        $inDouble = false;
        $qPos = null;
        $cPos = null;

        for ($i = 0; $i < $len; $i++) {
            $ch = $s[$i];
            if ($ch === '\\') {
                $i++;
                continue;
            }
            if (!$inDouble && $ch === "'") {
                $inSingle = !$inSingle;
                continue;
            }
            if (!$inSingle && $ch === '"') {
                $inDouble = !$inDouble;
                continue;
            }


value00: app/Services/RuleEngineService.php
value01:
        $db = \Config\Database::connect();

        // Query patres to check if test with given code exists
        $result = $db->table('patres')
            ->select('patres.*, testdefsite.TestSiteCode')
            ->join('testdefsite', 'testdefsite.TestSiteID = patres.TestSiteID', 'inner')
            ->where('patres.OrderID', $internalOID)
            ->where('testdefsite.TestSiteCode', $testCode)
            ->where('patres.DelDate', null)
            ->where('testdefsite.EndDate', null)
            ->get()
            ->getRow();

        return $result !== null;
    }


value00: app/Services/AuditService.php
value01:
      'pc_name' => self::getPcName(),
      'ip_address' => self::getIpAddress(),
      'user_id' => self::getUserId() ?? 'SYSTEM',
      'reason' => $reason,
      'context' => self::normalizeAuditValue($context),
      'created_at' => date('Y-m-d H:i:s')
    ]);
  }

  private static function log(string $table, array $data): void {
    $db = \Config\Database::connect();
    if (!$db->tableExists($table)) {
        return;
    }
    $db->table($table)->insert($data);
  }

  private static function normalizeAuditValue($value)
  {
      if ($value === null || is_scalar($value)) {
          return $value;
      }

      $json = json_encode($value, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
      return $json !== false ? $json : null;
  }


value00: app/Services/RuleExpressionService.php
value01:
            if ($ch === '"' || $ch === "'") {
                $quote = $ch;
                $buf = $quote;
                $i++;
                for (; $i < $len; $i++) {
                    $c = $s[$i];
                    $buf .= $c;
                    if ($c === '\\' && $i + 1 < $len) {
                        $buf .= $s[$i + 1];
                        $i++;
                        continue;
                    }
                    if ($c === $quote) {
                        break;
                    }
                }
                $tokens[] = ['type' => 'string', 'value' => $buf];
                continue;
            }


value00: app/Services/RuleExpressionService.php
value01:
            $ch = $s[$i];

            if ($ch === '\\') {
                $buf .= $ch;
                if ($i + 1 < $len) {
                    $buf .= $s[$i + 1];
                    $i++;
                }
                continue;
            }

            if (!$inDouble && $ch === "'") {
                $inSingle = !$inSingle;
                $buf .= $ch;
                continue;
            }
            if (!$inSingle && $ch === '"') {
                $inDouble = !$inDouble;
                $buf .= $ch;
                continue;
            }


value00: app/Services/RuleEngineService.php
value01:
            default:
                log_message('warning', 'Unknown action type: ' . $type);
                break;
        }
    }

    /**
     * Execute SET_RESULT action
     */
    protected function executeSetResult(array $action, array $context): void
    {
        $order = $context['order'] ?? null;
        if (!is_array($order) || empty($order['InternalOID'])) {
            throw new \Exception('SET_RESULT requires context.order.InternalOID');
        }

        $internalOID = (int) $order['InternalOID'];
        $testSiteID = $context['testSiteID'] ?? null;


value00: app/Services/RuleExpressionService.php
value01:
        // Compile actions (supports multi-action with : separator)
        $thenActions = $this->compileMultiAction($thenAction);
        $elseActions = $this->compileMultiAction($elseAction);

        // Build valueExpr for backward compatibility
        $thenValueExpr = $this->buildValueExpr($thenActions);
        $elseValueExpr = $this->buildValueExpr($elseActions);

        $valueExpr = "({$compiledCondition}) ? {$thenValueExpr} : {$elseValueExpr}";

        return [
            'conditionExpr' => $compiledCondition,
            'valueExpr' => $valueExpr,
            'then' => $thenActions,
            'else' => $elseActions,
        ];
    }


value00: app/Services/RuleExpressionService.php
value01:
            if ($inSingle || $inDouble) {
                continue;
            }
            if ($ch === '(') {
                $depth++;
                continue;
            }
            if ($ch === ')') {
                $depth = max(0, $depth - 1);
                continue;
            }
            if ($depth === 0 && $ch === '?' && $qPos === null) {
                $qPos = $i;
                continue;
            }
            if ($depth === 0 && $ch === ':' && $qPos !== null) {
                $cPos = $i;
                break;
            }
        }


value00: app/Services/RuleExpressionService.php
value01:
        $pos = stripos($expr, '(');
        if ($pos === false) {
            return null;
        }

        $start = $pos + 1;
        $end = $this->findMatchingParen($expr, $pos);
        if ($end === null) {
            throw new \InvalidArgumentException('Invalid DSL: unbalanced parentheses in if(...)');
        }

        // Only allow trailing whitespace after the closing paren
        if (trim(substr($expr, $end + 1)) !== '') {
            throw new \InvalidArgumentException('Invalid DSL: unexpected trailing characters after if(...)');
        }

        return trim(substr($expr, $start, $end - $start));
    }


value00: app/Services/RuleExpressionService.php
value01:
    private function findMatchingParen(string $s, int $openIndex): ?int
    {
        $len = strlen($s);
        $depth = 0;
        $inSingle = false;
        $inDouble = false;

        for ($i = $openIndex; $i < $len; $i++)


value00: app/Services/RuleEngineService.php
value01:
    /**
     * Execute an action based on its type
     */
    protected function executeAction(array $action, array $context): void
    {
        $type = strtoupper((string) ($action['type'] ?? ''));

        switch ($type) {
            case 'RESULT_SET':
            case 'SET_RESULT': // legacy
                $this->executeSetResult($action, $context);
                break;
            case 'TEST_INSERT':
            case 'INSERT_TEST': // legacy
                $this->executeInsertTest($action, $context);
                break;
            case 'TEST_DELETE':
                $this->executeDeleteTest($action, $context);
                break;
            case 'COMMENT_INSERT':


value00: app/Services/RuleExpressionService.php
value01:
; $i < $len; $i++) {
            $ch = $s[$i];

            if ($ch === '\\') {
                $i++;
                continue;
            }
            if (!$inDouble && $ch === "'") {
                $inSingle = !$inSingle;
                continue;
            }
            if (!$inSingle && $ch === '"') {
                $inDouble = !$inDouble;
                continue;
            }
            if ($inSingle || $inDouble) {
                continue;
            }

            if ($ch === '(') {
                $depth++;
                continue;
            }
            if ($ch === ')') {
                $depth--;
                if ($depth === 0) {
                    return $i;
                }
            }
        }
        return null;
    }


value00: app/Services/RuleExpressionService.php
value01:
            // Check if it's a quoted string (single or double quotes)
            if (preg_match('/^["\'](.+)["\']$/s', $value, $vm)) {
                return [
                    'type' => 'RESULT_SET',
                    'value' => $vm[1],
                    'valueExpr' => '"' . addslashes($vm[1]) . '"',
                    'testCode' => $testCode,
                ];
            }

            // Complex expression
            return [
                'type' => 'RESULT_SET',
                'valueExpr' => $value,
                'testCode' => $testCode,
            ];
        }


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Split string by a delimiter only when not inside quotes/parentheses.
     * If $limit is provided, splits into at most $limit parts.
     */
    private function splitTopLevel(string $s, string $delimiter, ?int $limit = null): array
    {
        $s = (string) $s;
        $len = strlen($s);
        $depth = 0;
        $inSingle = false;
        $inDouble = false;
        $parts = [];
        $buf = '';

        for ($i = 0; $i < $len; $i++) {
            $ch = $s[$i];

            if ($ch === '\\')


value00: app/Services/RuleEngineService.php
value01:
        if ($existing) {
            // Test already exists, skip
            return;
        }

        // Insert new test row
        $ok = $db->table('patres')->insert([
            'OrderID' => $internalOID,
            'TestSiteID' => $testSiteID,
            'TestSiteCode' => $testCode,
            'CreateDate' => date('Y-m-d H:i:s'),
        ]);

        if ($ok === false) {
            throw new \Exception('INSERT_TEST insert failed');
        }
    }


value00: app/Services/RuleEngineService.php
value01:
    private function evaluateCondition(string $conditionExpr, array $context): bool
    {
        // Handle requested() function(s) by querying database
        $conditionExpr = preg_replace_callback(
            '/requested\s*\(\s*["\']([^"\']+)["\']\s*\)/i',
            function (array $m) use ($context) {
                $testCode = $m[1] ?? '';
                if ($testCode === '') {
                    return 'false';
                }
                return $this->isTestRequested($testCode, $context) ? 'true' : 'false';
            },
            $conditionExpr
        );

        return $this->expr->evaluateBoolean($conditionExpr, $context);
    }

    /**
     * Check if a test was requested for the current order
     */


value00: app/Services/RuleExpressionService.php
value01:
            if (ctype_digit($ch)) {
                $buf = $ch;
                while ($i + 1 < $len && (ctype_digit($s[$i + 1]) || $s[$i + 1] === '.')) {
                    $buf .= $s[$i + 1];
                    $i++;
                }
                $tokens[] = ['type' => 'number', 'value' => $buf];
                continue;
            }

            if (ctype_alpha($ch) || $ch === '_') {
                $buf = $ch;
                while ($i + 1 < $len && (ctype_alnum($s[$i + 1]) || $s[$i + 1] === '_')) {
                    $buf .= $s[$i + 1];
                    $i++;
                }
                $tokens[] = ['type' => 'ident', 'value' => $buf];
                continue;
            }

            $tokens[] = ['type' => 'other', 'value' => $ch];
        }


value00: app/Services/RuleExpressionService.php
value01:
            if (!$inSingle && !$inDouble) {
                if ($ch === '(') {
                    $depth++;
                } elseif ($ch === ')') {
                    $depth = max(0, $depth - 1);
                }

                if ($depth === 0 && $ch === $delimiter) {
                    $parts[] = trim($buf);
                    $buf = '';
                    if ($limit !== null && count($parts) >= ($limit - 1)) {
                        $buf .= substr($s, $i + 1);
                        $i = $len;
                    }
                    continue;
                }
            }

            $buf .= $ch;
        }

        $parts[] = trim($buf);
        $parts = array_values(array_filter($parts, static fn ($p) => $p !== ''));
        return $parts;
    }


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Compile DSL condition to ExpressionLanguage expression
     * Supports: sex(), priority(), age, requested(), &&, ||, (), comparison operators
     */
    private function compileCondition(string $condition): string
    {
        $condition = trim($condition);
        if ($condition === '') {
            return 'true';
        }

        $tokens = $this->tokenize($condition);
        $out = '';
        $count = count($tokens);

        for ($i = 0; $i < $count; $i++) {
            $t = $tokens[$i];
            $type = $t['type'];


value00: app/Services/RuleEngineService.php
value01:
                $matches = $this->evaluateCondition($conditionExpr, $context);
                
                if (!$matches) {
                    // Execute else actions
                    $actions = $compiled['else'] ?? [];
                } else {
                    // Execute then actions
                    $actions = $compiled['then'] ?? [];
                }

                // Execute all actions
                foreach ($actions as $action) {
                    $this->executeAction($action, $context);
                }


value00: app/Services/RuleExpressionService.php
value01:
    private function unquoteStringArgument(string $value): ?string
    {
        $value = trim($value);
        if ($value === '') {
            return null;
        }

        $quote = $value[0];
        if ($quote !== '"' && $quote !== "'") {
            return null;
        }

        if (substr($value, -1) !== $quote) {
            return null;
        }

        $content = substr($value, 1, -1);
        return stripcslashes($content);
    }
}


value00: app/Services/RuleExpressionService.php
value01:
        if ($qPos === null || $cPos === null) {
            return null;
        }

        $cond = trim(substr($s, 0, $qPos));
        $then = trim(substr($s, $qPos + 1, $cPos - $qPos - 1));
        $else = trim(substr($s, $cPos + 1));

        if ($cond === '' || $then === '' || $else === '') {
            return null;
        }

        return $cond . '; ' . $then . '; ' . $else;
    }


value00: app/Services/RuleEngineService.php
value01:
                foreach ($actions as $action) {
                    $this->executeAction($action, $context);
                }
            } catch (\Throwable $e) {
                log_message('error', 'Rule engine error (RuleID=' . $rid . '): ' . $e->getMessage());
                continue;
            }
        }
    }

    /**
     * Evaluate a condition expression
     * Handles special functions like requested() by querying the database
     */


value00: app/Services/RuleExpressionService.php
value01:
    public function evaluate(string $expr, array $context = [])
    {
        $expr = trim($expr);
        if ($expr === '') {
            return null;
        }

        $names = array_keys($context);
        sort($names);
        $cacheKey = md5($expr . '|' . implode(',', $names));

        if (!isset($this->parsedCache[$cacheKey])) {
            $this->parsedCache[$cacheKey] = $this->language->parse($expr, $names);
        }

        return $this->language->evaluate($this->parsedCache[$cacheKey], $context);
    }

    public function evaluateBoolean(?string $expr, array $context = []): bool
    {
        if ($expr === null || trim($expr) === '') {
            return true;
        }

        return (bool) $this->evaluate($expr, $context);
    }


value00: app/Services/RuleExpressionService.php
value01:
                // age >= 18
                if ($lower === 'age') {
                    $op = $tokens[$i + 1] ?? null;
                    $num = $tokens[$i + 2] ?? null;
                    if (($op['type'] ?? '') === 'op' && in_array($op['value'], ['<', '>', '<=', '>=', '==', '!='], true) && ($num['type'] ?? '') === 'number') {
                        $out .= 'age ' . $op['value'] . ' ' . $num['value'];
                        $i += 2;
                        continue;
                    }
                }
            }

            $out .= $val;
        }

        return trim($out);
    }

    /**
     * Compile multi-action string (separated by :)
     * Returns array of action objects
     */


value00: app/Services/RuleEngineService.php
value01:
    private function resolveTestSiteCode(int $testSiteID): ?string
    {
        try {
            $testDefSiteModel = new TestDefSiteModel();
            $row = $testDefSiteModel->where('TestSiteID', $testSiteID)->where('EndDate', null)->first();
            return $row['TestSiteCode'] ?? null;
        } catch (\Throwable $e) {
            return null;
        }
    }


value00: app/Services/RuleExpressionService.php
value01:
        return $tokens;
    }

    /**
     * Parse a single quoted string argument from tokens starting at $start.
     * Expects: '(' string ')'. Returns ['value' => string, 'endIndex' => int] or null.
     */


value00: app/Services/RuleEngineService.php
value01:
        if (!$testSite || empty($testSite['TestSiteID'])) {
            // Unknown test code: no-op
            return;
        }

        $testSiteID = (int) $testSite['TestSiteID'];
        $db = \Config\Database::connect();

        // Soft delete matching patres row(s)
        $db->table('patres')
            ->where('OrderID', $internalOID)
            ->where('TestSiteID', $testSiteID)
            ->where('DelDate', null)
            ->update(['DelDate' => date('Y-m-d H:i:s')]);
    }


value00: app/Services/RuleEngineService.php
value01:
        $internalOID = (int) $order['InternalOID'];
        $testCode = $action['testCode'] ?? null;

        if (empty($testCode)) {
            throw new \Exception('INSERT_TEST requires testCode');
        }

        // Look up TestSiteID from TestSiteCode
        $testDefSiteModel = new TestDefSiteModel();
        $testSite = $testDefSiteModel->where('TestSiteCode', $testCode)
            ->where('EndDate', null)
            ->first();

        if (!$testSite || empty($testSite['TestSiteID'])) {
            throw new \Exception('INSERT_TEST: Test not found with code: ' . $testCode);
        }

        $testSiteID = (int) $testSite['TestSiteID'];

        $db = \Config\Database::connect();

        // Check if test already exists (avoid duplicates)


value00: app/Services/RuleEngineService.php
value01:
        $db = \Config\Database::connect();

        // Check if test already exists (avoid duplicates)
        $existing = $db->table('patres')
            ->where('OrderID', $internalOID)
            ->where('TestSiteID', $testSiteID)
            ->where('DelDate', null)
            ->get()
            ->getRow();

        if ($existing) {
            // Test already exists, skip
            return;
        }

        // Insert new test row


value00: app/Services/RuleEngineService.php
value01:
    /**
     * Execute TEST_DELETE action - soft delete a test from patres
     */
    protected function executeDeleteTest(array $action, array $context): void
    {
        $order = $context['order'] ?? null;
        if (!is_array($order) || empty($order['InternalOID'])) {
            throw new \Exception('TEST_DELETE requires context.order.InternalOID');
        }

        $internalOID = (int) $order['InternalOID'];
        $testCode = $action['testCode'] ?? null;
        if (empty($testCode)) {
            throw new \Exception('TEST_DELETE requires testCode');
        }

        $testDefSiteModel = new TestDefSiteModel();
        $testSite = $testDefSiteModel->where('TestSiteCode', $testCode)
            ->where('EndDate', null)
            ->first();


value00: app/Services/RuleExpressionService.php
value01:
            if (ctype_space($ch)) {
                continue;
            }

            $two = ($i + 1 < $len) ? $ch . $s[$i + 1] : '';
            if (in_array($two, ['&&', '||', '>=', '<=', '==', '!='], true)) {
                $tokens[] = ['type' => 'op', 'value' => $two];
                $i++;
                continue;
            }

            if (in_array($ch, ['>', '<', '(', ')', '[', ']', ',', '+', '-', '*', '/', '%', '!', '.', ':', '?'], true)) {
                $tokens[] = ['type' => ($ch === '>' || $ch === '<' || $ch === '!' ? 'op' : 'punct'), 'value' => $ch];
                continue;
            }


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Tokenize a condition for lightweight transforms.
     * Returns tokens with keys: type (ident|number|string|op|punct|other), value.
     */
    private function tokenize(string $s): array
    {
        $len = strlen($s);
        $tokens = [];

        for ($i = 0; $i < $len; $i++) {
            $ch = $s[$i];

            if (ctype_space($ch)) {
                continue;
            }


value00: app/Services/RuleEngineService.php
value01:
            case 'ADD_COMMENT': // legacy
                $this->executeAddComment($action, $context);
                break;
            case 'NO_OP':
                // Do nothing
                break;
            default:
                log_message('warning', 'Unknown action type: ' . $type);
                break;
        }


value00: app/Views/errors/cli/error_exception.php
value01:
        $padClass = '       '; // 7 spaces
        $c        = str_pad($i + 1, 3, ' ', STR_PAD_LEFT);

        if (isset($error['file'])) {
            $filepath = clean_path($error['file']) . ':' . $error['line'];

            CLI::write($c . $padFile . CLI::color($filepath, 'yellow'));
        } else {
            CLI::write($c . $padFile . CLI::color('[internal function]', 'yellow'));
        }

        $function = '';

        if (isset($error['class'])) {
            $type = ($error['type'] === '->') ? '()' . $error['type'] : $error['type'];
            $function .= $padClass . $error['class'] . $type . $error['function'];
        } elseif (! isset($error['class']) && isset($error['function'])) {
            $function .= $padClass . $error['function'];
        }


value00: app/Services/RuleExpressionService.php
value01:
            $t = $tokens[$i];
            $type = $t['type'];
            $val = $t['value'];

            if ($type === 'op' && $val === '&&') {
                $out .= ' and ';
                continue;
            }
            if ($type === 'op' && $val === '||') {
                $out .= ' or ';
                continue;
            }


value00: app/Services/RuleExpressionService.php
value01:
    /**
     * Compile DSL expression to engine-compatible JSON structure.
     *
     * Supported DSL (canonical):
     * - if(condition; then-actions; else-actions)
     * - sex('F'|'M') -> patient["Sex"] == 'F'
     * - priority('R'|'S'|'U') -> order["Priority"] == 'S'
     * - age > 18 -> age > 18
     * - requested('CODE') -> requested('CODE') (resolved at runtime)
     * - result_set('TestSiteCode', value)
     * - result_set(value) (deprecated, uses current context TestSiteID)
     * - test_insert('CODE')
     * - test_delete('CODE')
     * - comment_insert('text')
     * - nothing
     * - Multi-action: result_set(0.5):test_insert('CODE'):comment_insert('text')


value00: app/Views/errors/cli/error_exception.php
value01:
<?php

use CodeIgniter\CLI\CLI;

// The main Exception
CLI::write('[' . $exception::class . ']', 'light_gray', 'red');
CLI::write($message);
CLI::write('at ' . CLI::color(clean_path($exception->getFile()) . ':' . $exception->getLine(), 'green'));
CLI::newLine();

$last = $exception;

while ($prevException = $last->getPrevious()) {
    $last = $prevException;

    CLI::write('  Caused by:');
    CLI::write('  [' . $prevException::class . ']', 'red');
    CLI::write('  ' . $prevException->getMessage());
    CLI::write('  at ' . CLI::color(clean_path($prevException->getFile()) . ':' . $prevException->getLine(), 'green'));
    CLI::newLine();
}

// The backtrace


value00: app/Views/errors/cli/error_exception.php
value01:
elseif (! isset($error['class']) && isset($error['function'])) {
            $function .= $padClass . $error['function'];
        }

        $args = implode(', ', array_map(static fn ($value): string => match (true) {
            is_object($value) => 'Object(' . $value::class . ')',
            is_array($value)  => $value !== [] ? '[...]' : '[]',
            $value === null   => 'null', // return the lowercased version
            default           => var_export($value, true),
        }, array_values($error['args'] ?? [])));

        $function .= '(' . $args . ')';

        CLI::write($function);
        CLI::newLine();
    }
}


value00: app/Services/RuleExpressionService.php
value01:
    private function parseSingleStringArg(array $tokens, int $start): ?array
    {
        $t0 = $tokens[$start] ?? null;
        $t1 = $tokens[$start + 1] ?? null;
        $t2 = $tokens[$start + 2] ?? null;
        if (($t0['value'] ?? null) !== '(') {
            return null;
        }
        if (($t1['type'] ?? null) !== 'string') {
            return null;
        }
        if (($t2['value'] ?? null) !== ')') {
            return null;
        }

        $raw = $t1['value'];
        $quote = $raw[0] ?? '';
        if ($quote !== '"' && $quote !== "'") {
            return null;
        }
        $val = substr($raw, 1, -1);

        return [
            'value' => $val,
            'endIndex' => $start + 2,
        ];
    }


value00: app/Views/errors/html/production.php
value01:
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="robots" content="noindex">

    <title><?= lang('Errors.whoops') ?></title>

    <style>
        <?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
    </style>
</head>
<body>

    <div class="container text-center">

        <h1 class="headline"><?= lang('Errors.whoops') ?></h1>

        <p class="lead"><?= lang('Errors.weHitASnag') ?></p>

    </div>

</body>

</html>


value00: app/Views/errors/cli/error_exception.php
value01:
if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE) {
    $backtraces = $last->getTrace();

    if ($backtraces) {
        CLI::write('Backtrace:', 'green');
    }

    foreach ($backtraces as $i => $error) {
        $padFile  = '    '; // 4 spaces
        $padClass = '       '; // 7 spaces
        $c        = str_pad($i + 1, 3, ' ', STR_PAD_LEFT);


value00: app/Views/errors/html/debug.js
value01:
    // Assign onclick events to the tab links, and
    // highlight the first tab
    var i = 0;

    for (var id in tabLinks)
    {
        tabLinks[id].onclick = showTab;
        tabLinks[id].onfocus = function () {
            this.blur()
        };
        if (i == 0)
        {
            tabLinks[id].className = 'active';
        }
        i ++;
    }

    // Hide all content divs except the first
    var i = 0;

    for (var id in contentDivs)
    {
        if (i != 0)
        {
            console.log(contentDivs[id]);
            contentDivs[id].className = 'content hide';
        }
        i ++;
    }
}


value00: app/Views/errors/html/error_404.php
value01:
            letter-spacing: normal;
            font-size: 3rem;
            margin-top: 0;
            margin-bottom: 0;
            color: #222;
        }
        .wrap {
            max-width: 1024px;
            margin: 5rem auto;
            padding: 2rem;
            background: #fff;
            text-align: center;
            border: 1px solid #efefef;
            border-radius: 0.5rem;
            position: relative;
        }
        pre {
            white-space: normal;
            margin-top: 1.5rem;
        }
        code {
            background: #fafafa;
            border: 1px solid #efefef;
            padding: 0.5rem 1rem;
            border-radius: 5px;
            display: block;


value00: app/Views/errors/html/debug.js
value01:
function getHash(url)
{
    var hashPos = url.lastIndexOf('#');
    return url.substring(hashPos + 1);
}

function toggle(elem)
{
    elem = document.getElementById(elem);

    if (elem.style && elem.style['display'])
    {
        // Only works with the "style" attr
        var disp = elem.style['display'];
    }
    else if (elem.currentStyle)
    {
        // For MSIE, naturally
        var disp = elem.currentStyle['display'];
    }
    else if (window.getComputedStyle)
    {
        // For most other browsers
        var disp = document.defaultView.getComputedStyle(elem, null).getPropertyValue('display');
    }

    // Toggle the state of the "display" style
    elem.style.display = disp == 'block' ? 'none' : 'block';

    return false;
}


value00: app/Views/errors/html/error_400.php
value01:
            border: 1px solid #efefef;
            padding: 0.5rem 1rem;
            border-radius: 5px;
            display: block;
        }
        p {
            margin-top: 1.5rem;
        }
        .footer {
            margin-top: 2rem;
            border-top: 1px solid #efefef;
            padding: 1em 2em 0 2em;
            font-size: 85%;
            color: #999;
        }
        a:active,
        a:link,
        a:visited {
            color: #dd4814;
        }
    </style>
</head>
<body>
<div class="wrap">
    <h1>400</h1>

    <p>


value00: app/Views/errors/html/error_404.php
value01:
            <?php if (ENVIRONMENT !== 'production') : ?>
                <?= nl2br(esc($message)) ?>
            <?php else : ?>
                <?= lang('Errors.sorryCannotFind') ?>
            <?php endif; ?>
        </p>
    </div>
</body>
</html>


value00: app/Views/errors/html/debug.js
value01:
var tabLinks    = new Array();
var contentDivs = new Array();

function init()
{
    // Grab the tab links and content divs from the page
    var tabListItems = document.getElementById('tabs').childNodes;
    console.log(tabListItems);
    for (var i = 0; i < tabListItems.length; i ++)
    {
        if (tabListItems[i].nodeName == "LI")
        {
            var tabLink     = getFirstChildWithTagName(tabListItems[i], 'A');
            var id          = getHash(tabLink.getAttribute('href'));
            tabLinks[id]    = tabLink;
            contentDivs[id] = document.getElementById(id);
        }
    }

    // Assign onclick events to the tab links, and
    // highlight the first tab
    var i = 0;


value00: app/Views/errors/html/error_404.php
value01:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title><?= lang('Errors.pageNotFound') ?></title>

    <style>
        div.logo {
            height: 200px;
            width: 155px;
            display: inline-block;
            opacity: 0.08;
            position: absolute;
            top: 2rem;
            left: 50%;
            margin-left: -73px;
        }
        body {
            height: 100%;
            background: #fafafa;
            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
            color: #777;
            font-weight: 300;
        }
        h1 {
            font-weight: lighter;
            letter-spacing: normal;
            font-size: 3rem;
            margin-top: 0;
            margin-bottom: 0;


value00: app/Views/errors/html/error_404.php
value01:
            border: 1px solid #efefef;
            padding: 0.5rem 1rem;
            border-radius: 5px;
            display: block;
        }
        p {
            margin-top: 1.5rem;
        }
        .footer {
            margin-top: 2rem;
            border-top: 1px solid #efefef;
            padding: 1em 2em 0 2em;
            font-size: 85%;
            color: #999;
        }
        a:active,
        a:link,
        a:visited {
            color: #dd4814;
        }
    </style>
</head>
<body>
    <div class="wrap">
        <h1>404</h1>

        <p>


value00: app/Views/errors/html/debug.js
value01:
function showTab()
{
    var selectedId = getHash(this.getAttribute('href'));

    // Highlight the selected tab, and dim all others.
    // Also show the selected content div, and hide all others.
    for (var id in contentDivs)
    {
        if (id == selectedId)
        {
            tabLinks[id].className    = 'active';
            contentDivs[id].className = 'content';
        }
        else
        {
            tabLinks[id].className    = '';
            contentDivs[id].className = 'content hide';
        }
    }

    // Stop the browser following the link
    return false;
}


value00: app/Views/errors/html/debug.js
value01:
function getFirstChildWithTagName(element, tagName)
{
    for (var i = 0; i < element.childNodes.length; i ++)
    {
        if (element.childNodes[i].nodeName == tagName)
        {
            return element.childNodes[i];
        }
    }
}

function getHash(url)
{
    var hashPos = url.lastIndexOf('#');
    return url.substring(hashPos + 1);
}


value00: app/Views/errors/html/error_400.php
value01:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title><?= lang('Errors.badRequest') ?></title>

    <style>
        div.logo {
            height: 200px;
            width: 155px;
            display: inline-block;
            opacity: 0.08;
            position: absolute;
            top: 2rem;
            left: 50%;
            margin-left: -73px;
        }
        body {
            height: 100%;
            background: #fafafa;
            font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
            color: #777;
            font-weight: 300;
        }
        h1 {
            font-weight: lighter;
            letter-spacing: normal;
            font-size: 3rem;
            margin-top: 0;
            margin-bottom: 0;


value00: app/Views/errors/html/error_400.php
value01:
        <?php if (ENVIRONMENT !== 'production') : ?>
            <?= nl2br(esc($message)) ?>
        <?php else : ?>
            <?= lang('Errors.sorryBadRequest') ?>
        <?php endif; ?>
    </p>
</div>
</body>
</html>


value00: public/index.php
value01:
$minPhpVersion = '8.1'; // If you update this, don't forget to update `spark`.
if (version_compare(PHP_VERSION, $minPhpVersion, '<')) {
    $message = sprintf(
        'Your PHP version must be %s or higher to run CodeIgniter. Current version: %s',
        $minPhpVersion,
        PHP_VERSION,
    );

    header('HTTP/1.1 503 Service Unavailable.', true, 503);
    echo $message;

    exit(1);
}

/*
 *---------------------------------------------------------------
 * SET THE CURRENT DIRECTORY
 *---------------------------------------------------------------
 */

// Path to the front controller (this file)
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR);


value00: public/index.php
value01:
define('FCPATH', __DIR__ . DIRECTORY_SEPARATOR);

// Ensure the current directory is pointing to the front controller's directory
if (getcwd() . DIRECTORY_SEPARATOR !== FCPATH) {
    chdir(FCPATH);
}

/*
 *---------------------------------------------------------------
 * BOOTSTRAP THE APPLICATION
 *---------------------------------------------------------------
 * This process sets up the path constants, loads and registers
 * our autoloader, along with Composer's, loads our constants
 * and fires up an environment-specific bootstrapping.
 */

// LOAD OUR PATHS CONFIG FILE
// This is the line that might need to be changed, depending on your folder structure.
require FCPATH . '../app/Config/Paths.php';
// ^^^ Change this line if you move your application folder


value00: public/index.php
value01:
require FCPATH . '../app/Config/Paths.php';
// ^^^ Change this line if you move your application folder

$paths = new Paths();

// LOAD THE FRAMEWORK BOOTSTRAP FILE
require $paths->systemDirectory . '/Boot.php';

exit(Boot::bootWeb($paths));


value00: public/index.php
value01:
<?php

use CodeIgniter\Boot;
use Config\Paths;

/*
 *---------------------------------------------------------------
 * CHECK PHP VERSION
 *---------------------------------------------------------------
 */

$minPhpVersion = '8.1'; // If you update this, don't forget to update `spark`.


value00: docs/test-rule-engine.md
value01:
| `test_delete('CODE')` | Remove a previously requested test from the current order when the rule deems it unnecessary | `test_delete('INS')` |
| `comment_insert('text')` | Insert an order comment (`ordercom`) describing priority or clinical guidance | `comment_insert('Male patient - review')` |
| `nothing` | Explicit no-op to terminate an action chain | `nothing` |

> **Note:** `set_priority()` was removed. Use `comment_insert()` for priority notes without altering billing.


value00: docs/test-rule-engine.md
value01:
### Execution Flow

1. Write or edit the DSL in `ConditionExpr`.
2. POST the expression to `POST /api/rule/compile` to validate syntax and produce compiled JSON.
3. Save the compiled payload into `ConditionExprCompiled` and persist the rule in `ruledef`.
4. Link the rule to one or more tests through `testrule.TestSiteID` (rules only run for linked tests).
5. When the configured event fires (`test_created` or `result_updated`), the engine evaluates `ConditionExprCompiled` and runs the resulting `then` or `else` actions.

> **Note:** The rule engine currently fires only for `test_created` and `result_updated`. Other event codes can exist in the database but are not triggered by the application unless additional `RuleEngineService::run(...)` calls are added.


value00: docs/audit-logging.md
value01:
### logsystem

**Sessions & security**

- Login/logout
- Failed login attempts
- Lockouts/password resets
- Token issue/refresh/revoke
- Authorization failures

**Import/export**

- Import/export job start/end
- Batch ID, source, record counts, status

**System operations**

- Background jobs start/end
- Integration sync runs
- System config changes
- Service errors that affect data integrity


value00: docs/test-rule-engine.md
value01:
## Event Triggers

| Event Code | Status | Trigger Point |
|------------|--------|----------------|
| `test_created` | Active | Fired after a new test row is persisted; the handler calls `RuleEngineService::run('test_created', ...)` to evaluate test-scoped rules |
| `result_updated` | Active | Fired whenever a test result is saved or updated so result-dependent rules run immediately |

Other event codes remain in the database for future workflows, but only `test_created` and `result_updated` are executed by the current application flow.


value00: docs/test-rule-engine.md
value01:
## Syntax Guide

### Basic Format

```
if(condition; then-action; else-action)
```

### Logical Operators

- Use `&&` for AND (all sub-conditions must match).
- Use `||` for OR (any matching branch satisfies the rule).
- Surround mixed logic with parentheses for clarity and precedence.

### Multi-Action Syntax

Actions within any branch are separated by `:` and evaluated in order. Every `then` and `else` branch must end with an action; use `nothing` when no further work is required.

```
if(sex('M'); result_set(0.5):test_insert('HBA1C'); nothing)
```


value00: docs/test-rule-engine.md
value01:
### Compile DSL

Validates the DSL and returns a compiled JSON structure that should be persisted in `ConditionExprCompiled`.

```http
POST /api/rule/compile
Content-Type: application/json

{
  "expr": "if(sex('M'); result_set(0.5); result_set(0.6))"
}
```

Response:

```json
{
  "status": "success",
  "data": {
    "raw": "if(sex('M'); result_set(0.5); result_set(0.6))",
    "compiled": {
      "conditionExpr": "sex('M')",
      "then": ["result_set(0.5)"],
      "else": ["result_set(0.6)"]
    },
    "conditionExprCompiled": "{...}"
  }
}
```


value00: docs/test-rule-engine.md
value01:
## Available Functions

### Conditions

| Function | Description | Example |
|----------|-------------|---------|
| `sex('M'|'F')` | Match patient sex | `sex('M')` |
| `priority('R'|'S'|'U')` | Match order priority | `priority('S')` |
| `age > 18` | Numeric age comparisons (`>`, `<`, `>=`, `<=`) | `age >= 18 && age <= 65` |
| `requested('CODE')` | Check whether the order already requested a test (queries `patres`) | `requested('GLU')` |

### Logical Operators

| Operator | Meaning | Example |
|----------|---------|---------|
| `&&` | AND (all truthy) | `sex('M') && age > 40` |
| `||` | OR (any truthy) | `sex('M') || age > 65` |
| `()` | Group expressions | `(sex('M') && age > 40) || priority('S')` |

## Actions


value00: docs/test-rule-engine.md
value01:
#### Migration Process

Run the migration which:

1. Converts ternary syntax to semicolon syntax.
2. Recompiles every expression into JSON so the engine consumes `ConditionExprCompiled` directly.
3. Eliminates reliance on the `ruleaction` table.

```bash
php spark migrate
```


value00: app/Views/reports/lab_report.php
value01:
esc($order['TrnDate'] ?? 'N/A') ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Priority</span>
                    <span class="info-value"><?= esc($order['Priority'] ?? 'N/A') ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Requesting Physician</span>
                    <span class="info-value"><?= esc($order['ReqApp'] ?? 'N/A') ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Placer ID</span>
                    <span class="info-value"><?= esc($order['PlacerID'] ?? 'N/A')


value00: docs/audit-logging.md
value01:
## Standard Log Schema (Shared Columns)

Use a shared schema for all four tables to keep instrumentation and reporting consistent. The legacy names below match existing patterns and can be reused.

| Column | Description |
| --- | --- |
| `LogID` (PK) | Auto increment primary key per table (e.g., `LogPatientID`) |
| `TblName` | Source table name |
| `RecID` | Record ID of the entity |
| `FldName` | Field name that changed (nullable for bulk events) |
| `FldValuePrev` | Previous value (string or JSON) |
| `FldValueNew` | New value (string or JSON) |
| `UserID` | Acting user ID (nullable for system actions) |
| `SiteID` | Site context |
| `DIDType` | Device identifier type |
| `DID` | Device identifier |


value00: docs/test-rule-engine.md
value01:
# Test Rule Engine Documentation

## Overview

The CLQMS Rule Engine evaluates business rules that inspect orders, patients, and tests, then executes actions when the compiled condition matches.

Rules are authored using a domain specific language stored in `ruledef.ConditionExpr`. Before the platform executes any rule, the DSL must be compiled into JSON and stored in `ConditionExprCompiled`, and each rule must be linked to the tests it should influence via `testrule`.


value00: docs/test-rule-engine.md
value01:
## Runtime Requirements

1. **Compiled expression required:** Rules without `ConditionExprCompiled` are ignored (see `RuleEngineService::run`).
2. **Order context:** `context.order.InternalOID` must exist for any action that writes to `patres` or `ordercom`.
3. **TestSiteID:** `result_set()` needs `testSiteID` (either provided in context or from `order.TestSiteID`). When you provide a `TestSiteCode` as the first argument (`result_set('tesA', value)`), the engine resolves that code before writing the result. `test_insert()` resolves a `TestSiteID` via the `TestSiteCode` in `TestDefSiteModel`, and `test_delete()` removes the matching `TestSiteID` rows when needed.
4. **Requested check:** `requested('CODE')` inspects `patres` rows for the same `OrderID` and `TestSiteCode`.


value00: app/Views/reports/lab_report.php
value01:
            margin-bottom: 20px;
        }
        
        .header h1 {
            font-size: 24px;
            margin-bottom: 5px;
        }
        
        .header .subtitle {
            font-size: 14px;
            color: #666;
        }
        
        .patient-section {
            margin-bottom: 20px;
            padding: 15px;
            background: #f9f9f9;
            border: 1px solid #ddd;
        }
        
        .section-title {
            font-weight: bold;
            font-size: 14px;
            margin-bottom: 10px;
            color: #333;
            border-bottom: 1px solid #ccc;
            padding-bottom: 5px;


value00: app/Views/reports/lab_report.php
value01:
    <?php
    // Helper function to calculate flag dynamically
    function calculateFlag($result) {
        if (empty($result['Result']) || !is_numeric($result['Result'])) {
            return null;
        }
        
        if (empty($result['Low']) || empty($result['High'])) {
            return null;
        }
        
        $value = floatval($result['Result']);
        $low = floatval($result['Low']);
        $high = floatval($result['High']);
        $lowSign = $result['LowSign'] ?? '<=';
        $highSign = $result['HighSign'] ?? '<=';


value00: app/Views/reports/lab_report.php
value01:
esc($order['PlacerID'] ?? 'N/A') ?></span>
                </div>
            </div>
        </div>
        
        <div class="results-section">
            <div class="section-title">Test Results</div>
            <table class="results-table">
                <thead>
                    <tr>
                        <th>Test Name</th>
                        <th>Code</th>
                        <th>Result</th>
                        <th>Unit</th>
                        <th>Flag</th>
                        <th>Reference Range</th>
                    </tr>
                </thead>
                <tbody>
                    <?php


value00: docs/audit-logging.md
value01:
## Activity & Event Codes

Use consistent `ActivityID` and `EventID` values. Recommended defaults:

- `ActivityID`: `CREATE`, `UPDATE`, `DELETE`, `READ`, `MERGE`, `SPLIT`, `CANCEL`, `REOPEN`, `VERIFY`, `AMEND`, `RETRACT`, `RELEASE`, `IMPORT`, `EXPORT`, `LOGIN`, `LOGOUT`
- `EventID`: domain-specific codes (e.g., `PATIENT_REGISTERED`, `ORDER_CREATED`, `RESULT_VERIFIED`, `QC_RECORDED`)


value00: docs/test-rule-engine.md
value01:
```
if(sex('M') && age > 40; result_set(1.5):test_insert('EXTRA_TEST'):comment_insert('Male over 40'); nothing)
```

```
if(sex('F') && (age >= 18 && age <= 50) && priority('S'); result_set('HIGH_PRIO'):comment_insert('Female stat 18-50'); result_set('NORMAL'))
```

```
if(requested('GLU'); test_delete('INS'):comment_insert('Duplicate insulin request removed'); nothing)
```


value00: app/Views/reports/lab_report.php
value01:
esc($patient['Birthdate'] ?? 'N/A') ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Sex</span>
                    <span class="info-value"><?= esc($patient['Sex'] ?? 'N/A') ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Phone</span>
                    <span class="info-value"><?= esc($patient['MobilePhone'] ?? $patient['Phone'] ?? 'N/A') ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Address</span>
                    <span class="info-value"><?= esc(($patient['Street_1'] ?? '') . ' ' . ($patient['City'] ?? ''))


value00: docs/audit-logging.md
value01:
## Event Catalog

### logpatient

**Patient core**

- Register patient
- Update demographics
- Merge/unmerge/split
- Identity changes (MRN, external identifiers)
- Consent grant/revoke/update
- Insurance add/update/remove
- Patient record view (if required by compliance)

**Visit/ADT**

- Admit, transfer, discharge
- Bed/ward/unit changes
- Visit status updates

**Other**

- Patient notes/attachments added/removed
- Patient alerts/flags changes


value00: app/Views/reports/lab_report.php
value01:
            max-width: 800px;
            margin: 0 auto;
            border: 1px solid #ccc;
            padding: 30px;
        }
        
        .header {
            text-align: center;
            border-bottom: 2px solid #333;
            padding-bottom: 20px;
            margin-bottom: 20px;
        }
        
        .header h1 {
            font-size: 24px;
            margin-bottom: 5px;


value00: docs/test-rule-engine.md
value01:
### Runtime Errors

- `RESULT_SET requires context.order.InternalOID` or `testSiteID`: include those fields in the context passed to `RuleEngineService::run()`.
- `TEST_INSERT` failures mean the provided `TestSiteCode` does not exist or the rule attempted to insert a duplicate test; check `testdefsite` and existing `patres` rows.
- `COMMENT_INSERT requires comment`: ensure the action provides text.


value00: app/Views/reports/lab_report.php
value01:
if (!empty($results)): ?>
                        <?php foreach ($results as $result): ?>
                            <tr>
                                <td><?= esc($result['TestSiteName'] ?? 'N/A') ?></td>
                                <td><?= esc($result['TestSiteCode'] ?? 'N/A') ?></td>
                                <td><?= esc($result['Result'] ?? 'Pending') ?></td>
                                <td><?= esc($result['Unit1'] ?? '') ?></td>
                                <td>
                                    <?php 
                                    $flag = calculateFlag($result);
                                    if (!empty($flag))


value00: docs/test-rule-engine.md
value01:
### Evaluate Expression (Validation)

This endpoint evaluates an expression against a runtime context. It does not compile DSL or persist the result.

```http
POST /api/rule/validate
Content-Type: application/json

{
  "expr": "order[\"Age\"] > 18",
  "context": {
    "order": {
      "Age": 25
    }
  }
}
```

Response:

```json
{
  "status": "success",
  "data": {
    "valid": true,
    "result": true
  }
}
```


value00: app/Views/reports/lab_report.php
value01:
?>
                                </td>
                                <td class="reference-range">
                                    <?php if (!empty($result['Low']) && !empty($result['High'])): ?>
                                        <?= esc($result['LowSign'] ?? '') ?> <?= esc($result['Low']) ?> - <?= esc($result['HighSign'] ?? '') ?> <?= esc($result['High']) ?>
                                    <?php else: ?>
                                        -
                                    <?php endif; ?>
                                </td>
                            </tr>
                        <?php endforeach;


value00: public/bundle-api-docs.js
value01:
#!/usr/bin/env node

/**
 * OpenAPI Documentation Bundler - Node.js Version
 * 
 * This script merges the modular OpenAPI specification files into a single
 * api-docs.bundled.yaml file that can be served to Swagger UI.
 * 
 * It merges paths from the paths/ directory and then uses Redocly CLI to resolve
 * all $ref references into inline definitions.
 * 
 * Usage: node bundle-api-docs.js
 */

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');


value00: app/Views/reports/lab_report.php
value01:
?>
                </tbody>
            </table>
        </div>
        
        <div class="signature-section">
            <div class="signature-box">
                <div class="signature-line"></div>
                <div>Authorized Signature</div>
                <div style="font-size: 10px; color: #666; margin-top: 5px;">Pathologist / Lab Director</div>
            </div>
        </div>
        
        <div class="footer">
            <p><strong>Report Generated:</strong> <?= esc($generatedAt) ?></p>
            <p><strong>CLQMS</strong> - Clinical Laboratory Quality Management System</p>
            <p>This report is electronically generated and is valid without signature.</p>
        </div>
    </div>
</body>
</html>


value00: app/Views/reports/lab_report.php
value01:
esc(($patient['Street_1'] ?? '') . ' ' . ($patient['City'] ?? '')) ?></span>
                </div>
            </div>
        </div>
        
        <div class="order-section">
            <div class="section-title">Order Information</div>
            <div class="info-grid">
                <div class="info-item">
                    <span class="info-label">Order ID</span>
                    <span class="info-value"><?= esc($order['OrderID'] ?? 'N/A') ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Order Date</span>
                    <span class="info-value"><?= esc($order['TrnDate'] ?? 'N/A')


value00: docs/test-rule-engine.md
value01:
### Multiple Rules

Create each rule as its own `ruledef` row; do not chain expressions with commas. The `testrule` table manages rule-to-test mappings, so multiple rules can attach to the same test. Example:

1. Insert `RULE_MALE_RESULT` and `RULE_SENIOR_COMMENT` in `ruledef`.
2. Add two `testrule` rows linking each rule to the appropriate `TestSiteID`.

Each rule compiles and runs independently when its trigger fires and the test is linked.


value00: docs/test-rule-engine.md
value01:
## Troubleshooting

### Rule Not Executing

1. Ensure the rule has a compiled payload (`ConditionExprCompiled`).
2. Confirm the rule is linked to the relevant `TestSiteID` in `testrule`.
3. Verify the `EventCode` matches the currently triggered event (`test_created` or `result_updated`).
4. Check that `EndDate IS NULL` for both `ruledef` and `testrule` (soft deletes disable execution).
5. Use `/api/rule/compile` to validate the DSL and view errors.

### Invalid Expression

1. POST the expression to `/api/rule/compile` to get a detailed compilation error.
2. If using `/api/rule/validate`, supply the expected `context` payload; the endpoint simply evaluates the expression without saving it.


value00: docs/test-rule-engine.md
value01:
| Action | Description | Example |
|--------|-------------|---------|
| `result_set(value)` | (deprecated) Write to `patres.Result` for the current context test | `result_set(0.5)` |
| `result_set('CODE', value)` | Target a specific test by `TestSiteCode`, allowing multiple tests to be updated in one rule | `result_set('tesA', 0.5)` |
| `test_insert('CODE')` | Insert a test row by `TestSiteCode` if it doesn’t already exist for the order | `test_insert('HBA1C')` |
| `test_delete('CODE')` | Remove a previously requested test from the current order when the rule deems it unnecessary | `test_delete('INS')` |


value00: public/bundle-api-docs.js
value01:
console.log(`Found ${pathFiles.length} path files to merge...`);

// Merge all paths content
let mergedPaths = [];
for (const filepath of pathFiles) {
  const filename = path.basename(filepath);
  try {
    let content = fs.readFileSync(filepath, 'utf8');
    // Fix relative paths: ../components/ -> ./components/
    // because paths are now at the root level after merging
    content = content.replace(/\.\.\/components\//g, './components/');
    mergedPaths.push(`# From: ${filename}\n${content}`);
    console.log(`  [OK] Merged paths from ${filename}`);
  } catch (e) {
    console.log(`  [WARN] Failed to read ${filename}: ${e.message}`);
  }
}

// Replace empty paths with merged paths


value00: docs/test-rule-engine.md
value01:
## Database Schema

### Tables

- **ruledef** – stores rule metadata, raw DSL, and compiled JSON.
- **testrule** – mapping table that links rules to tests via `TestSiteID`.
- **ruleaction** – deprecated. Actions are now embedded in `ConditionExprCompiled`.

### Key Columns

| Column | Table | Description |
|--------|-------|-------------|
| `EventCode` | ruledef | The trigger event (typically `test_created` or `result_updated`). |
| `ConditionExpr` | ruledef | Raw DSL expression (semicolon syntax). |
| `ConditionExprCompiled` | ruledef | JSON payload consumed at runtime (`then`, `else`, etc.). |
| `ActionType` / `ActionParams` | ruleaction | Deprecated; actions live in compiled JSON now. |


value00: public/bundle-api-docs.js
value01:
const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

const publicDir = __dirname;
const pathsDir = path.join(publicDir, 'paths');
const tempFile = path.join(publicDir, 'api-docs.merged.yaml');
const outputFile = path.join(publicDir, 'api-docs.bundled.yaml');

// Read the base api-docs.yaml
const apiDocsPath = path.join(publicDir, 'api-docs.yaml');
let apiDocsContent = fs.readFileSync(apiDocsPath, 'utf8');

// Parse YAML manually (simple approach - looking for paths: {})
const pathsMatch = apiDocsContent.match(/^paths:\s*\{\}/m);
if (!pathsMatch) {
  console.error('Could not find empty paths section in api-docs.yaml');
  process.exit(1);
}


value00: docs/test-rule-engine.md
value01:
### Get Rule

```http
GET /api/rule/1
```

Response includes `linkedTests`:

```json
{
  "status": "success",
  "message": "fetch success",
  "data": {
    "RuleID": 1,
    "RuleCode": "RULE_001",
    "RuleName": "Sex-based result",
    "EventCode": "test_created",
    "ConditionExpr": "if(sex('M'); result_set(0.5); result_set(0.6))",
    "ConditionExprCompiled": "{...}",
    "linkedTests": [1, 2]
  }
}
```


value00: app/Views/reports/lab_report.php
value01:
            border: 1px solid #f5c6cb;
        }
        
        .reference-range {
            font-size: 10px;
            color: #666;
        }
        
        .footer {
            margin-top: 30px;
            padding-top: 20px;
            border-top: 1px solid #ccc;
            font-size: 10px;
            color: #666;
        }
        
        .signature-section {
            margin-top: 40px;
            display: flex;
            justify-content: flex-end;
        }
        
        .signature-box {
            text-align: center;
            width: 200px;
        }
        
        .signature-line {
            border-bottom: 1px solid #333;
            margin-top: 60px;
            margin-bottom: 5px;


value00: app/Views/reports/lab_report.php
value01:
            font-size: 11px;
        }
        
        .results-table tr:nth-child(even) {
            background: #f5f5f5;
        }
        
        .flag {
            display: inline-block;
            padding: 2px 6px;
            border-radius: 3px;
            font-weight: bold;
            font-size: 11px;
        }
        
        .flag-low {
            background: #fff3cd;
            color: #856404;
            border: 1px solid #ffc107;
        }
        
        .flag-high {
            background: #f8d7da;
            color: #721c24;
            border: 1px solid #f5c6cb;
        }
        
        .reference-range {
            font-size: 10px;


value00: docs/audit-logging.md
value01:
### logorder

**Orders/tests**

- Create/cancel/reopen order
- Add/remove tests
- Priority changes
- Order comments added/removed

**Specimen lifecycle**

- Collected, labeled, received, rejected
- Centrifuged, aliquoted, stored
- Disposed/expired

**Results**

- Result entered/updated
- Verified/amended
- Released/retracted/corrected
- Result comments/interpretation changes
- Auto-verification override

**QC**

- QC result recorded
- QC failure/override


value00: docs/audit-logging.md
value01:
| `SiteID` | Site context |
| `DIDType` | Device identifier type |
| `DID` | Device identifier |
| `MachineID` | Workstation or host identifier |
| `SessionID` | Session identifier |
| `AppID` | Client application ID |
| `ProcessID` | Process/workflow identifier |
| `WebPageID` | UI page/context (nullable) |
| `EventID` | Event code (see catalog) |
| `ActivityID` | Action code (create/update/delete/read/etc.) |
| `Reason` | User/system reason |
| `LogDate` | Timestamp of event |
| `Context` | JSON metadata (optional but recommended) |
| `IpAddress` | Remote IP (optional but recommended) |

Recommended: keep a JSON string in `Context` for extra details (e.g., route, request id, batch id, error message). Use size limits to avoid oversized rows.


value00: app/Views/reports/lab_report.php
value01:
                                    $flag = calculateFlag($result);
                                    if (!empty($flag)): 
                                    ?>
                                        <span class="flag flag-<?= $flag === 'H' ? 'high' : 'low' ?>">
                                            <?= $flag === 'H' ? 'HIGH' : 'LOW' ?>
                                        </span>
                                    <?php else: ?>
                                        -
                                    <?php endif; ?>
                                </td>
                                <td class="reference-range">
                                    <?php


value00: app/Views/reports/lab_report.php
value01:
?>
                    <?php else: ?>
                        <tr>
                            <td colspan="6" style="text-align: center;">No results found for this order</td>
                        </tr>
                    <?php endif;


value00: docs/audit-logging.md
value01:
### logmaster

**Value sets**

- Create/update/retire value set items

**Test definitions**

- Test definition updates (units, methods, ranges)
- Reference range changes
- Formula/delta check changes
- Test panel membership add/remove

**Infrastructure**

- Analyzer/instrument config changes
- Host app integration config
- Coding system changes

**Users/roles**

- User create/disable/reset
- Role changes
- Permission changes

**Sites/workstations**

- Site/location/workstation CRUD


value00: app/Views/reports/lab_report.php
value01:
esc(($patient['NameFirst'] ?? '') . ' ' . ($patient['NameLast'] ?? '')) ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Patient ID</span>
                    <span class="info-value"><?= esc($patient['PatientID'] ?? 'N/A') ?></span>
                </div>
                <div class="info-item">
                    <span class="info-label">Date of Birth</span>
                    <span class="info-value"><?= esc($patient['Birthdate'] ?? 'N/A')


value00: app/Views/reports/lab_report.php
value01:
    ?>
    <button class="print-button" onclick="window.print()">Print Report</button>
    
    <div class="report-container">
        <div class="header">
            <h1>LABORATORY REPORT</h1>
            <div class="subtitle">Clinical Laboratory Quality Management System</div>
        </div>
        
        <div class="patient-section">
            <div class="section-title">Patient Information</div>
            <div class="info-grid">
                <div class="info-item">
                    <span class="info-label">Patient Name</span>
                    <span class="info-value"><?= esc(($patient['NameFirst'] ?? '') . ' ' . ($patient['NameLast'] ?? ''))


value00: app/Views/reports/lab_report.php
value01:
        }
        
        .print-button:hover {
            background: #0056b3;
        }
        
        @media print {
            body {
                padding: 0;
            }
            
            .report-container {
                border: none;
                padding: 0;
            }
            
            .print-button {
                display: none;
            }
            
            .results-table {
                page-break-inside: auto;
            }
            
            .results-table tr {
                page-break-inside: avoid;
            }
        }
    </style>
</head>
<body>


value00: public/bundle-api-docs.js
value01:
// Now use Redocly CLI to resolve all $ref references
console.log('\nResolving $ref references with Redocly CLI...');
try {
  const runBundle = (cmd) => execSync(cmd, { cwd: publicDir, stdio: 'inherit' });

  try {
    runBundle(`redocly bundle "${tempFile}" -o "${outputFile}"`);
  } catch (e) {
    // Fallback: use npx if redocly isn't installed globally
    runBundle(`npx --yes @redocly/cli bundle "${tempFile}" -o "${outputFile}"`);
  }
  
  console.log(`\n[SUCCESS] Resolved all $ref references to: ${outputFile}`);
  
  // Clean up temp file
  fs.unlinkSync(tempFile);
  console.log(`[CLEANUP] Removed temp file: ${tempFile}`);


value00: docs/test-rule-engine.md
value01:
### Create Rule

```http
POST /api/rule
Content-Type: application/json

{
  "RuleCode": "RULE_001",
  "RuleName": "Sex-based result",
  "EventCode": "test_created",
  "ConditionExpr": "if(sex('M'); result_set(0.5); result_set(0.6))",
  "ConditionExprCompiled": "<compiled JSON here>",
  "TestSiteIDs": [1, 2]
}
```

Response:

```json
{
  "status": "success",
  "message": "Rule created successfully",
  "data": {
    "RuleID": 1
  }
}
```


value00: docs/test-rule-engine.md
value01:
## Examples

```
if(sex('M'); result_set('tesA', 0.5):result_set('tesB', 1.2); result_set('tesA', 0.6):result_set('tesB', 1.0))
```
Sets both `tesA`/`tesB` results together per branch.

```
if(requested('GLU'); test_insert('HBA1C'):test_insert('INS'); nothing)
```
Adds new tests when glucose is already requested.

```
if(sex('M') && age > 40; result_set(1.2); result_set(1.0))
```

```
if((sex('M') && age > 40) || (sex('F') && age > 50); result_set(1.5); result_set(1.0))
```

```
if(priority('S'); result_set('URGENT'):test_insert('STAT_TEST'); result_set('NORMAL'))
```

```
if(sex('M') && age > 40; result_set(1.5):test_insert('EXTRA_TEST'):comment_insert('Male over 40'); nothing)
```


value00: app/Views/reports/lab_report.php
value01:
        $lowSign = $result['LowSign'] ?? '<=';
        $highSign = $result['HighSign'] ?? '<=';
        
        // Check low
        if ($lowSign === '<=' && $value <= $low) {
            return 'L';
        }
        if ($lowSign === '<' && $value < $low) {
            return 'L';
        }
        
        // Check high
        if ($highSign === '>=' && $value >= $high) {
            return 'H';
        }
        if ($highSign === '>' && $value > $high) {
            return 'H';
        }
        
        return null; // Normal
    }


value00: app/Views/reports/lab_report.php
value01:
</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        
        body {
            font-family: Arial, sans-serif;
            font-size: 12px;
            line-height: 1.4;
            color: #333;
            background: #fff;
            padding: 20px;
        }
        
        .report-container {
            max-width: 800px;
            margin: 0 auto;
            border: 1px solid #ccc;
            padding: 30px;


value00: docs/audit-logging.md
value01:
# Audit Logging Strategy

## Overview

This document defines how CLQMS should capture audit and operational logs across four tables:

- `logpatient` — patient, visit, and ADT activity
- `logorder` — orders, tests, specimens, results, and QC
- `logmaster` — master data and configuration changes
- `logsystem` — sessions, security, import/export, and system operations

The intent is to audit all domains, including master data changes, and to standardize event capture so reporting and compliance are consistent.


value00: app/Views/reports/lab_report.php
value01:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Lab Report - <?= esc($order['OrderID'] ?? 'N/A') ?>


value00: docs/audit-logging.md
value01:
## Capture Guidelines

- Always capture `UserID`, `SessionID`, `SiteID`, and `LogDate` when available.
- If the action is system-driven, set `UserID` to `SYSTEM` (or null) and add context in `Context`.
- Store payload diffs in `FldValuePrev` and `FldValueNew` for single-field changes; for multi-field changes, put a JSON diff in `Context` and leave `FldName` null.
- For bulk operations, store batch metadata in `Context` (`batch_id`, `record_count`, `source`).
- Do not log secrets, tokens, or full PHI when not required. Mask or omit sensitive fields.

## Retention & Governance

- Define retention policy per table (e.g., 7 years for patient/order, 2 years for system).
- Archive before purge; record purge activity in `logsystem`.
- Restrict write/delete permissions to service accounts only.


value00: app/Views/reports/lab_report.php
value01:
            margin-bottom: 10px;
            color: #333;
            border-bottom: 1px solid #ccc;
            padding-bottom: 5px;
        }
        
        .info-grid {
            display: grid;
            grid-template-columns: repeat(3, 1fr);
            gap: 10px;
        }
        
        .info-item {
            display: flex;
            flex-direction: column;
        }
        
        .info-label {
            font-weight: bold;
            color: #666;
            font-size: 11px;
        }
        
        .info-value {
            color: #333;
        }
        
        .order-section {
            margin-bottom: 20px;
            padding: 15px;
            background: #f9f9f9;
            border: 1px solid #ddd;


value00: docs/test-rule-engine.md
value01:
### Update Rule

```http
PATCH /api/rule/1
Content-Type: application/json

{
  "RuleName": "Sex-based result v2",
  "ConditionExpr": "if(sex('M'); result_set(0.7); result_set(0.6))",
  "ConditionExprCompiled": "<compiled JSON here>",
  "TestSiteIDs": [1, 3]
}
```

Response:

```json
{
  "status": "success",
  "message": "Rule updated successfully",
  "data": {
    "RuleID": 1
  }
}
```

### Delete Rule

```http
DELETE /api/rule/1
```

Response:

```json
{
  "status": "success",
  "message": "Rule deleted successfully",
  "data": {
    "RuleID": 1
  }
}
```


value00: public/bundle-api-docs.js
value01:
catch (e) {
  console.error(`\n[ERROR] Failed to run Redocly CLI: ${e.message}`);
  console.error('Make sure Redocly CLI is installed (global or via npx): npm install -g @redocly/cli');
  process.exit(1);
}


value00: docs/audit-logging.md
value01:
## Table Ownership

| Event | Table |
| --- | --- |
| Patient registered/updated/merged | `logpatient` |
| Insurance/consent changed | `logpatient` |
| Patient visit (admit/transfer/discharge) | `logpatient` |
| Order created/cancelled | `logorder` |
| Sample received/rejected | `logorder` |
| Result entered/verified/amended | `logorder` |
| Result released/retracted/corrected | `logorder` |
| QC result recorded | `logorder` |
| Test panel added/removed | `logmaster` |
| Reference range changed | `logmaster` |
| Analyzer config updated | `logmaster` |
| User role changed | `logmaster` |
| User login/logout | `logsystem` |
| Import/export job start/end | `logsystem` |


value00: docs/audit-logging.md
value01:
## Implementation Checklist

1. Create the four tables with shared schema (or migrate existing log tables to match).
2. Add a single audit service with helpers to build a normalized payload.
3. Instrument controllers/services for each event category above.
4. Add automated tests for representative audit writes.
5. Document `EventID` codes used by each endpoint/service.


value00: docs/test-rule-engine.md
value01:
## Best Practices

1. Always run `POST /api/rule/compile` before persisting a rule so `ConditionExprCompiled` exists.
2. Link each rule to the relevant tests via `testrule.TestSiteID`—rules are scoped to linked tests.
3. Use multi-action (`:`) to bundle several actions in a single branch; finish the branch with `nothing` if no further work is needed.
4. Prefer `comment_insert()` over the removed `set_priority()` action when documenting priority decisions.
5. Group complex boolean logic with parentheses for clarity when mixing `&&` and `||`.
6. Use `requested('CODE')` responsibly; it performs a database lookup on `patres` so avoid invoking it in high-frequency loops without reason.

## Migration Guide


value00: public/bundle-api-docs.js
value01:
// Replace empty paths with merged paths
const pathsYaml = mergedPaths.join('\n\n');
const mergedContent = apiDocsContent.replace(
  /^paths:\s*\{\}/m,
  `paths:\n${pathsYaml.split('\n').map(line => '  ' + line).join('\n')}`
);

// Write merged file (still has $refs)
fs.writeFileSync(tempFile, mergedContent, 'utf8');
console.log(`\n[SUCCESS] Merged paths into: ${tempFile}`);

// Now use Redocly CLI to resolve all $ref references
console.log('\nResolving $ref references with Redocly CLI...');


value00: docs/test-rule-engine.md
value01:
### Syntax Changes (v2.0)

The DSL moved from ternary (`condition ? action : action`) to semicolon syntax. Existing rules must be migrated via the provided script.

| Old Syntax | New Syntax |
|------------|------------|
| `if(condition ? action : action)` | `if(condition; action; action)` |

#### Migration Examples

```
# BEFORE
if(sex('M') ? result_set(0.5) : result_set(0.6))

# AFTER
if(sex('M'); result_set(0.5); result_set(0.6))
```

```
# BEFORE
if(sex('F') ? set_priority('S') : nothing)

# AFTER
if(sex('F'); comment_insert('Female patient - review priority'); nothing)
```


value00: docs/test-rule-engine.md
value01:
## API Endpoints

All endpoints live under `/api/rule` and accept JSON. Responses use the standard `{ status, message, data }` envelope.

### List Rules

```http
GET /api/rule?EventCode=test_created&TestSiteID=12&search=glucose
```

Query Params:

- `EventCode` (optional) filter by event code.
- `TestSiteID` (optional) filter rules linked to a test site.
- `search` (optional) partial match against `RuleName`.

Response:

```json
{
  "status": "success",
  "message": "fetch success",
  "data": [
    {
      "RuleID": 1,
      "RuleCode": "RULE_001",
      "RuleName": "Sex-based result",
      "EventCode": "test_created",
      "ConditionExpr": "if(sex('M'); result_set(0.5); result_set(0.6))",
      "ConditionExprCompiled": "{...}"
    }
  ]
}
```


value00: app/Views/reports/lab_report.php
value01:
        .signature-line {
            border-bottom: 1px solid #333;
            margin-top: 60px;
            margin-bottom: 5px;
        }
        
        .print-button {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
        }
        
        .print-button:hover {
            background: #0056b3;
        }
        
        @media print {


value00: public/bundle-api-docs.js
value01:
if (!pathsMatch) {
  console.error('Could not find empty paths section in api-docs.yaml');
  process.exit(1);
}

// Read all path files
const pathFiles = fs.readdirSync(pathsDir)
  .filter(f => f.endsWith('.yaml'))
  .map(f => path.join(pathsDir, f));

console.log(`Found ${pathFiles.length} path files to merge...`);

// Merge all paths content
let mergedPaths = [];


value00: app/Views/reports/lab_report.php
value01:
            margin-bottom: 20px;
            padding: 15px;
            background: #f9f9f9;
            border: 1px solid #ddd;
        }
        
        .results-section {
            margin-bottom: 20px;
        }
        
        .results-table {
            width: 100%;
            border-collapse: collapse;
            margin-top: 10px;
        }
        
        .results-table th,
        .results-table td {
            border: 1px solid #ccc;
            padding: 8px;
            text-align: left;
        }
        
        .results-table th {
            background: #e0e0e0;
            font-weight: bold;
            font-size: 11px;
        }
        
        .results-table tr:nth-child(even) {
            background: #f5f5f5;


value00: public/bundle-api-docs.js
value01:
  // Clean up temp file
  fs.unlinkSync(tempFile);
  console.log(`[CLEANUP] Removed temp file: ${tempFile}`);
  
  // Show stats
  const stats = fs.statSync(outputFile);
  console.log(`\n${'='.repeat(60)}`);
  console.log('Bundling complete!');
  console.log(`  Output: ${outputFile}`);
  console.log(`  Size: ${(stats.size / 1024).toFixed(1)} KB`);
  console.log(`\nYou can now serve this file to Swagger UI.`);
  console.log(`${'='.repeat(60)}`);
  
}


value00: docs/test-rule-engine.md
value01:
## Rule Structure

```
Rule
├── Event Trigger (when to run)
├── Conditions (when to match)
└── Actions (what to do)
```

The DSL expression lives in `ConditionExpr`. The compile endpoint (`/api/rule/compile`) renders the lifeblood of execution, producing `conditionExpr`, `valueExpr`, `then`, and `else` nodes that the engine consumes at runtime.


value00: docs/calculator-operators.md
value01:
### Calculate By Code Or Name

Evaluates a configured calculation by `TestSiteCode` or `TestSiteName`. Returns a compact map with a single key/value or `{}` on failure.

```http
POST /api/calc/testcode/GLU
Content-Type: application/json

{
  "result": 110,
  "factor": 1.1
}
```

Response:

```json
{
  "GLU": 121
}
```

---


value00: docs/calculator-operators.md
value01:
## Variables in CalculatorService

When using `calculateFromDefinition()`, the following variables are automatically available:

| Variable | Description | Type |
|----------|-------------|------|
| `{result}` | The test result value | Float |
| `{factor}` | Calculation factor (default: 1) | Float |
| `{gender}` | Gender value (0=Unknown, 1=Female, 2=Male) | Integer |
| `{age}` | Patient age | Float |
| `{ref_low}` | Reference range low value | Float |
| `{ref_high}` | Reference range high value | Float |

### Gender Mapping

The `gender` variable accepts the following values:

| Value | Description |
|-------|-------------|
| `0` | Unknown |
| `1` | Female |
| `2` | Male |

Or use string values: `'unknown'`, `'female'`, `'male'`

---


value00: docs/calculator-operators.md
value01:
# Calculator Service Operators Reference

## Overview

The `CalculatorService` (`app/Services/CalculatorService.php`) uses the [mossadal/math-parser](https://github.com/mossadal/math-parser) library to safely evaluate mathematical expressions. This document lists all available operators, functions, and constants.

---


value00: docs/calculator-operators.md
value01:
## Best Practices

1. **Always validate formulas** before storing in database
2. **Use placeholder syntax** `{variable_name}` for clarity
3. **Handle exceptions** in production code
4. **Test edge cases** like zero values and boundary conditions
5. **Document formulas** with comments in your code

---

## References

- [math-parser GitHub](https://github.com/mossadal/math-parser)
- [math-parser Documentation](http://mossadal.github.io/math-parser/)
- `app/Services/CalculatorService.php`


value00: docs/calculator-operators.md
value01:
### Trigonometric Functions (Degrees)

| Function | Description | Example |
|----------|-------------|---------|
| `sind(x)` | Sine (degrees) | `sind(90)` → `1` |
| `cosd(x)` | Cosine (degrees) | `cosd(0)` → `1` |
| `tand(x)` | Tangent (degrees) | `tand(45)` → `1` |
| `cotd(x)` | Cotangent (degrees) | `cotd(45)` → `1` |

### Hyperbolic Functions

| Function | Description | Example |
|----------|-------------|---------|
| `sinh(x)` | Hyperbolic sine | `sinh(1)` → `1.175...` |
| `cosh(x)` | Hyperbolic cosine | `cosh(1)` → `1.543...` |
| `tanh(x)` | Hyperbolic tangent | `tanh(1)` → `0.761...` |
| `coth(x)` | Hyperbolic cotangent | `coth(2)` → `1.037...` |


value00: docs/calculator-operators.md
value01:
## Usage Examples

### Basic Calculation

```php
use App\Services\CalculatorService;

$calculator = new CalculatorService();

// Simple arithmetic
$result = $calculator->calculate("5 + 3 * 2");
// Result: 11

// Using functions
$result = $calculator->calculate("sqrt(16) + abs(-5)");
// Result: 9

// Using constants
$result = $calculator->calculate("2 * pi * r", ['r' => 5]);
// Result: 31.415...
```

### With Variables

```php
$formula = "{result} * {factor} + 10";
$variables = [
    'result' => 5.2,
    'factor' => 2
];

$result = $calculator->calculate($formula, $variables);
// Result: 20.4
```


value00: docs/calculator-operators.md
value01:
## Mathematical Functions

### Rounding Functions

| Function | Description | Example |
|----------|-------------|---------|
| `sqrt(x)` | Square root | `sqrt(16)` → `4` |
| `round(x)` | Round to nearest integer | `round(3.7)` → `4` |
| `ceil(x)` | Round up to integer | `ceil(3.2)` → `4` |
| `floor(x)` | Round down to integer | `floor(3.9)` → `3` |
| `abs(x)` | Absolute value | `abs(-5)` → `5` |
| `sgn(x)` | Sign function | `sgn(-10)` → `-1` |

### Trigonometric Functions (Radians)

| Function | Description | Example |
|----------|-------------|---------|
| `sin(x)` | Sine | `sin(pi/2)` → `1` |
| `cos(x)` | Cosine | `cos(0)` → `1` |
| `tan(x)` | Tangent | `tan(pi/4)` → `1` |
| `cot(x)` | Cotangent | `cot(pi/4)` → `1` |


value00: docs/calculator-operators.md
value01:
## Constants

| Constant | Value | Description | Example |
|----------|-------|-------------|---------|
| `pi` | 3.14159265... | Ratio of circle circumference to diameter | `pi * r ^ 2` |
| `e` | 2.71828182... | Euler's number | `e ^ x` |
| `NAN` | Not a Number | Invalid mathematical result | - |
| `INF` | Infinity | Positive infinity | - |

---


value00: docs/calculator-operators.md
value01:
## API Endpoints

All endpoints live under `/api` and accept JSON. Responses use the standard `{ status, message, data }` envelope unless stated otherwise.

### Calculate Formula

```http
POST /api/calculate
Content-Type: application/json

{
  "formula": "{result} * {factor} + {gender}",
  "variables": {
    "result": 100,
    "factor": 0.5,
    "gender": "female"
  }
}
```

Response:

```json
{
  "status": "success",
  "data": {
    "result": 50.5,
    "formula": "{result} * {factor} + {gender}",
    "variables": {
      "result": 100,
      "factor": 0.5,
      "gender": "female"
    }
  }
}
```


value00: docs/calculator-operators.md
value01:
## Supported Operators

### Arithmetic Operators

| Operator | Description | Example | Result |
|----------|-------------|---------|--------|
| `+` | Addition | `5 + 3` | `8` |
| `-` | Subtraction | `10 - 4` | `6` |
| `*` | Multiplication | `6 * 7` | `42` |
| `/` | Division | `20 / 4` | `5` |
| `^` | Exponentiation (power) | `2 ^ 3` | `8` |
| `!` | Factorial | `5!` | `120` |
| `!!` | Semi-factorial (double factorial) | `5!!` | `15` |

### Parentheses

Use parentheses to control operation precedence:

```
(2 + 3) * 4   // Result: 20
2 + 3 * 4     // Result: 14
```

---


value00: docs/calculator-operators.md
value01:
## Error Handling

The service throws exceptions for invalid formulas or missing variables:

```php
try {
    $result = $calculator->calculate("{result} / 0");
} catch (\Exception $e) {
    // Handle division by zero or other errors
    log_message('error', $e->getMessage());
}
```

Common errors:

- **Invalid formula syntax**: Malformed expressions
- **Missing variable**: Variable placeholder not provided in data array
- **Non-numeric value**: Variables must be numeric
- **Division by zero**: Mathematical error

---


value00: docs/calculator-operators.md
value01:
## Implicit Multiplication

The parser supports implicit multiplication (no explicit `*` operator needed):

| Expression | Parsed As | Result (x=2, y=3) |
|------------|-----------|-------------------|
| `2x` | `2 * x` | `4` |
| `x sin(x)` | `x * sin(x)` | `1.818...` |
| `2xy` | `2 * x * y` | `12` |
| `x^2y` | `x^2 * y` | `12` |

**Note:** Implicit multiplication has the same precedence as explicit multiplication. `xy^2z` is parsed as `x*y^2*z`, NOT as `x*y^(2*z)`.

---


value00: docs/calculator-operators.md
value01:
### Validate Formula

```http
POST /api/calculate/validate
Content-Type: application/json

{
  "formula": "{result} * 2 + 5"
}
```

Response:

```json
{
  "status": "success",
  "data": {
    "valid": true,
    "error": null,
    "variables": ["result"]
  }
}
```

### Calculate By Test Site

Uses the `testdefcal` definition for a test site. The incoming body supplies the variables required by the formula.

```http
POST /api/calc/testsite/123
Content-Type: application/json

{
  "result": 85,
  "gender": "female",
  "age": 30
}
```

Response:

```json
{
  "status": "success",
  "data": {
    "result": 92.4,
    "testSiteID": 123,
    "formula": "{result} * {factor} + {age}",
    "variables": {
      "result": 85,
      "gender": "female",
      "age": 30
    }
  }
}
```


value00: docs/calculator-operators.md
value01:
### Using calculateFromDefinition

```php
$calcDef = [
    'FormulaCode' => '{result} * {factor} + {gender}',
    'Factor' => 2
];

$testValues = [
    'result' => 10,
    'gender' => 1  // Female
];

$result = $calculator->calculateFromDefinition($calcDef, $testValues);
// Result: 21 (10 * 2 + 1)
```

---


value00: docs/calculator-operators.md
value01:
### Inverse Hyperbolic Functions

| Function | Aliases | Description | Example |
|----------|---------|-------------|---------|
| `arsinh(x)` | `asinh(x)`, `arcsinh(x)` | Inverse hyperbolic sine | `arsinh(1)` → `0.881...` |
| `arcosh(x)` | `acosh(x)`, `arccosh(x)` | Inverse hyperbolic cosine | `arcosh(2)` → `1.316...` |
| `artanh(x)` | `atanh(x)`, `arctanh(x)` | Inverse hyperbolic tangent | `artanh(0.5)` → `0.549...` |
| `arcoth(x)` | `acoth(x)`, `arccoth(x)` | Inverse hyperbolic cotangent | `arcoth(2)` → `0.549...` |


value00: docs/calculator-operators.md
value01:
### BMI Calculation

```php
$formula = "{weight} / ({height} ^ 2)";
$variables = [
    'weight' => 70,   // kg
    'height' => 1.75  // meters
];

$result = $calculator->calculate($formula, $variables);
// Result: 22.86
```

### Gender-Based Calculation

```php
// Apply different multipliers based on gender
$formula = "{result} * (1 + 0.1 * {gender})";
$variables = [
    'result' => 100,
    'gender' => 1  // Female = 1
];

$result = $calculator->calculate($formula, $variables);
// Result: 110
```

### Complex Formula

```php
// Pythagorean theorem with rounding
$formula = "round(sqrt({a} ^ 2 + {b} ^ 2))";
$variables = [
    'a' => 3,
    'b' => 4
];

$result = $calculator->calculate($formula, $variables);
// Result: 5
```


value00: tests/README.md
value01:
## Creating Tests

All tests go in the **tests/** directory. Each test file is a class that extends a
**Test Case** (see above) and contains methods for the individual tests. These method
names must start with the word "test" and should have descriptive names for precisely what
they are testing:
`testUserCanModifyFile()` `testOutputColorMatchesInput()` `testIsLoggedInFailsWithInvalidUser()`

Writing tests is an art, and there are many resources available to help learn how.
Review the links above and always pay attention to your code coverage.


value00: docs/calculator-operators.md
value01:
### Inverse Trigonometric Functions

| Function | Aliases | Description | Example |
|----------|---------|-------------|---------|
| `arcsin(x)` | `asin(x)` | Inverse sine | `arcsin(0.5)` → `0.523...` |
| `arccos(x)` | `acos(x)` | Inverse cosine | `arccos(0.5)` → `1.047...` |
| `arctan(x)` | `atan(x)` | Inverse tangent | `arctan(1)` → `0.785...` |
| `arccot(x)` | `acot(x)` | Inverse cotangent | `arccot(1)` → `0.785...` |


value00: tests/README.md
value01:
# Running Application Tests

This is the quick-start to CodeIgniter testing. Its intent is to describe what
it takes to set up your application and get it ready to run unit tests.
It is not intended to be a full description of the test features that you can
use to test your application. Those details can be found in the documentation.

## Resources

* [CodeIgniter 4 User Guide on Testing](https://codeigniter.com/user_guide/testing/index.html)
* [PHPUnit docs](https://phpunit.de/documentation.html)
* [Any tutorials on Unit testing in CI4?](https://forum.codeigniter.com/showthread.php?tid=81830)


value00: docs/calculator-operators.md
value01:
## Formula Validation

Validate formulas before storing them:

```php
$validation = $calculator->validate("{result} / {factor}");
// Returns: ['valid' => true, 'error' => null]

$validation = $calculator->validate("{result} /");
// Returns: ['valid' => false, 'error' => 'Error message']
```

### Extract Variables

Get a list of variables used in a formula:

```php
$variables = $calculator->extractVariables("{result} * {factor} + {age}");
// Returns: ['result', 'factor', 'age']
```

---


value00: docs/calculator-operators.md
value01:
### Logarithmic & Exponential Functions

| Function | Aliases | Description | Example |
|----------|---------|-------------|---------|
| `exp(x)` | - | Exponential (e^x) | `exp(2)` → `7.389...` |
| `log(x)` | `ln(x)` | Natural logarithm (base e) | `log(e)` → `1` |
| `log10(x)` | `lg(x)` | Logarithm base 10 | `log10(100)` → `2` |

---


value00: tests/README.md
value01:
## Setting Up

A number of the tests use a running database.
In order to set up the database edit the details for the `tests` group in
**app/Config/Database.php** or **.env**.
Make sure that you provide a database engine that is currently running on your machine.
More details on a test database setup are in the
[Testing Your Database](https://codeigniter.com/user_guide/testing/database.html) section of the documentation.


value00: tests/README.md
value01:
This runs all of the tests again collecting information about how many lines,
functions, and files are tested. It also reports the percentage of the code that is covered by tests.
It is collected in two formats: a simple text file that provides an overview as well
as a comprehensive collection of HTML files that show the status of every line of code in the project.

The text file can be found at **tests/coverage.txt**.
The HTML files can be viewed by opening **tests/coverage/index.html** in your favorite browser.


value00: tests/README.md
value01:
### Database Tests

Tests can include migrating, seeding, and testing against a mock or live database.
Be sure to modify the test case (or create your own) to point to your seed and migrations
and include any additional steps to be run before tests in the `setUp()` method.
See [Testing Your Database](https://codeigniter.com/user_guide/testing/database.html)
for details.


value00: tests/README.md
value01:
## Uniform Show Endpoints Tests

A specific test suite has been created to ensure all `show` endpoints return a uniform structure (a single object or `null` in the `data` field).

To run this specific test:
```console
> vendor\bin\phpunit tests/feature/UniformShowTest.php
```

This test verifies:
1.  **Uniformity**: All `/api/.../{id}` endpoints return a single associative array (object) in the `data` field when found.
2.  **Null State**: All endpoints return `null` in the `data` field when the record is not found, rather than an empty array or missing key.


value00: tests/README.md
value01:
## Running the tests

The entire test suite can be run by simply typing one command-line command from the main directory.

```console
> ./phpunit
```

If you are using Windows, use the following command.

```console
> vendor\bin\phpunit
```

You can limit tests to those within a single test directory by specifying the
directory name after phpunit.

```console
> ./phpunit app/Models
```


value00: tests/README.md
value01:
## Generating Code Coverage

To generate coverage information, including HTML reports you can view in your browser,
you can use the following command:

```console
> ./phpunit --colors --coverage-text=tests/coverage.txt --coverage-html=tests/coverage/ -d memory_limit=1024m
```


value00: tests/README.md
value01:
## Test Cases

Every test needs a *test case*, or class that your tests extend. CodeIgniter 4
provides one class that you may use directly:
* `CodeIgniter\Test\CIUnitTestCase`

Most of the time you will want to write your own test cases that extend `CIUnitTestCase`
to hold functions and services common to your test suites.


value00: tests/README.md
value01:
## PHPUnit XML Configuration

The repository has a ``phpunit.xml.dist`` file in the project root that's used for
PHPUnit configuration. This is used to provide a default configuration if you
do not have your own configuration file in the project root.

The normal practice would be to copy ``phpunit.xml.dist`` to ``phpunit.xml``
(which is git ignored), and to tailor it as you see fit.
For instance, you might wish to exclude database tests, or automatically generate
HTML code coverage reports.


value00: tests/README.md
value01:
## Requirements

It is recommended to use the latest version of PHPUnit. At the time of this
writing, we are running version 9.x. Support for this has been built into the
**composer.json** file that ships with CodeIgniter and can easily be installed
via [Composer](https://getcomposer.org/) if you don't already have it installed globally.

```console
> composer install
```

If running under macOS or Linux, you can create a symbolic link to make running tests a touch nicer.

```console
> ln -s ./vendor/bin/phpunit ./phpunit
```

You also need to install [XDebug](https://xdebug.org/docs/install) in order
for code coverage to be calculated successfully. After installing `XDebug`, you must add `xdebug.mode=coverage` in the **php.ini** file to enable code coverage.


value00: tests/UniformShowTest_README.md
value01:
## Test Suite

The test suite is located at: `tests/feature/UniformShowTest.php`

### What it tests:
- **`testShowEndpointsReturnObjectWhenFound`**: Iterates through a list of known endpoints and verifies that if data is returned, it is an associative array (object) and not a sequential array.
- **`testShowEndpointsReturnNullWhenNotFound`**: Iterates through endpoints with non-existent IDs and verifies that the `data` field is strictly `null`.

## How to Run

Ensure your test database is configured in `phpunit.xml` or `.env`.

Run the following command from the project root:

```bash
vendor/bin/phpunit tests/feature/UniformShowTest.php
```


value00: tests/UniformShowTest_README.md
value01:
# Uniform Show Endpoints Testing

This document describes the testing strategy and execution for ensuring all API `show` endpoints follow a uniform response structure.

## Requirement

All `show` endpoints (e.g., `/api/location/1`, `/api/patient/1`, etc.) must return:
1.  **A single Object** (associative array) in the `data` field when a record is found.
2.  **Null** in the `data` field when a record is not found.

Previously, some endpoints returned a sequential array with one item, which was inconsistent for frontend development.


value00: tests/UniformShowTest_README.md
value01:
## Maintenance

If you add a new resource with a `show` method, add its endpoint to the `$endpoints` array in `tests/feature/UniformShowTest.php` to ensure it remains compliant with the uniformity requirement.


value00: tests/phpunit-bootstrap.php
value01:
<?php

declare(strict_types=1);

require_once __DIR__ . '/../vendor/codeigniter4/framework/system/Test/bootstrap.php';

use CodeIgniter\Database\MigrationRunner;
use Config\Database;
use Config\Migrations as MigrationsConfig;

if (defined('CLQMS_PHPUNIT_BOOTSTRAPPED') || ENVIRONMENT !== 'testing') {
    return;
}

define('CLQMS_PHPUNIT_BOOTSTRAPPED', true);

$db = Database::connect('tests');
$forge = Database::forge('tests');

$db->query('SET FOREIGN_KEY_CHECKS=0');
foreach ($db->listTables() as $table) {
    $forge->dropTable($table, true);
}
$db->query('SET FOREIGN_KEY_CHECKS=1');

$migrationsConfig = config(MigrationsConfig::class);
$migrationRunner = new MigrationRunner($migrationsConfig, 'tests');


value00: tests/phpunit-bootstrap.php
value01:
$migrationsConfig = config(MigrationsConfig::class);
$migrationRunner = new MigrationRunner($migrationsConfig, 'tests');
try {
    $migrationRunner->latest();
} catch (DatabaseException $e) {
    $message = $e->getMessage();
    if (strpos($message, 'already exists') === false) {
        throw $e;
    }
}

$initialBufferLevel = ob_get_level();
ob_start();
try {
    $seeder = Database::seeder('tests');
    $seeder->setSilent(true)->call('DBSeeder');
} finally {
    while (ob_get_level() > $initialBufferLevel) {
        ob_end_clean();
    }
}


value00: tests/feature/OrganizationControllerTest.php
value01:
    public function testCreateSite()
    {
        $siteData = [
            'SiteCode' => 'S' . substr(time(), -5),
            'SiteName' => 'Test Site ' . time()
        ];

        $result = $this->withHeaders(['Cookie' => 'token=' . $this->token])
                       ->withBody(json_encode($siteData))
                       ->call('post', 'api/organization/site');

        $result->assertStatus(201);
        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsInt($data['data']);
    }


value00: tests/feature/OrganizationControllerTest.php
value01:
    public function testCreateAccount()
    {
        $accountData = [
            'AccountName' => 'Test Account ' . time()
        ];

        $result = $this->withHeaders(['Cookie' => 'token=' . $this->token])
                       ->withBody(json_encode($accountData))
                       ->call('post', 'api/organization/account');

        $result->assertStatus(201);
        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsInt($data['data']);
    }


value00: tests/feature/OrganizationControllerTest.php
value01:
<?php

namespace Tests\Feature;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;

class OrganizationControllerTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected $token;

    protected function setUp(): void
    {
        parent::setUp();

        // Generate JWT Token
        $key = getenv('JWT_SECRET') ?: 'my-secret-key';
        $payload = [
            'iss' => 'localhost',
            'aud' => 'localhost',
            'iat' => time(),
            'nbf' => time(),
            'exp' => time() + 3600,
            'uid' => 1,
            'email' => 'admin@admin.com'
        ];
        $this->token = JWT::encode($payload, $key, 'HS256');
    }


value00: tests/feature/OrganizationControllerTest.php
value01:
    public function testAccountIndexReturnsSuccess()
    {
        $result = $this->callProtected('get', 'api/organization/account');
        $result->assertStatus(200);
        $json = $result->getJSON();
        $data = json_decode($json, true);
        $this->assertEquals('success', $data['status']);
    }

    public function testDepartmentIndexReturnsSuccess()
    {
        $result = $this->callProtected('get', 'api/organization/department');
        $result->assertStatus(200);
        $json = $result->getJSON();
        $data = json_decode($json, true);
        $this->assertEquals('success', $data['status']);
    }


value00: tests/feature/OrganizationControllerTest.php
value01:
    protected function callProtected($method, $path, $params = [])
    {
        return $this->withHeaders(['Cookie' => 'token=' . $this->token])
                    ->call($method, $path, $params);
    }

    public function testSiteIndexReturnsSuccess()
    {
        $result = $this->callProtected('get', 'api/organization/site');
        $result->assertStatus(200);
        $json = $result->getJSON();
        $data = json_decode($json, true);
        $this->assertEquals('success', $data['status']);
    }


value00: tests/feature/OrganizationControllerTest.php
value01:
    public function testCreateContainerDef()
    {
        $conData = [
            'SiteID' => 1,
            'ConCode' => 'C' . substr(time(), -2),
            'ConName' => 'Test Container ' . time()
        ];

        $result = $this->withHeaders(['Cookie' => 'token=' . $this->token])
                       ->withBody(json_encode($conData))
                       ->call('post', 'api/specimen/containerdef');

        $result->assertStatus(201);
        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsInt($data['data']);
    }
}


value00: tests/feature/ContactControllerTest.php
value01:
    protected function callProtected($method, $path, $params = [])
    {
        return $this->withHeaders(['Cookie' => 'token=' . $this->token])
                    ->call($method, $path, $params);
    }

    public function testIndexReturnsSuccess()
    {
        $result = $this->callProtected('get', 'api/contact');

        $result->assertStatus(200);
        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsArray($data['data']);
    }


value00: tests/feature/ContactControllerTest.php
value01:
    public function testCreateContact()
    {
        $contactData = [
            'NameFirst' => 'TestContact' . time(),
            'NameLast' => 'LastName',
            'Specialty' => 'GP',
            'Occupation' => 'MD'
        ];

        $result = $this->withHeaders(['Cookie' => 'token=' . $this->token])
                       ->withBody(json_encode($contactData))
                       ->call('post', 'api/contact');

        $result->assertStatus(201);
        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsArray($data['data']);
        $this->assertEquals('success', $data['data']['status']);
        $this->assertIsInt($data['data']['ContactID']);
    }
}


value00: tests/feature/ContactControllerTest.php
value01:
<?php

namespace Tests\Feature;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Firebase\JWT\JWT;

class ContactControllerTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected $token;

    protected function setUp(): void
    {
        parent::setUp();

        // Generate JWT Token
        $key = getenv('JWT_SECRET') ?: 'my-secret-key';
        $payload = [
            'iss' => 'localhost',
            'aud' => 'localhost',
            'iat' => time(),
            'nbf' => time(),
            'exp' => time() + 3600,
            'uid' => 1,
            'email' => 'admin@admin.com'
        ];
        $this->token = JWT::encode($payload, $key, 'HS256');
    }


value00: tests/feature/ContactControllerTest.php
value01:
    public function testShowReturnsDataIfFound()
    {
        $indexResult = $this->callProtected('get', 'api/contact');
        $indexData = json_decode($indexResult->getJSON(), true);
        
        if (empty($indexData['data'])) {
            $this->markTestSkipped('No contacts found in database to test show.');
        }

        $id = $indexData['data'][0]['ContactID'];
        $result = $this->callProtected('get', "api/contact/$id");

        $result->assertStatus(200);
        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsArray($data['data']);
        $this->assertEquals($id, $data['data']['ContactID']);
    }


value00: tests/feature/Organization/CodingSysControllerTest.php
value01:
<?php

namespace Tests\Feature\Organization;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;

class CodingSysControllerTest extends CIUnitTestCase
{
    use FeatureTestTrait;


value00: tests/feature/Organization/CodingSysControllerTest.php
value01:
class CodingSysControllerTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected $endpoint = 'api/organization/codingsys';

    public function testIndexCodingSys()
    {
        $result = $this->get($this->endpoint);
        $result->assertStatus(200);
    }

    public function testCreateCodingSys()
    {
        $payload = [
            'CodingSysAbb' => 'ICD' . substr(time(), -3),
            'FullText' => 'International Classification of Diseases 10 ' . time(),
            'Description' => 'Medical diagnosis coding system'
        ];

        $result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
        $result->assertStatus(201);
    }
}


value00: tests/feature/Organization/HostAppControllerTest.php
value01:
class HostAppControllerTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected $endpoint = 'api/organization/hostapp';

    protected function setUp(): void
    {
        parent::setUp();
    }

    public function testIndexHostApp()
    {
        $result = $this->get($this->endpoint);
        $result->assertStatus(200);
    }

    public function testCreateHostApp()
    {
        $payload = [
            'HostAppID' => 'TEST1',
            'HostAppName' => 'Test Host Application',
            'SiteID' => null
        ];

        $result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
        $result->assertStatus(201);
    }
}


value00: tests/feature/Organization/HostAppControllerTest.php
value01:
<?php

namespace Tests\Feature\Organization;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;

class HostAppControllerTest extends CIUnitTestCase
{
    use FeatureTestTrait;


value00: tests/feature/Patients/PatientIndexTest.php
value01:
    /**
     *   Case 6: parameter Birthdate → return data sesuai tanggal - Passed
     */
    public function testIndexWithBirthdate() {
        $result = $this->call('get', $this->endpoint, [
            'Birthdate' => '2000-01-01'
        ]);

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        if (!empty($data['data'])) {
            $this->assertEquals('2000-01-01', $data['data'][0]['Birthdate']);
        }
    }

    /**
     *   Case 7: Simulasi server error (optional, jika bisa trigger) - Passed
     */


value00: tests/feature/Patients/PatientIndexTest.php
value01:
    /**
     *   Case 2: parameter Name yang tidak ada → return kosong [] - Passed
     */
    public function testIndexWithWrongNameParam() {
        $result = $this->call('get', $this->endpoint, [
            'Name' => 'TidakAdaNama'
        ]);

        $result->assertStatus(200);
        $result->assertJSONFragment([
            'status' => 'success',
            'data'   => []
        ]);
    }

    /**
     *   Case 3: parameter Name benar → return array tidak kosong - Passed
     */


value00: tests/feature/Patients/PatientIndexTest.php
value01:
    /**
     *   Case 4: parameter InternalPID → return data sesuai ID - Passed
     */
    public function testIndexWithInternalPID() {
        $result = $this->call('get', $this->endpoint, [
            'InternalPID' => 1
        ]);

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        if (!empty($data['data'])) {
            $this->assertEquals(1, $data['data'][0]['InternalPID']);
        }
    }

    /**
     *   Case 5: parameter PatientID → return data sesuai PatientID - Passed
     */


value00: tests/feature/Patients/PatientIndexTest.php
value01:
    /**
     *   Case 7: Simulasi server error (optional, jika bisa trigger) - Passed
     */
    public function testIndexServerErrorSimulation() {
        // Misalnya panggil dengan param aneh untuk trigger exception
        $result = $this->call('get', $this->endpoint, [
            'InternalPID' => "' OR 1=2 --"
        ]);

        // Boleh assert 200 (jika sanitasi bagus) atau 500 (kalau exception)
        $this->assertContains($result->getStatusCode(), [200, 500, null]);
    }

}


value00: tests/feature/Patients/PatientIndexTest.php
value01:
<?php

namespace Tests\Feature\Patients;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;

class PatientIndexTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected $endpoint = 'api/patient';

    protected function setUp(): void
    {
        parent::setUp();
    }

    /**
     *   Case 1: tanpa parameter, harus 200 dan status success - Passed
     */
    public function testIndexWithoutParams() {
        $result = $this->call('get', $this->endpoint);

        $result->assertStatus(200);
        $result->assertJSONFragment([
            'status' => 'success',
        ]);
    }

    /**
     *   Case 2: parameter Name yang tidak ada → return kosong [] - Passed
     */


value00: tests/feature/Patients/PatientIndexTest.php
value01:
    /**
     *   Case 5: parameter PatientID → return data sesuai PatientID - Passed
     */
    public function testIndexWithPatientID() {
        $result = $this->call('get', $this->endpoint, [
            'PatientID' => '123'
        ]);

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        if (!empty($data['data'])) {
            $this->assertStringContainsString('123', $data['data'][0]['PatientID']);
        }
    }

    /**
     *   Case 6: parameter Birthdate → return data sesuai tanggal - Passed
     */


value00: tests/feature/Patients/PatientIndexTest.php
value01:
    /**
     *   Case 3: parameter Name benar → return array tidak kosong - Passed
     */
    public function testIndexWithCorrectNameParam() {
        // Sesuaikan dengan data di database test Anda
        $result = $this->call('get', $this->endpoint, [
            'Name' => 'Jane'
        ]);

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertNotEmpty($data['data']);
        $this->assertIsArray($data['data']);
    }

    /**
     *   Case 4: parameter InternalPID → return data sesuai ID - Passed
     */


value00: tests/feature/Patients/PatientCreateTest.php
value01:
        $result->assertStatus(400);
        
        // Test dengan PatiD yg sudah ada
        // Kondisi Jika PatiD Sama
        $payload = [
            "PatientID"=> "SMAJ1",
            "AlternatePID"=> "ALT001234",
            "Prefix"=> "Mr.",
            "NameFirst"=> "Budi",
            "NameMiddle"=> "Santoso",
            "NameMaiden"=> "Kiki",
            "NameLast"=> "Wijaya",
            "Suffix"=> "S.kom",
            "NameAlias"=> "Bud",
            "Sex"=> "1",
        ];
        $result = $this->withBodyFormat('json')->call('post', 'api/patient', $payload);
        $result->assertStatus(400);

    }


value00: tests/feature/Patients/PatientCreateTest.php
value01:
    // 201 - Passed
    // Test Sukses Dengan User Dummy
    public function testCreatePatientSuccess() {

        $faker = Factory::create('id_ID');
        
        for ($i = 0; $i < 2; $i++) {
            $payload = [
                "PatientID"      => "DUM" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
                "AlternatePID"   => "DMY" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
            "Suffix"         => "S.Kom",
            "NameAlias"      => $faker->userName,
            "Sex"         => $faker->numberBetween(5, 6),
            "PlaceOfBirth"   => $faker->city,
            "Birthdate"      => $faker->date('Y-m-d'),
            "ZIP"            => $faker->postcode,
            "Street_1"       => $faker->streetAddress,
            "Street_2"       => "RT " . $faker->numberBetween(1, 10) . " RW " . $faker->numberBetween(1, 10),
            "Street_3"       => "Blok " . $faker->numberBetween(1, 20),
            "City"           => $faker->city


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
                "AlternatePID"   => "DMY" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
                "Prefix"         => $faker->title,
                "NameFirst"      => $faker->firstName,
                "NameMiddle"     => $faker->firstName,
                "NameMaiden"     => $faker->firstName,
                "NameLast"       => $faker->lastName,
                "Suffix"         => "S.Kom",
                "NameAlias"      => $faker->userName,
                "Sex"         => $faker->numberBetween(5, 6)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
                "DeathIndicator" => (string) $faker->numberBetween(16, 17),
                "LinkTo"         => (string) $faker->numberBetween(2, 3),
                "Custodian"      => $i-1,
                "PatIdt" => [
                    "IdentifierType" => "KTP",
                    "Identifier"     => $faker->nik() ?? $faker->numerify('################')
                ],
                "PatAtt" => [
                    [ "Address" => "/api/upload/" . $faker->uuid . ".jpg" ]
                ]


value00: tests/feature/Patients/PatientCreateTest.php
value01:
            "Suffix"         => "S.Kom",
            "NameAlias"      => $faker->userName,
            "Sex"         => $faker->numberBetween(5, 6),
            "PlaceOfBirth"   => $faker->city,
            "Birthdate"      => $faker->date('Y-m-d'),
            "ZIP"            => $faker->postcode,
            "Street_1"       => $faker->streetAddress,
            "Street_2"       => "RT " . $faker->numberBetween(1, 10) . " RW " . $faker->numberBetween(1, 10),
            "Street_3"       => "Blok " . $faker->numberBetween(1, 20),
            "City"           => $faker->city,
            "Province"       => $faker->state,
            "EmailAddress1"  => "AAQ" . (string)$faker->numberBetween(1, 1110).'@gmail.com'


value00: tests/feature/Patients/PatientCreateTest.php
value01:
                "PatAtt" => [
                    [ "Address" => "/api/upload/" . $faker->uuid . ".jpg" ]
                ],
                "PatCom" => $faker->sentence,
            ];
            if($payload['DeathIndicator'] == '16') {
                $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
            } else {
                $payload['DeathDateTime'] = null;
            }

            $result = $this->withBodyFormat('json')->call('post', 'api/patient', $payload);



            $result->assertStatus(201);
        }
    }

    // Error - Passed
    // Test dengan user dummy dan harus error


value00: tests/feature/Patients/PatientCreateTest.php
value01:
<?php

namespace Tests\Feature\Patients;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Faker\Factory;

class PatientCreateTest extends CIUnitTestCase
{
    use FeatureTestTrait;
    protected $endpoint = 'api/patient';

    protected function setUp(): void
    {
        parent::setUp();
    }
    
    // 400 - Passed 
    // Validation Gagal - Array Tidak Complete
    public function testCreatePatientValidationFail() {

        // Test dengan payload yg tidak lengkap
        // error 400 yg diharapkan
        $payload = ['Name' => 'Ngawur'];
        $result = $this->withBodyFormat('json')->call('post', 'api/patient', $payload);
        $result->assertStatus(400);
        
        // Test dengan PatiD yg sudah ada


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
                "NameAlias"      => $faker->userName,
                "Sex"         => $faker->numberBetween(5, 6),
                "PlaceOfBirth"   => $faker->city,
                "Birthdate"      => $faker->date('Y-m-d'),
                "ZIP"            => $faker->postcode,
                "Street_1"       => $faker->streetAddress,
                "Street_2"       => "RT " . $faker->numberBetween(1, 10) . " RW " . $faker->numberBetween(1, 10),
                "Street_3"       => "Blok " . $faker->numberBetween(1, 20),
                "City"           => $faker->city,
                "Province"       => $faker->state,
                "EmailAddress1"  => "A" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000).'@gmail.com'


value00: tests/feature/Patients/PatientCreateTest.php
value01:
            "Prefix"         => $faker->title,
            "NameFirst"      => $faker->firstName,
            "NameMiddle"     => $faker->firstName,
            "NameMaiden"     => $faker->firstName,
            "NameLast"       => $faker->lastName,
            "Suffix"         => "S.Kom",
            "NameAlias"      => $faker->userName,
            "Sex"         => $faker->numberBetween(5, 6)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
            "Street_3"       => "Blok " . $faker->numberBetween(1, 20),
            "City"           => $faker->city,
            "Province"       => $faker->state,
            "EmailAddress1"  => "AiaAe" . $faker->numberBetween(1, 1110).$faker->numberBetween(1, 1110).'@gmail.com',
            "EmailAddress2"  => "BiaAe" . $faker->numberBetween(1, 1110).$faker->numberBetween(1, 1110).'@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),
            "Race"           => (string) $faker->numberBetween(175, 205),
            "Country"        => (string) $faker->numberBetween(221, 469),
            "MaritalStatus"  => (string) $faker->numberBetween(8, 15)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
    // 500 - Passed
    // Test dengan user dummy dan harus gagal - identifier tidak valid
    public function testCreateDatabaseError() {
        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"      => "DaUALU" . $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
            "AlternatePID"   => "DaMALU" . $faker->numberBetween(5, 1000).$faker->numberBetween(1, 1000),
            "Prefix"         => $faker->title,
            "NameFirst"      => $faker->firstName,
            "NameMiddle"     => $faker->firstName


value00: tests/feature/Patients/PatientCreateTest.php
value01:
            "Religion"       => (string) $faker->numberBetween(206, 212),
            "Ethnic"         => (string) $faker->numberBetween(213, 220),
            "Citizenship"    => "WNI",
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            "LinkTo"         => (string) $faker->numberBetween(2, 3)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
            "Province"       => $faker->state,
            "EmailAddress1"  => "AAQ" . (string)$faker->numberBetween(1, 1110).'@gmail.com',
            "EmailAddress2"  => "BAQ" . (string)$faker->numberBetween(1, 1110).'@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),
            "Race"           => (string) $faker->numberBetween(175, 205),
            "Country"        => (string) $faker->numberBetween(221, 469),
            "MaritalStatus"  => (string) $faker->numberBetween(8, 15),
            "Religion"       => (string) $faker->numberBetween(206, 212),
            "Ethnic"         => (string) $faker->numberBetween(213, 220)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
                [ "Address" => "/api/upload/test3_" . $faker->uuid . ".jpg" ]
            ],
            "PatCom" => null,
        ];

        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }

        $result = $this->withBodyFormat('json')->post($this->endpoint, $payload);

        $result->assertStatus(201);
    }

    // 500 - Passed
    // Test dengan user dummy dan harus gagal - identifier tidak valid


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
            "Street_3"       => "Blok " . $faker->numberBetween(1, 20),
            "City"           => $faker->city,
            "Province"       => $faker->state,
            "EmailAddress1"  => "AiA" . $faker->numberBetween(1, 1110).$faker->numberBetween(1, 1110).'@gmail.com',
            "EmailAddress2"  => "BiA" . $faker->numberBetween(1, 1110).$faker->numberBetween(1, 1110).'@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),
            "Race"           => (string) $faker->numberBetween(175, 205),
            "Country"        => (string) $faker->numberBetween(221, 469)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
            "Country"        => (string) $faker->numberBetween(221, 469),
            "MaritalStatus"  => (string) $faker->numberBetween(8, 15),
            "Religion"       => (string) $faker->numberBetween(206, 212),
            "Ethnic"         => (string) $faker->numberBetween(213, 220),
            "Citizenship"    => "WNI",
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            "LinkTo"         => (string) $faker->numberBetween(2, 3)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
            "PatAtt" => [
                [ "Address" => "/api/upload/" . $faker->word . ".jpg" ]
            ],
            "PatCom" => $faker->sentence,
        ];

        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }

        $result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        $this->assertArrayHasKey("messages", $data);
    }


value00: tests/feature/Patients/PatientCreateTest.php
value01:
;

        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }
        
        $result = $this->withBodyFormat('json')->post($this->endpoint, $payload);


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            "LinkTo"         => (string) $faker->numberBetween(2, 3),
            "Custodian"      => 1,
            "PatIdt" => [
                "IdentifierType" => [], // Ini Salah
                "Identifier"     => $faker->nik() ?? $faker->numerify('################')
            ],
            "PatAtt" => [
                [ "Address" => "/api/upload/asasd" . $faker->word . ".jpg" ],
                [ "Address" => "/api/upload/adsds" . $faker->word . ".jpg" ],
                [ "Address" => "/api/upload/sdjs" . $faker->word . ".jpg" ]
            ],
            "PatCom" => [], 
        ]


value00: tests/feature/Patients/PatientCreateTest.php
value01:
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            "LinkTo"         => (string) $faker->numberBetween(2, 3),
            "Custodian"      => 1,
            "PatIdt" => [
                "IdentifierType" => "KTP",
                // Identifier lebih dari 255 karakter - error validasi
            ],
            "PatAtt" => [
                [ "Address" => "/api/upload/" . $faker->word . ".jpg" ]
            ]


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            "LinkTo"         => (string) $faker->numberBetween(2, 3),
            "Custodian"      => 1,
            "PatIdt" => [
                "IdentifierType" => "KTP",
                "Identifier"     => $faker->nik() ?? $faker->numerify('################')
            ],
            "PatAtt" => [
                [ "Address" => "/api/upload/test1_" . $faker->uuid . ".jpg" ],
                [ "Address" => "/api/upload/test2_" . $faker->uuid . ".jpg" ],
                [ "Address" => "/api/upload/test3_" . $faker->uuid . ".jpg" ]
            ],
            "PatCom" => null,
        ]


value00: tests/feature/Patients/PatientCreateTest.php
value01:
                "MobilePhone"    => $faker->numerify('08##########'),
                "Race"           => (string) $faker->numberBetween(175, 205),
                "Country"        => (string) $faker->numberBetween(221, 469),
                "MaritalStatus"  => (string) $faker->numberBetween(8, 15),
                "Religion"       => (string) $faker->numberBetween(206, 212),
                "Ethnic"         => (string) $faker->numberBetween(213, 220),
                "Citizenship"    => "WNI",
                "DeathIndicator" => (string) $faker->numberBetween(16, 17),
                "LinkTo"         => (string) $faker->numberBetween(2, 3)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
        $result = $this->withBodyFormat('json')->post($this->endpoint, $payload);
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        // Since we fixed the controller, this might return success or validation error, but not crash.
        // If success (201), data has 'status'=>'success'. If validation error (400), data has 'messages'.
        // Let's just assert it is NOT 500 first.
        if ($result->response()->getStatusCode() !== 500) {
              $this->fail("Expected 500 Server Error but got " . $result->response()->getStatusCode() . " Body: " . $json);
        }
        $this->assertEquals(500, $result->response()->getStatusCode());
    }

}


value00: tests/feature/Patients/PatientCreateTest.php
value01:
    // Error - Passed
    // Test dengan user dummy dan harus error
    public function testCreatePatidtValidationError() {

        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"      => "DUMQ" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
            "AlternatePID"   => "DMYQ" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
            "Prefix"         => $faker->title,
            "NameFirst"      => $faker->firstName,
            "NameMiddle"     => $faker->firstName


value00: tests/feature/Patients/PatientCreateTest.php
value01:
    public function testCreateWithoutAttachments() {
        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"      => "DUAU" . $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
            "AlternatePID"   => "DMAU" . $faker->numberBetween(5, 1000).$faker->numberBetween(1, 1000),
            "Prefix"         => $faker->title,
            "NameFirst"      => $faker->firstName,
            "NameMiddle"     => $faker->firstName,
            "NameMaiden"     => $faker->firstName,
            "NameLast"       => $faker->lastName,
            "Suffix"         => "S.Kom",
            "NameAlias"      => $faker->userName,
            "Sex"         => $faker->numberBetween(5, 6)


value00: tests/feature/Patients/PatientCreateTest.php
value01:
            "Race"           => (string) $faker->numberBetween(175, 205),
            "Country"        => (string) $faker->numberBetween(221, 469),
            "MaritalStatus"  => (string) $faker->numberBetween(8, 15),
            "Religion"       => (string) $faker->numberBetween(206, 212),
            "Ethnic"         => (string) $faker->numberBetween(213, 220),
            "Citizenship"    => "WNI",
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            "LinkTo"         => (string) $faker->numberBetween(2, 3)


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    /**
     * Test Case 1: Delete without InternalPID key
     * Expected: 500 Internal Server Error (Undefined array key)
     */
    public function testDeleteWithoutInternalPID() 
    {
        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, []);

        $result->assertStatus(500);
        
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        $this->assertArrayHasKey('messages', $data);
    }

    /**
     * Test Case 2: Delete with null InternalPID
     * Expected: 400 Bad Request - Patient ID must be a valid integer
     */


value00: tests/feature/Patients/PatientCreateTest.php
value01:
    // 201 - Passed
    // Test dengan user dummy dan harus berhasil - PatCom kosong
    public function testCreateWithoutPatComments() {
        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"      => "DUALU" . $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000),
            "AlternatePID"   => "DMALU" . $faker->numberBetween(5, 1000).$faker->numberBetween(1, 1000),
            "Prefix"         => $faker->title,
            "NameFirst"      => $faker->firstName,
            "NameMiddle"     => $faker->firstName


value00: tests/feature/Patients/PatientCreateTest.php
value01:
,
            "Street_3"       => "Blok " . $faker->numberBetween(1, 20),
            "City"           => $faker->city,
            "Province"       => $faker->state,
            "EmailAddress1"  => "AiAe" . $faker->numberBetween(1, 1110).$faker->numberBetween(1, 1110).'@gmail.com',
            "EmailAddress2"  => "BiAe" . $faker->numberBetween(1, 1110).$faker->numberBetween(1, 1110).'@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),
            "Race"           => (string) $faker->numberBetween(175, 205),
            "Country"        => (string) $faker->numberBetween(221, 469)


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
<?php

namespace Tests\Feature\Patients;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Faker\Factory;

/**
 * PatientDeleteTest
 * 
 * Test cases for Patient DELETE endpoint
 * Endpoint: DELETE api/patient
 *


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    //  * 
    //  * IMPORTANT: This test modifies database state.
    //  * Make sure you have proper seeding or use DatabaseTestTrait
    //  */
    // public function testDeleteSuccess() 
    // {
    //     // First create a patient to delete
    //     $faker = Factory::create('id_ID');
    //     
    //     $createPayload = [
    //         "PatientID"      => "DELTEST" . $faker->numberBetween(10000, 99999),
    //         "AlternatePID"   => "DELALT" . $faker->numberBetween(10000, 99999),
    //         "Prefix"         => "Mr.",
    //         "NameFirst"      => "ToBeDeleted",
    //         "NameLast"       => "Patient",
    //         "Sex"         => "5",
    //         "Birthdate"      => "1990-01-01",


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    public function testDeleteNotFound() 
    {
        $testId = 9999999;

        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, [
                            'InternalPID' => $testId
                       ]);

        // May return 404 (not found), 500 (database error), or null (connection issue)
        $statusCode = $result->getStatusCode();
        $this->assertTrue(
            in_array($statusCode, [404, 500]) || $statusCode === null,
            "Expected status code 404, 500, or null but got: " . var_export($statusCode, true)
        );
        
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        $this->assertArrayHasKey('messages', $data);
    }


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    /**
     * Test Case 3: Delete with zero InternalPID
     * Expected: 400 Bad Request - Patient ID must be a valid integer
     */
    public function testDeleteWithZeroInternalPID() 
    {
        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, [
                            'InternalPID' => 0,
                       ]);

        $result->assertStatus(400);
        $result->assertJSONFragment([
            'status' => 'error',
            'message' => 'Patient ID must be a valid integer.'
        ]);
    }

    /**
     * Test Case 4: Delete with empty string InternalPID
     * Expected: 400 Bad Request - Patient ID must be a valid integer
     */


value00: tests/feature/Patients/PatientCreateTest.php
value01:
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            "LinkTo"         => (string) $faker->numberBetween(2, 3),
            "Custodian"      => 1,
            "PatIdt" => [
                "IdentifierType" => "KTP",
                "Identifier"     => $faker->nik() ?? $faker->numerify('################')
            ],
            "PatAtt" => [],
            "PatCom" => $faker->sentence,
        ];

        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }

        $result = $this->withBodyFormat('json')->post($this->endpoint, $payload);

        $result->assertStatus(201);
    }


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    // =====================================================
    // SUCCESS TEST CASES
    // NOTE: Uncomment these when you have proper DB seeding
    // =====================================================

    // /**
    //  * Test Case: Delete success
    //  * Expected: 200 OK - Patient deleted successfully
    //  * 
    //  * IMPORTANT: This test modifies database state.
    //  * Make sure you have proper seeding or use DatabaseTestTrait


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
class PatientDeleteTest extends CIUnitTestCase
{
    use FeatureTestTrait;
    
    protected $endpoint = 'api/patient';

    protected function setUp(): void
    {
        parent::setUp();
    }

    /**
     * Test Case 1: Delete without InternalPID key
     * Expected: 500 Internal Server Error (Undefined array key)
     */


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    //         "NameLast"       => "Patient",
    //         "Sex"         => "5",
    //         "Birthdate"      => "1990-01-01",
    //     ];
    //     
    //     // Create the patient
    //     $createResult = $this->withBodyFormat('json')->post($this->endpoint, $createPayload);
    //     $createResult->assertStatus(201);


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    /**
     * Test Case 7: Delete with SQL injection attempt
     * Expected: 400 Bad Request - should be sanitized
     */
    public function testDeleteSQLInjectionAttempt() 
    {
        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, [
                            'InternalPID' => "' OR 1=1 --"
                       ]);
        
        $result->assertStatus(400);
        $result->assertJSONFragment([
            'status' => 'error',
            'message' => 'Patient ID must be a valid integer.'
        ]);
    }

    /**
     * Test Case 8: Delete with negative number
     * Expected: 400 Bad Request - should reject negative numbers
     */


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    /**
     * Test Case 8: Delete with negative number
     * Expected: 400 Bad Request - should reject negative numbers
     */
    public function testDeleteWithNegativeInternalPID() 
    {
        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, [
                            'InternalPID' => -1,
                       ]);

        $result->assertStatus(400);
        $result->assertJSONFragment([
            'status' => 'error',
            'message' => 'Patient ID must be a valid integer.'
        ]);
    }

    /**
     * Test Case 9: Delete with string number
     * Expected: Depends on implementation - may accept "123" as valid
     */


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    /**
     * Test Case 5: Delete with array InternalPID
     * Expected: 400 Bad Request - Patient ID must be a valid integer
     */
    public function testDeleteWithArrayInternalPID() 
    {
        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, [
                            'InternalPID' => [],
                       ]);

        $result->assertStatus(400);
        $result->assertJSONFragment([
            'status' => 'error',
            'message' => 'Patient ID must be a valid integer.'
        ]);
    }

    /**
     * Test Case 6: Delete non-existent patient
     * Expected: 404 Not Found (or 500 if database not configured)
     */


value00: tests/feature/Patients/PatientCreateTest.php
value01:
                "EmailAddress1"  => "A" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000).'@gmail.com',
                "EmailAddress2"  => "B" . $faker->numberBetween(1, 1000). $faker->numberBetween(1, 1000).$faker->numberBetween(1, 1000).'@gmail.com',
                "Phone"          => $faker->numerify('08##########'),
                "MobilePhone"    => $faker->numerify('08##########'),
                "Race"           => (string) $faker->numberBetween(175, 205)


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    public function testDeleteWithEmptyStringInternalPID() 
    {
        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, [
                            'InternalPID' => '',
                       ]);

        $result->assertStatus(400);
        $result->assertJSONFragment([
            'status' => 'error',
            'message' => 'Patient ID must be a valid integer.'
        ]);
    }

    /**
     * Test Case 5: Delete with array InternalPID
     * Expected: 400 Bad Request - Patient ID must be a valid integer
     */


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
use Faker\Factory;

/**
 * PatientDeleteTest
 * 
 * Test cases for Patient DELETE endpoint
 * Endpoint: DELETE api/patient
 * 
 * Test Cases:
 * 1. testDeleteWithoutInternalPID - 500 error when InternalPID not provided
 * 2. testDeleteWithNullInternalPID - 400 error when InternalPID is null
 * 3. testDeleteWithZeroInternalPID - 400 error when InternalPID is 0
 * 4. testDeleteWithEmptyStringInternalPID - 400 error when InternalPID is empty string
 * 5. testDeleteWithArrayInternalPID - 400 error when InternalPID is array
 * 6. testDeleteNotFound - 404 error when patient not found
 * 7. testDeleteSQLInjectionAttempt - 400 error when SQL injection attempted
 * 8. testDeleteSuccess - 200 success when valid delete (commented - requires DB setup)
 */


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    //     $createResult = $this->withBodyFormat('json')->post($this->endpoint, $createPayload);
    //     $createResult->assertStatus(201);
    //     
    //     // Get the created patient ID from response or database
    //     // Then delete it
    //     $testId = 3; // Replace with actual ID
    //     
    //     $result = $this->withBodyFormat('json')
    //                    ->delete($this->endpoint, [
    //                         'InternalPID' => $testId
    //                    ]);
    //
    //     $result->assertStatus(200);
    //     $result->assertJSONFragment([
    //         'status' => 'success',
    //         'message' => 'Patient ID with '.$testId.' deleted successfully.'
    //     ]);
    // }
}


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    /**
     * Test Case 2: Delete with null InternalPID
     * Expected: 400 Bad Request - Patient ID must be a valid integer
     */
    public function testDeleteWithNullInternalPID() 
    {
        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, [
                            'InternalPID' => null,
                       ]);

        $result->assertStatus(400);
        $result->assertJSONFragment([
            'status' => 'error',
            'message' => 'Patient ID must be a valid integer.'
        ]);
    }

    /**
     * Test Case 3: Delete with zero InternalPID
     * Expected: 400 Bad Request - Patient ID must be a valid integer
     */


value00: tests/feature/Patients/PatientDeleteTest.php
value01:
    /**
     * Test Case 9: Delete with string number
     * Expected: Depends on implementation - may accept "123" as valid
     */
    public function testDeleteWithStringNumber() 
    {
        $testId = "9999998";

        $result = $this->withBodyFormat('json')
                       ->delete($this->endpoint, [
                            'InternalPID' => $testId
                       ]);

        // Should either be 404 (not found), 200 (deleted), 500 (db error), or null (connection issue)
        $statusCode = $result->getStatusCode();
        $this->assertTrue(
            in_array($statusCode, [200, 404, 500]) || $statusCode === null,
            "Expected status code 200, 404, 500, or null but got: " . var_export($statusCode, true)
        );
    }


value00: tests/feature/Patients/PatientCheckTest.php
value01:
<?php

namespace Tests\Feature\Patients;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Faker\Factory;

/**
 * PatientCheckTest
 * 
 * Test cases for Patient Check endpoint
 * Endpoint: GET api/patient/check


value00: tests/feature/Patients/PatientCheckTest.php
value01:
use Faker\Factory;

/**
 * PatientCheckTest
 * 
 * Test cases for Patient Check endpoint
 * Endpoint: GET api/patient/check
 * 
 * This endpoint checks if a PatientID or EmailAddress1 already exists in the database.
 * Returns data: true if NOT found (available), false if found (already exists)
 * 
 * Test Cases:
 * 1. testCheckPatientIDExists - Check existing PatientID
 * 2. testCheckPatientIDNotExists - Check non-existing PatientID
 * 3. testCheckEmailExists - Check existing Email
 * 4. testCheckEmailNotExists - Check non-existing Email
 * 5. testCheckWithoutParams - Check without any parameters
 * 6. testCheckWithBothParams - Check with both parameters (PatientID takes priority)
 */
class PatientCheckTest extends CIUnitTestCase


value00: tests/feature/Patients/PatientCheckTest.php
value01:
    public function testCheckPatientIDExists()
    {
        // Assuming SMAJ1 exists in the test database
        $result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
            'PatientID' => 'SMAJ1'
        ]);

        $result->assertStatus(200);
        
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        $this->assertEquals('success', $data['status']);
        // If patient exists, data should be false (not available)
        if ($data['data'] === false) {
            $this->assertStringContainsString('already exists', $data['message']);
        }
    }

    /**
     * Test Case 2: Check non-existing PatientID
     * Expected: 200 OK with data: true (available)
     */


value00: tests/feature/Patients/PatientCheckTest.php
value01:
    /**
     * Test Case 2: Check non-existing PatientID
     * Expected: 200 OK with data: true (available)
     */
    public function testCheckPatientIDNotExists()
    {
        $faker = Factory::create();
        
        $result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
            'PatientID' => 'NONEXISTENT' . $faker->numberBetween(100000, 999999)
        ]);

        $result->assertStatus(200);
        
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        $this->assertEquals('success', $data['status']);
        $this->assertTrue($data['data']); // Available means true
        $this->assertStringContainsString('not found', $data['message']);
    }


value00: tests/feature/Patients/PatientCheckTest.php
value01:
    /**
     * Test Case 3: Check existing Email
     * Expected: 200 OK with data: false (already exists)
     * 
     * NOTE: Requires a known email in the database
     */
    public function testCheckEmailFormat()
    {
        $result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
            'EmailAddress1' => 'test@example.com'
        ]);

        $result->assertStatus(200);
        
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        $this->assertEquals('success', $data['status']);
        $this->assertIsBool($data['data']);
    }

    /**
     * Test Case 4: Check non-existing Email
     * Expected: 200 OK with data: true (available)
     */


value00: tests/feature/Patients/PatientCheckTest.php
value01:
        // Should not crash - either 200 or appropriate error
        // Workaround for environment wrapping
        if ($result->getStatusCode() === null) {
            $body = strip_tags($result->getBody());
            $data = json_decode($body, true);
            $this->assertEquals('success', $data['status'] ?? '');
            // message could be 'PatientID not found.' or '... already exists.'
            $this->assertArrayHasKey('message', $data);
            return;
        }


        $this->assertContains($result->getStatusCode(), [200, 400, 500]);
    }


value00: tests/feature/Patients/PatientCheckTest.php
value01:
    /**
     * Test Case 8: Check with very long PatientID
     * Expected: Should handle gracefully
     */
    public function testCheckWithVeryLongPatientID()
    {
        $longId = str_repeat('A', 100);
        
        $result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
            'PatientID' => $longId
        ]);

        $result->assertStatus(200);
        
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        // Long ID that doesn't exist should return true (available)
        $this->assertEquals('success', $data['status']);
    }
}


value00: tests/feature/Patients/PatientCheckTest.php
value01:
    public function testCheckEmailNotExists()
    {
        $faker = Factory::create();
        
        $result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
            'EmailAddress1' => 'nonexistent_' . $faker->numberBetween(100000, 999999) . '@test.com'
        ]);

        $result->assertStatus(200);
        
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        $this->assertEquals('success', $data['status']);
        $this->assertTrue($data['data']); // Available means true
        $this->assertStringContainsString('not found', $data['message']);
    }

    /**
     * Test Case 5: Check with empty PatientID
     * Expected: May return 500 or handle gracefully
     */


value00: tests/feature/PatVisit/PatVisitShowTest.php
value01:
<?php

namespace Tests\Feature\PatVisit;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;

class PatVisitShowTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected $endpoint = 'api/patvisit';

    protected function setUp(): void
    {
        parent::setUp();
    }


value00: tests/feature/PatVisit/PatVisitShowTest.php
value01:
    protected $endpoint = 'api/patvisit';

    protected function setUp(): void
    {
        parent::setUp();
    }

    public function testShowPatientVisitSuccess()
    {
        $PVID = '1';

        $response = $this->get($this->endpoint .'/'. $PVID);

        $response->assertStatus(200);

        $response->assertJSONFragment([
            'status' => 'success',
            'message' => 'data not found',
        ]);

        $json = json_decode($response->getJSON(), true);

        $this->assertArrayHasKey('data', $json);
        $this->assertIsArray($json['data']);
    }

    /**
     * Test: Show patient visit with invalid/nonexistent PVID
     */


value00: tests/feature/Patients/PatientCheckTest.php
value01:
    /**
     * Test Case 5: Check with empty PatientID
     * Expected: May return 500 or handle gracefully
     */
    public function testCheckWithEmptyPatientID()
    {
        $result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
            'PatientID' => ''
        ]);

        // Should either return 200 (graceful handling) or 500 (error)
        // Workaround for environment wrapping JSON in HTML causing null status code


value00: tests/feature/Patients/PatientCheckTest.php
value01:
        // Workaround for environment wrapping JSON in HTML causing null status code
        if ($result->getStatusCode() === null) {
            $body = strip_tags($result->getBody());
            $data = json_decode($body, true);
            $this->assertEquals('error', $data['status'] ?? '');
            $this->assertEquals('PatientID or EmailAddress1 parameter is required.', $data['message'] ?? '');
            return;
        }

        $this->assertContains($result->getStatusCode(), [200, 400, 500]);
    }


value00: tests/feature/Patients/PatientCheckTest.php
value01:
    /**
     * Test Case 6: Check for response structure
     * Expected: Response should have status, message, and data keys
     */
    public function testCheckResponseStructure()
    {
        $result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
            'PatientID' => 'TEST123'
        ]);

        $result->assertStatus(200);
        
        $json = $result->getJSON();
        $data = json_decode($json, true);
        
        $this->assertArrayHasKey('status', $data);
        $this->assertArrayHasKey('message', $data);
        $this->assertArrayHasKey('data', $data);
    }

    /**
     * Test Case 7: Check with special characters in PatientID
     * Expected: Should handle gracefully
     */


value00: tests/feature/Patients/PatientCheckTest.php
value01:
{
    use FeatureTestTrait;
    
    protected $endpoint = 'api/patient/check';

    protected function setUp(): void
    {
        parent::setUp();
    }

    /**
     * Test Case 1: Check existing PatientID
     * Expected: 200 OK with data: false (already exists)
     */


value00: tests/feature/Patients/PatientCheckTest.php
value01:
    /**
     * Test Case 7: Check with special characters in PatientID
     * Expected: Should handle gracefully
     */
    public function testCheckWithSpecialCharacters()
    {
        $result = $this->withHeaders(['Accept' => 'application/json'])->call('get', $this->endpoint, [
            'PatientID' => "TEST'123"
        ]);

        // Should not crash - either 200 or appropriate error
        // Workaround for environment wrapping


value00: tests/feature/PatVisit/PatVisitShowTest.php
value01:
    /**
     * Test: Show patient visit with invalid/nonexistent PVID
     */
    public function testShowPatientVisitNotFound()
    {
        $invalidPVID = '99999';

        $response = $this->get($this->endpoint .'/'. $invalidPVID);

        $response->assertStatus(200);

        $json = json_decode($response->getJSON(), true);

        $this->assertArrayHasKey('data', $json);
        $this->assertIsArray($json['data']);
        $this->assertCount(0, $json['data']); // harus kosong
    }

}


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
    {
        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"     => "SMAJ1",
            "EmailAddress1" => 'asaas7890@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),
            'NameFirst'     => $faker->firstName,
            'NameLast'      => $faker->lastName,
            'Sex'        => '1',
            'Birthdate'     => $faker->date('Y-m-d'),
            'PatIdt'        => [ 'IdentifierType' => 'KTP', 'Identifier' => $faker->nik() ],
            'PatCom'        => $faker->sentence,
            'PatAtt'        => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ],
            ],
        ];


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            'PatAtt'         => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ],
            ],
        ];
        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }

        $result = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/1', $payload);
        $result->assertStatus(201);
        $json = $result->getJSON();
        $data = json_decode($json, true);
        $this->assertEquals('success', $data['status']);
    }


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            'Birthdate'      => $faker->date('Y-m-d'),
            'EmailAddress1'  => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),

            'PlaceOfBirth'   => $faker->city,
            'LinkTo'         => null,
            'PatIdt'         => [
                'IdentifierType' => 'KTP',
                'Identifier'     => $faker->nik(),
            ],
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            'PatCom'         => 'Update be',
            'PatAtt'         => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ],
            ],


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
    /**
     * 201 - Update dengan PatCom kosong
     */
    public function testUpdateWithoutPatCom()
    {
        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"     => "SMAJ1",
            'NameFirst'      => $faker->firstName,
            'NameMiddle'     => $faker->firstName,
            'NameLast'       => $faker->lastName,
            'Sex'         => '1',
            'Birthdate'      => $faker->date('Y-m-d'),
            'EmailAddress1'  => 'update_' . $faker->numberBetween(1,999) . '@gmail.com'


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            'Birthdate'      => $faker->date('Y-m-d'),
            'EmailAddress1'  => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),
            'PlaceOfBirth'   => $faker->city,
            'LinkTo'         => null,
            'PatIdt'         => [
                'IdentifierType' => 'KTP',
                'Identifier'     => $faker->nik(),
            ],
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            'PatCom'         => null,
            'PatAtt'         => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ],
            ],


value00: tests/feature/PatVisit/PatVisitByPatientTest.php
value01:
    /**
     * Test: Show all visits by valid InternalPID
     */
    public function testShowByPatientSuccess()
    {
        $InternalPID = 1;

        $response = $this->get($this->endpoint . '/' . $InternalPID);
        $response->assertStatus(200);

        // Pastikan JSON berisi struktur dasar
        $response->assertJSONFragment([
            'status' => 'success',
            // 'message' => 'data not found',
        ]);

        $json = json_decode($response->getJSON(), true);

        // Pastikan 'data' ada
        $this->assertArrayHasKey('data', $json);
        $this->assertIsArray($json['data']);
    }

    /**
     * Test: Show by patient with invalid / nonexistent InternalPID
     */


value00: tests/feature/PatVisit/PatVisitByPatientTest.php
value01:
<?php

namespace Tests\Feature\PatVisit;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;

class PatVisitByPatientTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected $endpoint = 'api/patvisit/patient';

    protected function setUp(): void
    {
        parent::setUp();
    }

    /**
     * Test: Show all visits by valid InternalPID
     */


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            'PatAtt'         => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ],
            ],
        ];
        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }

        $result = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/1', $payload);
        $result->assertStatus(201);
    }

    // /**
    //  * 500 - Invalid PatIdt
    //  */
    public function testUpdatePatIdtInvalid()
    {
        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"     => "SMAJ1",
            'NameFirst'      => $faker->firstName,
            'NameMiddle'     => $faker->firstName


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
[
            "PatientID"     => "SMAJ1",
            'NameFirst'      => $faker->firstName,
            'NameMiddle'     => $faker->firstName,
            'NameLast'       => $faker->lastName,
            'Sex'         => '1',
            'Birthdate'      => $faker->date('Y-m-d'),
            'EmailAddress1'  => 'update_' . $faker->numberBetween(1,999) . '@gmail.com'


value00: tests/feature/Patients/PatientShowTest.php
value01:
    // 200 ok - Test validasi respons selalu sukses - Passed
    public function testShowAlwaysReturnSuccessStatus() {
        $result = $this->get($this->endpoint . '/99999');
        $result->assertJSONFragment(['status' => 'success']);
    }

    // 200 ok not found - Passed
    public function testShowNotFound() {
        $result = $this->get($this->endpoint . '/90909090');
        $result->assertStatus(200);
        $result->assertJSONFragment([
            'status' => 'success',
            "message" => "data not found."
        ]);
    }

    // 200 ok found - PatIdt null - Passed


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            'PatAtt'         => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ],
            ],
        ];
        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }

        $result = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/1', $payload);
        $result->assertStatus(201);
    }

    /**
     * 201 - Update tanpa PatAtt
     */
    public function testUpdateWithoutAttachments()
    {
        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"     => "SMAJ1",
            'NameFirst'      => $faker->firstName,
            'NameMiddle'     => $faker->firstName


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            'Birthdate'      => $faker->date('Y-m-d'),
            'EmailAddress1'  => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),
            'PlaceOfBirth'   => $faker->city,
            'LinkTo'         => null,
            'PatIdt'         => [
                'IdentifierType' => 'KTP',
                'Identifier'     => $faker->nik(),
            ],
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            'PatCom'         => null,
            'PatAtt'         => []


value00: tests/feature/Patients/PatientShowTest.php
value01:
    // 200 ok found - PatCom null - Passed
    public function testShowWithNullPatCom() {
        $result = $this->get($this->endpoint . '/2');
        $data = $result->getJSON();
        $data = json_decode($data, true);

        $result->assertStatus(200);
        $this->assertNull($data['data']['PatCom']);
    }

    // 200 ok found - PatAtt greater dan 1 - Passed
    public function testShowWithMultipleAddresses() {
        $result = $this->get($this->endpoint . '/1');
        $data = $result->getJSON();
        $data = json_decode($data, true);

        $result->assertStatus(200);
        $this->assertIsArray($data['data']['PatAtt']);
        $this->assertGreaterThanOrEqual(1, count($data['data']['PatAtt']));
    }

}


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            'Birthdate'      => $faker->date('Y-m-d'),
            'EmailAddress1'  => 'update_' . $faker->numberBetween(1,999) . '@gmail.com',
            "Phone"          => $faker->numerify('08##########'),
            "MobilePhone"    => $faker->numerify('08##########'),
            'PlaceOfBirth'   => $faker->city,
            'LinkTo'         => null,
            'PatIdt'         => [
                'IdentifierType' => [],
                'Identifier'     => $faker->nik(),
            ],
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            'PatCom'         => null,
            'PatAtt'         => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ],
            ],


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
<?php

namespace Tests\Feature\Patients;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Faker\Factory;

class PatientUpdateTest extends CIUnitTestCase
{
    use FeatureTestTrait;
    protected $endpoint = 'api/patient';

    protected function setUp(): void
    {
        parent::setUp();
    }
    /**
     * 400 - Validation Fail
     * Coba update tanpa field wajib → harus gagal validasi.
     */


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            'PatAtt'         => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ],
            ],
        ];
        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }

        $result = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/1', $payload);

        $result->assertStatus(500);
        $json = $result->getJSON();
        $data = json_decode($json, true);
        if (isset($data['messages']) && is_array($data['messages'])) {
             $this->assertArrayHasKey('error', $data['messages']);
        } else {
             $this->assertArrayHasKey('error', $data);
        }
    }

}


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
            "DeathIndicator" => (string) $faker->numberBetween(16, 17),
            'PatCom'         => null,
            'PatAtt'         => [],
        ];
        if($payload['DeathIndicator'] == '16') {
            $payload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $payload['DeathDateTime'] = null;
        }

        $result = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/1', $payload);
        $result->assertStatus(201);
    }

    /**
     * 201 - Update tanpa PatAtt
     */
    public function testUpdateWithAttachments()
    {
        $faker = Factory::create('id_ID');

        $payload = [
            "PatientID"     => "SMAJ1",
            'NameFirst'      => $faker->firstName,


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
    /**
     * 201 - Update Success
     * Pastikan bisa update data yang valid (gunakan dummy data dari database).
     */
    public function testUpdatePatientSuccess()
    {
        $faker = Factory::create('id_ID');

        // NOTE: Sebaiknya ambil InternalPID yang sudah ada (mock atau dari DB fixture)
        // Untuk contoh ini kita asumsikan ada ID 1
        $payload = [
            "PatientID"     => "SMAJ1",
            'NameFirst'      => $faker->firstName,
            'NameMiddle'     => $faker->firstName,
            'NameLast'       => $faker->lastName,
            'Sex'         => '1',
            'Birthdate'      => $faker->date('Y-m-d'),
            'EmailAddress1'  => 'update_' . $faker->numberBetween(1,999) . '@gmail.com'


value00: tests/feature/Patients/PatientShowTest.php
value01:
<?php

namespace Tests\Feature\Patients;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;

class PatientShowTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected $endpoint = 'api/patient';

    protected function setUp(): void
    {
        parent::setUp();
    }


value00: tests/feature/Patients/PatientShowTest.php
value01:
    // 200 ok found - PatIdt null - Passed
    public function testShowWithNullPatIdt() {
        $result = $this->get($this->endpoint . '/2');
        $data = $result->getJSON();
        $data = json_decode($data, true);

        $result->assertStatus(200);
        $this->assertNull($data['data']['PatIdt']);
    }

    // 200 ok found - PatAtt null - Passed
    public function testShowWithNullPatAtt() {
        $result = $this->get($this->endpoint . '/2');
        $data = $result->getJSON();
        $data = json_decode($data, true);

        $result->assertStatus(200);
        $this->assertNull($data['data']['PatAtt']);
    }

    // 200 ok found - PatCom null - Passed


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
        $result = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/999999', $payload);

        $result->assertStatus(201); // Update returns success even if no rows found (depending on logic)
    }


value00: tests/feature/Patients/PatientUpdateTest.php
value01:
    /**
     * 400 - Validation Fail
     * Coba update tanpa field wajib → harus gagal validasi.
     */
    public function testUpdatePatientValidationFail()
    {
        $payload = [ 'InternalPID' => null, 'NameFirst' => '' ]; // Tidak valid
        $result = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/1', $payload);

        $result->assertStatus(400);
        $json = $result->getJSON();
        $data = json_decode($json, true);
        $this->assertArrayHasKey('messages', $data);
    }

    /**
     * 404 / 500 - Update Nonexistent Patient
     * Coba update InternalPID yang tidak ada.
     */
    public function testUpdateNonexistentPatientShouldFail()


value00: tests/feature/Patients/PatientShowTest.php
value01:
    protected $endpoint = 'api/patient';

    protected function setUp(): void
    {
        parent::setUp();
    }

    // 200 ok found - Passed
    public function testShowSingleRow() {
        // Pastikan DB test punya seed patient InternalPID=10 tanpa id/addr
        $result = $this->get($this->endpoint . '/1');

        $data = $result->getJSON(true);
        $data = json_decode($data, true);

        $result->assertStatus(200);
        $result->assertArrayHasKey('data', $data);
        $result->assertIsArray($data['data']);
    }
    
    // 200 ok - Test validasi respons selalu sukses - Passed


value00: tests/feature/PatVisit/PatVisitByPatientTest.php
value01:
    /**
     * Test: Show by patient with invalid / nonexistent InternalPID
     */
    public function testShowByPatientNotFound()
    {
        $invalidPID = 9999999; // diasumsikan tidak ada

        $response = $this->get($this->endpoint . '/' . $invalidPID);

        $response->assertStatus(200);

        $json = json_decode($response->getJSON(), true);

        $this->assertArrayHasKey('data', $json);
        $this->assertIsArray($json['data']);
        $this->assertCount(0, $json['data']); // Data kosong
    }

}


value00: tests/feature/PatVisit/PatVisitCreateTest.php
value01:
    /**
     * Test: Create patient visit with invalid (empty) data - missing InternalPID
     */
    public function testCreatePatientVisitInvalidInput()
    {
        $payload = [];
        // Kirim data kosong
        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);

        $response->assertStatus(400);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'InternalPID is required and must be numeric'
        ]);
    }

    /**
     * Test: Create patient visit with non-existent patient
     */


value00: tests/feature/PatVisit/PatVisitCreateTest.php
value01:
    /**
     * Test: Create patient visit with non-existent patient
     */
    public function testCreatePatientVisitPatientNotFound()
    {
        $payload = [
            'InternalPID' => 999999, // Non-existent patient
            'EpisodeID' => 'EPI001'
        ];

        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);

        $response->assertStatus(404);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'Patient not found'
        ]);
    }

    /**
     * Test: Create patient visit with invalid InternalPID (non-numeric)
     */


value00: tests/feature/PatVisit/PatVisitCreateTest.php
value01:
    /**
     * Test: Create patient visit with invalid InternalPID (non-numeric)
     */
    public function testCreatePatientVisitInvalidInternalPID()
    {
        $payload = [
            'InternalPID' => 'invalid',
            'EpisodeID' => 'EPI001'
        ];

        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);

        $response->assertStatus(400);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'InternalPID is required and must be numeric'
        ]);
    }

}


value00: tests/feature/PatVisit/PatVisitCreateTest.php
value01:
        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);

        // Pastikan status 201 (created)
        $response->assertStatus(201);

        // Pastikan JSON mengandung status success dan message yang benar
        $response->assertJSONFragment([
            'status'  => 'success',
            'message' => 'Data created successfully'
        ]);

        $json = json_decode($response->getJSON(), true);

        // Pastikan struktur data yang dikembalikan benar
        $this->assertArrayHasKey('data', $json);
        $this->assertArrayHasKey('PVID', $json['data']);
        $this->assertArrayHasKey('InternalPVID', $json['data']);
    }


value00: tests/feature/PatVisit/PatVisitCreateTest.php
value01:
<?php

namespace Tests\Feature\PatVisit;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Tests\Support\Traits\CreatesPatients;

class PatVisitCreateTest extends CIUnitTestCase
{
    use FeatureTestTrait;
    use CreatesPatients;

    protected $endpoint = 'api/patvisit';

    protected function setUp(): void
    {
        parent::setUp();
    }

    /**
     * Test: Create patient visit with valid data
     */
    public function testCreatePatientVisitSuccess()


value00: tests/feature/PatVisit/PatVisitCreateTest.php
value01:
    {
        $payload = [
            "InternalPID"=> $this->createTestPatient(),
            "EpisodeID"=> null,
            "PatDiag"=> [
                "DiagCode"=> null,
                "Diagnosis"=> null
            ],
            "PatVisitADT"=> [
                "ADTCode"=> "A01",
                "LocationID"=> "1",
                "AttDoc"=> "1",
                "RefDoc"=> "1",
                "AdmDoc"=> "1",
                "CnsDoc"=> "1"
            ],
            "PatAtt"=> []
        ];

        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);


value00: tests/_support/Libraries/ConfigReader.php
value01:
<?php

namespace Tests\Support\Libraries;

use Config\App;

/**
 * Class ConfigReader
 *
 * An extension of BaseConfig that prevents the constructor from
 * loading external values. Used to read actual local values from
 * a config file.
 */
class ConfigReader extends App
{
    public function __construct()
    {
    }
}


value00: tests/feature/PatVisit/PatVisitDeleteTest.php
value01:
    {
        // Create a visit first to delete
        $createPayload = [
            "InternalPID"=> $this->createTestPatient(),
            "EpisodeID"=> "TEST001",
            "PatVisitADT"=> [
                "ADTCode"=> "A01",
                "LocationID"=> "1"
            ]
        ];

        $createResponse = $this->withBodyFormat('json')->call('post', $this->endpoint, $createPayload);
        $createResponse->assertStatus(201);
        
        $json = json_decode($createResponse->getJSON(), true);
        $internalPVID = $json['data']['InternalPVID'];


value00: tests/feature/PatVisit/PatVisitDeleteTest.php
value01:
    /**
     * Test: Delete patient visit with invalid ID (non-numeric)
     */
    public function testDeletePatientVisitInvalidId()
    {
        $payload = [
            'InternalPVID' => 'invalid'
        ];

        $response = $this->withBodyFormat('json')->call('delete', $this->endpoint, $payload);
        $response->assertStatus(400);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'Invalid or missing ID'
        ]);
    }

    /**
     * Test: Delete patient visit with non-existent ID
     */


value00: tests/feature/PatVisit/PatVisitDeleteTest.php
value01:
<?php

namespace Tests\Feature\PatVisit;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Tests\Support\Traits\CreatesPatients;

class PatVisitDeleteTest extends CIUnitTestCase
{
    use FeatureTestTrait;
    use CreatesPatients;

    protected $endpoint = 'api/patvisit';

    protected function setUp(): void
    {
        parent::setUp();
    }

    /**
     * Test: Delete patient visit successfully (soft delete)
     */
    public function testDeletePatientVisitSuccess()
    {
        // Create a visit first to delete


value00: tests/feature/PatVisit/PatVisitDeleteTest.php
value01:
    /**
     * Test: Delete patient visit with non-existent ID
     */
    public function testDeletePatientVisitNotFound()
    {
        $payload = [
            'InternalPVID' => 999999 // Non-existent visit
        ];

        $response = $this->withBodyFormat('json')->call('delete', $this->endpoint, $payload);
        $response->assertStatus(404);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'Visit not found'
        ]);
    }

}


value00: tests/feature/PatVisit/PatVisitDeleteTest.php
value01:
    /**
     * Test: Delete patient visit with missing ID
     */
    public function testDeletePatientVisitMissingId()
    {
        $payload = [];

        $response = $this->withBodyFormat('json')->call('delete', $this->endpoint, $payload);
        $response->assertStatus(400);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'Invalid or missing ID'
        ]);
    }

    /**
     * Test: Delete patient visit with invalid ID (non-numeric)
     */


value00: tests/feature/PatVisit/PatVisitDeleteTest.php
value01:
        $json = json_decode($createResponse->getJSON(), true);
        $internalPVID = $json['data']['InternalPVID'];

        // Now delete it
        $deletePayload = [
            'InternalPVID' => $internalPVID
        ];

        $response = $this->withBodyFormat('json')->call('delete', $this->endpoint, $deletePayload);

        $response->assertStatus(200);
        $response->assertJSONFragment([
            'status'  => 'success',
            'message' => 'Data deleted successfully'
        ]);
    }


value00: tests/_support/Models/ExampleModel.php
value01:
<?php

namespace Tests\Support\Models;

use CodeIgniter\Model;

class ExampleModel extends Model
{
    protected $table          = 'factories';
    protected $primaryKey     = 'id';
    protected $returnType     = 'object';
    protected $useSoftDeletes = false;
    protected $allowedFields  = [
        'name',
        'uid',
        'class',
        'icon',
        'summary',
    ];
    protected $useTimestamps      = true;
    protected $validationRules    = [];
    protected $validationMessages = [];
    protected $skipValidation     = false;
}


value00: tests/feature/PatVisit/PatVisitUpdateTest.php
value01:
    {
        // First create a visit to update
        $createPayload = [
            "InternalPID"=> $this->createTestPatient(),
            "EpisodeID"=> "TEST001",
            "PatVisitADT"=> [
                "ADTCode"=> "A01",
                "LocationID"=> "1"
            ]
        ];

        $createResponse = $this->withBodyFormat('json')->call('post', 'api/patvisit', $createPayload);
        $createResponse->assertStatus(201);
        
        $createJson = json_decode($createResponse->getJSON(), true);
        $internalPVID = $createJson['data']['InternalPVID'];
        $pvid = $createJson['data']['PVID'];


value00: tests/feature/PatVisit/PatVisitUpdateTest.php
value01:
        $internalPVID = $createJson['data']['InternalPVID'];
        $pvid = $createJson['data']['PVID'];

        // Now update it
        $payload = [
            'PVID'         => $pvid,
            'EpisodeID'    => 'EPI001',
            'PatDiag' => [
                'DiagCode' => 'A02',
                'DiagName' => 'Dysentery'
            ],
            'PatVisitADT' => [
                'LocationID' => 12,
                'AdmitDate'  => date('Y-m-d H:i:s')
            ]
        ];

        $response = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/' . $internalPVID, $payload);

        // Pastikan response sukses (200 OK untuk update)
        $response->assertStatus(200);


value00: tests/feature/PatVisit/PatVisitUpdateTest.php
value01:
    /**
     * Test: Update patient visit with non-existent ID
     */
    public function testUpdatePatientVisitNotFound()
    {
        $payload = [
            'EpisodeID'    => 'EPI001',
            'PatDiag' => [
                'DiagCode' => 'A02',
                'DiagName' => 'Dysentery'
            ]
        ];

        $response = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/999999', $payload);
        $response->assertStatus(404);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'Visit not found'
        ]);
    }

    /**
     * Test: Update patient visit with invalid ID (non-numeric)
     */


value00: tests/feature/PatVisit/PatVisitUpdateTest.php
value01:
        // Pastikan response sukses (200 OK untuk update)
        $response->assertStatus(200);

        // Periksa fragment JSON
        $response->assertJSONFragment([
            'status'  => 'success',
            'message' => 'Data updated successfully',
        ]);

        // Pastikan response mengandung data yang sesuai
        $json = json_decode($response->getJSON(), true);
        $this->assertArrayHasKey('data', $json);
        $this->assertEquals($pvid, $json['data']['PVID']);
    }


value00: tests/_support/Database/Seeds/ExampleSeeder.php
value01:
                'icon'    => 'fas fa-puzzle-piece',
                'summary' => 'Create widgets in your factory',
            ],
            [
                'name'    => 'Evil Factory',
                'uid'     => 'evil-maker',
                'class'   => 'Factories\Evil\MyFactory',
                'icon'    => 'fas fa-book-dead',
                'summary' => 'Abandon all hope, ye who enter here',
            ],
        ];

        $builder = $this->db->table('factories');

        foreach ($factories as $factory) {
            $builder->insert($factory);
        }
    }
}


value00: tests/feature/PatVisit/PatVisitUpdateTest.php
value01:
    /**
     * Test: Update patient visit with empty payload
     */
    public function testUpdatePatientVisitInvalidInput()
    {
        $payload = [];

        $response = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/0', $payload);
        $response->assertStatus(400);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'Invalid or missing ID'
        ]);
    }

}


value00: tests/_support/Database/Seeds/ExampleSeeder.php
value01:
<?php

namespace Tests\Support\Database\Seeds;

use CodeIgniter\Database\Seeder;

class ExampleSeeder extends Seeder
{
    public function run(): void
    {
        $factories = [
            [
                'name'    => 'Test Factory',
                'uid'     => 'test001',
                'class'   => 'Factories\Tests\NewFactory',
                'icon'    => 'fas fa-puzzle-piece',
                'summary' => 'Longer sample text for testing',
            ],
            [
                'name'    => 'Widget Factory',
                'uid'     => 'widget',
                'class'   => 'Factories\Tests\WidgetPlant',
                'icon'    => 'fas fa-puzzle-piece',
                'summary' => 'Create widgets in your factory',
            ],


value00: tests/feature/PatVisit/PatVisitUpdateTest.php
value01:
<?php

namespace Tests\Feature\PatVisit;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use Tests\Support\Traits\CreatesPatients;

class PatVisitUpdateTest extends CIUnitTestCase
{
    use FeatureTestTrait;
    use CreatesPatients;
    protected $endpoint = 'api/patvisit';

    protected function setUp(): void
    {
        parent::setUp();
    }

    /**
     * Test: Update patient visit successfully
     */
    public function testUpdatePatientVisitSuccess()
    {
        // First create a visit to update


value00: tests/feature/PatVisit/PatVisitUpdateTest.php
value01:
    public function testUpdatePatientVisitInvalidId()
    {
        $payload = [
            'PVID'         => 'DV0001',
            'EpisodeID'    => 'EPI001',
            'PatDiag' => [
                'DiagCode' => 'A02',
                'DiagName' => 'Dysentery'
            ],
            'PatVisitADT' => [
                'LocationID' => 12,
                'AdmitDate'  => date('Y-m-d H:i:s')
            ]
        ];

        $response = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/invalid', $payload);
        $response->assertStatus(400);
        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'Invalid or missing ID'
        ]);
    }

    /**
     * Test: Update patient visit with empty payload
     */


value00: tests/feature/PatVisit/PatVisitUpdateTest.php
value01:
    /**
     * Test: Update patient visit with missing ID
     */
    public function testUpdatePatientVisitMissingId()
    {
        // InternalPVID tidak ada
        $payload = [
            'EpisodeID' => 'EPI002',
            'PatDiag'   => ['DiagCode' => 'B01', 'DiagName' => 'Flu']
        ];

        $response = $this->withBodyFormat('json')->call('patch', $this->endpoint . '/0', $payload);
        // Karena ID tidak valid → 400 Bad Request
        $response->assertStatus(400);

        $response->assertJSONFragment([
            'status'  => 'error',
            'message' => 'Invalid or missing ID'
        ]);
    }

    /**
     * Test: Update patient visit with non-existent ID
     */


value00: tests/unit/Patient/PatientModelTest.php
value01:
    /**
     * Test Case 13: Model allowed fields contain death indicator
     */
    public function testModelHasDeathIndicatorField()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('allowedFields');
        $property->setAccessible(true);
        
        $allowedFields = $property->getValue($this->model);
        
        $this->assertContains('DeathIndicator', $allowedFields);
        $this->assertContains('TimeOfDeath', $allowedFields);
    }

    /**
     * Test Case 14: Model has getPatients method
     */
    public function testModelHasGetPatientsMethod()
    {
        $this->assertTrue(method_exists($this->model, 'getPatients'));
    }


value00: tests/unit/Patient/PatientModelTest.php
value01:
    /**
     * Test Case 8: Model uses timestamps
     */
    public function testModelUsesTimestamps()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('useTimestamps');
        $property->setAccessible(true);
        
        $this->assertTrue($property->getValue($this->model));
    }

    /**
     * Test Case 9: Model has no updated field (empty string)
     */
    public function testModelHasNoUpdatedField()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('updatedField');
        $property->setAccessible(true);
        
        $this->assertEquals('', $property->getValue($this->model));
    }


value00: tests/unit/Patient/PatientModelTest.php
value01:
    /**
     * Test Case 5: Model has correct deleted field
     */
    public function testModelHasCorrectDeletedField()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('deletedField');
        $property->setAccessible(true);
        
        $this->assertEquals('DelDate', $property->getValue($this->model));
    }

    /**
     * Test Case 6: Model has correct created field
     */


value00: tests/unit/Patient/PatientModelTest.php
value01:
    /**
     * Test Case 12: Model allowed fields contain demographic fields
     */
    public function testModelHasDemographicFields()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('allowedFields');
        $property->setAccessible(true);
        
        $allowedFields = $property->getValue($this->model);
        
        $this->assertContains('Country', $allowedFields);
        $this->assertContains('Race', $allowedFields);
        $this->assertContains('Religion', $allowedFields);
        $this->assertContains('Ethnic', $allowedFields);
        $this->assertContains('MaritalStatus', $allowedFields);
    }

    /**
     * Test Case 13: Model allowed fields contain death indicator
     */


value00: tests/unit/Patient/PatientModelTest.php
value01:
class PatientModelTest extends CIUnitTestCase
{
    protected PatientModel $model;

    protected function setUp(): void
    {
        parent::setUp();
        $this->model = new PatientModel();
    }

    /**
     * Test Case 1: Model can be instantiated
     */
    public function testModelInstantiation()
    {
        $this->assertInstanceOf(PatientModel::class, $this->model);
    }

    /**
     * Test Case 2: Model has correct table name
     */
    public function testModelHasCorrectTable()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('table');
        $property->setAccessible(true);
        
        $this->assertEquals('patient', $property->getValue($this->model));
    }


value00: tests/unit/Patient/PatientModelTest.php
value01:
    public function testModelHasAllowedFields()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('allowedFields');
        $property->setAccessible(true);
        
        $allowedFields = $property->getValue($this->model);
        
        $this->assertIsArray($allowedFields);
        $this->assertContains('PatientID', $allowedFields);
        $this->assertContains('NameFirst', $allowedFields);
        $this->assertContains('Sex', $allowedFields);
        $this->assertContains('Birthdate', $allowedFields);
        $this->assertContains('EmailAddress1', $allowedFields);
        $this->assertContains('Phone', $allowedFields);
        $this->assertContains('MobilePhone', $allowedFields);
    }


value00: tests/unit/Patient/PatientModelTest.php
value01:
    /**
     * Test Case 10: Model allowed fields count
     */
    public function testModelAllowedFieldsCount()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('allowedFields');
        $property->setAccessible(true);
        
        $allowedFields = $property->getValue($this->model);
        
        // Should have a reasonable number of fields
        $this->assertGreaterThan(20, count($allowedFields));
    }

    /**
     * Test Case 11: Model allowed fields contain address fields
     */


value00: tests/unit/Patient/PatientModelTest.php
value01:
<?php

namespace Tests\Unit\Patient;

use CodeIgniter\Test\CIUnitTestCase;
use App\Models\Patient\PatientModel;

/**
 * PatientModelTest
 * 
 * Unit tests for PatientModel - Testing model configuration only
 * These tests don't require a database connection


value00: tests/unit/Patient/PatientModelTest.php
value01:
 * PatientModelTest
 * 
 * Unit tests for PatientModel - Testing model configuration only
 * These tests don't require a database connection
 * 
 * For tests that require database, use Feature tests instead
 * 
 * Test Cases:
 * 1. testModelInstantiation - Model can be instantiated
 * 2. testModelHasCorrectTable - Model points to correct table
 * 3. testModelHasCorrectPrimaryKey - Model has correct primary key
 * 4. testModelUsesSoftDeletes - Model uses soft deletes
 * 5. testModelHasCorrectDeletedField - Model has correct deleted field
 * 6. testModelHasCorrectCreatedField - Model has correct created field
 * 7. testModelHasAllowedFields - Model has correct allowed fields
 */


value00: tests/unit/Patient/PatientModelTest.php
value01:
    /**
     * Test Case 6: Model has correct created field
     */
    public function testModelHasCorrectCreatedField()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('createdField');
        $property->setAccessible(true);
        
        $this->assertEquals('CreateDate', $property->getValue($this->model));
    }

    /**
     * Test Case 7: Model allowed fields contain required fields
     */


value00: tests/unit/Patient/PatientModelTest.php
value01:
    /**
     * Test Case 15: Model has getPatient method
     */
    public function testModelHasGetPatientMethod()
    {
        $this->assertTrue(method_exists($this->model, 'getPatient'));
    }

    /**
     * Test Case 16: Model has createPatient method
     */
    public function testModelHasCreatePatientMethod()
    {
        $this->assertTrue(method_exists($this->model, 'createPatient'));
    }

    /**
     * Test Case 17: Model has updatePatient method
     */
    public function testModelHasUpdatePatientMethod()
    {
        $this->assertTrue(method_exists($this->model, 'updatePatient'));
    }
}


value00: tests/unit/Patient/PatientModelTest.php
value01:
    public function testModelHasAddressFields()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('allowedFields');
        $property->setAccessible(true);
        
        $allowedFields = $property->getValue($this->model);
        
        $this->assertContains('Street_1', $allowedFields);
        $this->assertContains('Street_2', $allowedFields);
        $this->assertContains('Street_3', $allowedFields);
        $this->assertContains('City', $allowedFields);
        $this->assertContains('Province', $allowedFields);
        $this->assertContains('ZIP', $allowedFields);
    }

    /**
     * Test Case 12: Model allowed fields contain demographic fields
     */


value00: tests/unit/Patient/PatientModelTest.php
value01:
    /**
     * Test Case 3: Model has correct primary key
     */
    public function testModelHasCorrectPrimaryKey()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('primaryKey');
        $property->setAccessible(true);
        
        $this->assertEquals('InternalPID', $property->getValue($this->model));
    }

    /**
     * Test Case 4: Model uses soft deletes
     */
    public function testModelUsesSoftDeletes()
    {
        $reflection = new \ReflectionClass($this->model);
        $property = $reflection->getProperty('useSoftDeletes');
        $property->setAccessible(true);
        
        $this->assertTrue($property->getValue($this->model));
    }


value00: app/Views/errors/html/error_exception.php
value01:
<?php
use CodeIgniter\HTTP\Header;
use CodeIgniter\CodeIgniter;

$errorId = uniqid('error', true);
?>
<!doctype html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="robots" content="noindex">

    <title><?= esc($title) ?></title>
    <style>
        <?= preg_replace('#[\r\n\t ]+#', ' ', file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.css')) ?>
    </style>

    <script>
        <?= file_get_contents(__DIR__ . DIRECTORY_SEPARATOR . 'debug.js') ?>
    </script>
</head>
<body onload="init()">

    <!-- Header -->
    <div class="header">
        <div class="environment">
            Displayed at <?= esc(date('H:i:sa')) ?> &mdash;
            PHP: <?= esc(PHP_VERSION) ?>  &mdash;
            CodeIgniter: <?=


value00: app/Views/errors/html/error_exception.php
value01:
esc($argsId, 'attr') ?>');">arguments</a> )
                                    <div class="args" id="<?= esc($argsId, 'attr') ?>">
                                        <table cellspacing="0">

                                        <?php
                                        $params = null;
                                        // Reflection by name is not available for closure function
                                        if (! str_ends_with($row['function'], '}')) {
                                            $mirror = isset($row['class']) ? new ReflectionMethod($row['class'], $row['function']) : new ReflectionFunction($row['function']);
                                            $params = $mirror->getParameters();
                                        }


value00: app/Views/errors/html/error_exception.php
value01:
esc(date('H:i:sa')) ?> &mdash;
            PHP: <?= esc(PHP_VERSION) ?>  &mdash;
            CodeIgniter: <?= esc(CodeIgniter::CI_VERSION) ?> --
            Environment: <?= ENVIRONMENT ?>
        </div>
        <div class="container">
            <h1><?= esc($title), esc($exception->getCode() ? ' #' . $exception->getCode() : '') ?></h1>
            <p>
                <?= nl2br(esc($exception->getMessage())) ?>
                <a href="https://www.duckduckgo.com/?q=<?= urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage()))


value00: app/Views/errors/html/error_exception.php
value01:
?>

                            <!-- Class/Method -->
                            <?php if (isset($row['class'])) : ?>
                                &nbsp;&nbsp;&mdash;&nbsp;&nbsp;<?= esc($row['class'] . $row['type'] . $row['function']) ?>
                                <?php if (! empty($row['args'])) : ?>
                                    <?php $argsId = $errorId . 'args' . $index ?>
                                    ( <a href="#" onclick="return toggle('<?= esc($argsId, 'attr') ?>');">arguments</a> )
                                    <div class="args" id="<?=


value00: app/Views/errors/html/error_exception.php
value01:
?>
                </ol>

            </div>

            <!-- Server -->
            <div class="content" id="server">
                <?php foreach (['_SERVER', '_SESSION'] as $var) : ?>
                    <?php
                    if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
                        continue;
                    } ?>

                    <h3>$<?= esc($var) ?></h3>

                    <table>
                        <thead>
                            <tr>
                                <th>Key</th>
                                <th>Value</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php


value00: app/Views/errors/html/error_exception.php
value01:
?>
                        </tbody>
                    </table>

                <?php endif; ?>
            </div>

            <!-- Response -->
            <?php
                $response = service('response');
                $response->setStatusCode(http_response_code());
            ?>
            <div class="content" id="response">
                <table>
                    <tr>
                        <td style="width: 15em">Response Status</td>
                        <td><?= esc($response->getStatusCode() . ' - ' . $response->getReasonPhrase()) ?></td>
                    </tr>
                </table>

                <?php $headers = $response->headers(); ?>
                <?php


value00: app/Views/errors/html/error_exception.php
value01:
: ?>
    <div class="container">

        <ul class="tabs" id="tabs">
            <li><a href="#backtrace">Backtrace</a></li>
            <li><a href="#server">Server</a></li>
            <li><a href="#request">Request</a></li>
            <li><a href="#response">Response</a></li>
            <li><a href="#files">Files</a></li>
            <li><a href="#memory">Memory</a></li>
        </ul>

        <div class="tab-content">

            <!-- Backtrace -->
            <div class="content" id="backtrace">

                <ol class="trace">
                <?php foreach ($trace as $index => $row) : ?>

                    <li>
                        <p>
                            <!-- Trace info -->
                            <?php


value00: app/Views/errors/html/error_exception.php
value01:
?>

                <!-- Constants -->
                <?php $constants = get_defined_constants(true); ?>
                <?php if (! empty($constants['user'])) : ?>
                    <h3>Constants</h3>

                    <table>
                        <thead>
                            <tr>
                                <th>Key</th>
                                <th>Value</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php foreach ($constants['user'] as $key => $value) : ?>
                            <tr>
                                <td><?=


value00: app/Views/errors/html/error_exception.php
value01:
urlencode($title . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $exception->getMessage())) ?>"
                   rel="noreferrer" target="_blank">search &rarr;</a>
            </p>
        </div>
    </div>

    <!-- Source -->
    <div class="container">
        <p><b><?= esc(clean_path($file)) ?></b> at line <b><?= esc($line) ?></b></p>

        <?php if (is_file($file)) : ?>
            <div class="source">
                <?= static::highlightFile($file, $line, 15); ?>
            </div>
        <?php endif; ?>
    </div>

    <div class="container">
        <?php
        $last = $exception;


value00: app/Views/errors/html/error_exception.php
value01:
foreach ($constants['user'] as $key => $value) : ?>
                            <tr>
                                <td><?= esc($key) ?></td>
                                <td>
                                    <?php if (is_string($value)) : ?>
                                        <?= esc($value) ?>
                                    <?php else: ?>
                                        <pre><?= esc(print_r($value, true)) ?></pre>
                                    <?php endif; ?>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                        </tbody>
                    </table>
                <?php endif;


value00: tests/_support/Traits/CreatesPatients.php
value01:
            'ZIP'            => $faker->postcode,
            'Street_1'       => $faker->streetAddress,
            'City'           => $faker->city,
            'Province'       => $faker->state,
            'EmailAddress1'  => 'test.' . $faker->unique()->userName . '@example.com',
            'Phone'          => $faker->numerify('08##########'),
            'MobilePhone'    => $faker->numerify('08##########'),
            'Race'           => (string) $faker->numberBetween(175, 205),
            'Country'        => (string) $faker->numberBetween(221, 469),
            'MaritalStatus'  => (string) $faker->numberBetween(8, 15),
            'Religion'       => (string) $faker->numberBetween(206, 212),
            'Ethnic'         => (string) $faker->numberBetween(213, 220)


value00: app/Views/errors/html/error_exception.php
value01:
if (isset($row['file']) && is_file($row['file'])) : ?>
                                <?php
                                if (isset($row['function']) && in_array($row['function'], ['include', 'include_once', 'require', 'require_once'], true)) {
                                    echo esc($row['function'] . ' ' . clean_path($row['file']));
                                } else {
                                    echo esc(clean_path($row['file']) . ' : ' . $row['line']);
                                }
                                ?>
                            <?php else: ?>
                                {PHP internal code}
                            <?php endif; ?>

                            <!-- Class/Method -->
                            <?php


value00: app/Views/errors/html/error_exception.php
value01:
?>
            </div>

            <!-- Request -->
            <div class="content" id="request">
                <?php $request = service('request'); ?>

                <table>
                    <tbody>
                        <tr>
                            <td style="width: 10em">Path</td>
                            <td><?= esc($request->getUri()) ?></td>
                        </tr>
                        <tr>
                            <td>HTTP Method</td>
                            <td><?= esc($request->getMethod()) ?></td>
                        </tr>
                        <tr>
                            <td>IP Address</td>
                            <td><?= esc($request->getIPAddress())


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    public function testCreateGroupTestWithMembers(): void
    {
        $members = $this->resolveMemberIds(['GLU', 'CREA']);
        $testmap = $this->buildTestMap(true, true);
        $payload = $this->buildGroupPayload($members, $testmap);

        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
        $response->assertStatus(201);
        $response->assertJSONFragment([
            'status' => 'created',
            'message' => 'Test created successfully',
        ]);

        $json = json_decode($response->getJSON(), true);
        $this->assertArrayHasKey('data', $json);
        $this->assertArrayHasKey('TestSiteId', $json['data']);
        $this->assertIsInt($json['data']['TestSiteId']);
    }


value00: app/Views/errors/html/error_exception.php
value01:
?>
                            <?php endif; ?>

                            <?php if (! isset($row['class']) && isset($row['function'])) : ?>
                                &nbsp;&nbsp;&mdash;&nbsp;&nbsp;    <?= esc($row['function']) ?>()
                            <?php endif; ?>
                        </p>

                        <!-- Source? -->
                        <?php if (isset($row['file']) && is_file($row['file']) && isset($row['class'])) : ?>
                            <div class="source">
                                <?= static::highlightFile($row['file'], $row['line']) ?>
                            </div>
                        <?php endif; ?>
                    </li>

                <?php endforeach;


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
        $this->assertArrayHasKey('specimen_status', $valuesets);
        
        // Check structure
        $this->assertArrayHasKey('value', $valuesets['sex']);
        $this->assertArrayHasKey('label', $valuesets['sex']);
        $this->assertArrayHasKey('count', $valuesets['sex']);
        $this->assertIsInt($valuesets['sex']['count']);
        $this->assertGreaterThan(0, $valuesets['sex']['count']);
        $this->assertEquals('Sex', $valuesets['sex']['label']);
    }


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
    public function testShowByNameSexReturnsCorrectValues()
    {
        $result = $this->call('get', 'api/valueset/sex');

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $values = array_column($data['data'], 'value');
        $labels = array_column($data['data'], 'label');

        $this->assertContains('F', $values);
        $this->assertContains('M', $values);
        $this->assertContains('Female', $labels);
        $this->assertContains('Male', $labels);
    }


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
    public function testShowByNameReturnsSingleLookup()
    {
        $result = $this->call('get', 'api/valueset/sex');

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsArray($data['data']);
        $this->assertNotEmpty($data['data']);
        $this->assertArrayHasKey('value', $data['data'][0]);
        $this->assertArrayHasKey('label', $data['data'][0]);
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function buildRefTxtEntries(string $txtRefType, bool $multiple = false): array
    {
        $entries = [
            [
                'SpcType' => 'GEN',
                'TxtRefType' => $txtRefType,
                'Sex' => '2',
                'AgeStart' => 0,
                'AgeEnd' => 120,
                'RefTxt' => $txtRefType === 'VSET' ? 'NORM=Normal;ABN=Abnormal' : 'NORM=Normal',
                'Flag' => 'N',
            ],
        ];


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
    public function testShowByNameInvalidLookupReturns404()
    {
        $result = $this->call('get', 'api/valueset/nonexistent_lookup');

        $result->assertStatus(404);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('error', $data['status']);
    }


value00: app/Views/errors/html/error_exception.php
value01:
        $last = $exception;

        while ($prevException = $last->getPrevious()) {
            $last = $prevException;
            ?>

    <pre>
    Caused by:
    <?= esc($prevException::class), esc($prevException->getCode() ? ' #' . $prevException->getCode() : '') ?>

    <?= nl2br(esc($prevException->getMessage())) ?>
    <a href="https://www.duckduckgo.com/?q=<?= urlencode($prevException::class . ' ' . preg_replace('#\'.*\'|".*"#Us', '', $prevException->getMessage())) ?>"
       rel="noreferrer" target="_blank">search &rarr;</a>
    <?= esc(clean_path($prevException->getFile()) . ':' . $prevException->getLine()) ?>
    </pre>

        <?php
        }
        ?>
    </div>

    <?php if (defined('SHOW_DEBUG_BACKTRACE') && SHOW_DEBUG_BACKTRACE)


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetMaritalStatus()
    {
        $result = ValueSet::getRaw('marital_status');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $statusMap = array_combine($keys, $values);
        $this->assertEquals('Separated', $statusMap['A']);
        $this->assertEquals('Divorced', $statusMap['D']);
        $this->assertEquals('Married', $statusMap['M']);
        $this->assertEquals('Single', $statusMap['S']);
        $this->assertEquals('Widowed', $statusMap['W']);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
<?php

namespace Tests\Unit\ValueSet;

use CodeIgniter\Test\CIUnitTestCase;
use App\Libraries\ValueSet;

class ValueSetTest extends CIUnitTestCase
{
    protected function setUp(): void
    {
        parent::setUp();
        ValueSet::clearCache();
    }

    public function testGetPatientSexReturnsFormattedArray()
    {
        $result = ValueSet::get('sex');
        $this->assertIsArray($result);
        $this->assertNotEmpty($result);
        $this->assertArrayHasKey('value', $result[0]);
        $this->assertArrayHasKey('label', $result[0]);
    }

    public function testGetPatientSexContainsExpectedValues()
    {
        $result = ValueSet::get('sex');
        $values = array_column($result, 'value');
        $labels = array_column($result, 'label');


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    public function testCreateTechnicalWithThresholdReference(): void
    {
        $refnum = $this->buildRefNumEntries('THOLD', true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'NMRIC',
                'RefType' => 'THOLD',
            ], $refnum);
        }
    }

    public function testCreateTechnicalWithTextReference(): void
    {
        $reftxt = $this->buildRefTxtEntries('TEXT', true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'TEXT',
                'RefType' => 'TEXT',
            ], null, $reftxt);
        }
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetSpecimenStatus()
    {
        $result = ValueSet::getRaw('specimen_status');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $this->assertContains('STC', $keys);
        $this->assertContains('SCtd', $keys);
        $this->assertContains('STran', $keys);
        $this->assertContains('SArrv', $keys);
        $this->assertContains('SRejc', $keys);
        $this->assertContains('SRcvd', $keys);

        $statusMap = array_combine($keys, $values);
        $this->assertEquals('To be collected', $statusMap['STC']);
        $this->assertEquals('Collected', $statusMap['SCtd']);
        $this->assertEquals('In-transport', $statusMap['STran']);
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function assertTechnicalCreated(
        string $type,
        array $details = [],
        ?array $refnum = null,
        ?array $reftxt = null,
        ?array $testmap = null
    ): void {
        $payload = $this->buildTechnicalPayload($type, $details);
        if ($refnum !== null) {
            $payload['refnum'] = $refnum;
        }
        if ($reftxt !== null) {
            $payload['reftxt'] = $reftxt;
        }
        if ($testmap !== null) {
            $payload['testmap'] = $testmap;
        }

        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function buildCalculatedPayload(array $members = []): array
    {
        $payload = [
            'SiteID' => self::SITE_ID,
            'TestSiteCode' => $this->generateTestCode('CALC'),
            'TestSiteName' => 'Auto CALC',
            'TestType' => 'CALC',
            'SeqScr' => 1000,
            'SeqRpt' => 1000,
            'VisibleScr' => 1,
            'VisibleRpt' => 1,
            'CountStat' => 0,
            'details' => [
                'DisciplineID' => 2,
                'DepartmentID' => 2,
                'FormulaCode' => '{GLU} + {CREA}',
                'members' => array_map(fn ($id) => ['TestSiteID' => $id], $members),
            ],
        ];

        return $payload;
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function buildGroupPayload(array $members, array $testmap): array
    {
        return [
            'SiteID' => self::SITE_ID,
            'TestSiteCode' => $this->generateTestCode('PANEL'),
            'TestSiteName' => 'Auto Group',
            'TestType' => 'GROUP',
            'SeqScr' => 300,
            'SeqRpt' => 300,
            'VisibleScr' => 1,
            'VisibleRpt' => 1,
            'CountStat' => 1,
            'details' => [
                'members' => array_map(fn ($id) => ['TestSiteID' => $id], $members),
            ],
            'testmap' => $testmap,
        ];
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function normalizeDetails(array $details): array
    {
        $normalized = [
            'DisciplineID' => $details['DisciplineID'] ?? 2,
            'DepartmentID' => $details['DepartmentID'] ?? 2,
            'Method' => $details['Method'] ?? 'Automated test',
            'Unit1' => $details['Unit1'] ?? 'mg/dL',
            'Decimal' => $details['Decimal'] ?? 0,
        ];

        foreach (['ResultType', 'RefType', 'FormulaCode', 'members', 'ExpectedTAT'] as $key) {
            if (array_key_exists($key, $details)) {
                $normalized[$key] = $details[$key];
            }
        }

        return $normalized;
    }

    private function buildRefNumEntries(string $numRefType, bool $multiple = false): array


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetTestStatus()
    {
        $result = ValueSet::getRaw('test_status');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $statusMap = array_combine($keys, $values);
        $this->assertEquals('Waiting for Results', $statusMap['PENDING']);
        $this->assertEquals('Analyzing', $statusMap['IN_PROCESS']);
        $this->assertEquals('Verified & Signed', $statusMap['VERIFIED']);
    }


value00: tests/_support/Traits/CreatesPatients.php
value01:
[
            'PatientID'      => 'PAT' . $faker->numerify('##########'),
            'AlternatePID'   => 'ALT' . $faker->numerify('##########'),
            'Prefix'         => $faker->title,
            'NameFirst'      => 'Test',
            'NameMiddle'     => $faker->firstName,
            'NameLast'       => 'Patient',
            'Suffix'         => 'S.Kom',
            'Sex'            => (string) $faker->numberBetween(5, 6),
            'PlaceOfBirth'   => $faker->city,
            'Birthdate'      => $faker->date('Y-m-d'),
            'ZIP'            => $faker->postcode,
            'Street_1'       => $faker->streetAddress,
            'City'           => $faker->city


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function generateTestCode(string $prefix): string
    {
        $clean = strtoupper(substr($prefix, 0, 3));
        $suffix = strtoupper(substr(md5((string) microtime(true) . random_int(0, 9999)), 0, 6));
        return substr($clean . $suffix, 0, 10);
    }

    private function resolveMemberIds(array $codes): array
    {
        $ids = [];
        foreach ($codes as $code) {
            $row = $this->testModel->where('TestSiteCode', $code)->where('EndDate IS NULL')->first();
            $this->assertNotEmpty($row, "Seeded test code {$code} not found");
            $ids[] = (int) $row['TestSiteID'];
        }
        return $ids;
    }
}


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    protected function setUp(): void
    {
        parent::setUp();
        $this->testModel = new TestDefSiteModel();
    }

    public function testCreateTechnicalWithoutReferenceOrTestMap(): void
    {
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type);
        }
    }

    public function testCreateTechnicalWithNumericReference(): void
    {
        $refnum = $this->buildRefNumEntries('NMRC', true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'NMRIC',
                'RefType' => 'RANGE',
            ], $refnum);
        }
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetLabelConvertsCodeToLabel()
    {
        $this->assertEquals('Female', ValueSet::getLabel('sex', 'F'));
        $this->assertEquals('Male', ValueSet::getLabel('sex', 'M'));
        $this->assertEquals('Unknown', ValueSet::getLabel('sex', 'U'));
    }

    public function testGetLabelForOrderPriority()
    {
        $this->assertEquals('Stat', ValueSet::getLabel('order_priority', 'S'));
        $this->assertEquals('ASAP', ValueSet::getLabel('order_priority', 'A'));
        $this->assertEquals('Routine', ValueSet::getLabel('order_priority', 'R'));
        $this->assertEquals('Preop', ValueSet::getLabel('order_priority', 'P'));
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    public function testCreateCalculatedTestWithoutReferenceOrMap(): void
    {
        $this->assertCalculatedCreated(false);
    }

    public function testCreateCalculatedTestWithReferenceAndTestMap(): void
    {
        $refnum = $this->buildRefNumEntries('NMRC', true);
        $testmap = $this->buildTestMap(true, true);
        $members = $this->resolveMemberIds(['GLU', 'CREA']);
        $this->assertCalculatedCreated(true, $refnum, $testmap, $members);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
        $values = array_column($result, 'value');
        $labels = array_column($result, 'label');

        $this->assertContains('F', $values);
        $this->assertContains('M', $values);
        $this->assertContains('U', $values);
        $this->assertContains('Female', $labels);
        $this->assertContains('Male', $labels);
        $this->assertContains('Unknown', $labels);
    }


value00: app/Views/errors/html/error_exception.php
value01:
                                        foreach ($row['args'] as $key => $value) : ?>
                                            <tr>
                                                <td><code><?= esc(isset($params[$key]) ? '$' . $params[$key]->name : "#{$key}") ?></code></td>
                                                <td><pre><?= esc(print_r($value, true)) ?></pre></td>
                                            </tr>
                                        <?php endforeach ?>

                                        </table>
                                    </div>
                                <?php else : ?>
                                    ()
                                <?php endif;


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetMathSigns()
    {
        $result = ValueSet::getRaw('math_sign');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $signMap = array_combine($keys, $values);
        $this->assertEquals('Equal', $signMap['=']);
        $this->assertEquals('Less than', $signMap['<']);
        $this->assertEquals('Greater than', $signMap['>']);
        $this->assertEquals('Less than or equal to', $signMap['<=']);
        $this->assertEquals('Greater than or equal to', $signMap['>=']);
    }


value00: app/Views/errors/html/error_exception.php
value01:
?></td>
                    </tr>
                </table>

                <?php $headers = $response->headers(); ?>
                <?php if (! empty($headers)) : ?>
                    <h3>Headers</h3>

                    <table>
                        <thead>
                            <tr>
                                <th>Header</th>
                                <th>Value</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php foreach ($headers as $name => $value) : ?>
                            <tr>
                                <td><?=


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetOrderPriorityFromAlias()
    {
        $result = ValueSet::getRaw('priority');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $priorityMap = array_combine($keys, $values);
        $this->assertEquals('Stat', $priorityMap['S']);
        $this->assertEquals('Routine', $priorityMap['R']);
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
        if ($multiple) {
            $entries[] = [
                'SpcType' => 'GEN',
                'TxtRefType' => $txtRefType,
                'Sex' => '1',
                'AgeStart' => 0,
                'AgeEnd' => 120,
                'RefTxt' => $txtRefType === 'VSET' ? 'HIGH=High;LOW=Low' : 'ABN=Abnormal',
                'Flag' => 'N',
            ];
        }

        return $entries;
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    {
        $rangeType = $numRefType === 'THOLD' ? 'PANIC' : 'REF';
        $entries = [
            [
                'NumRefType' => $numRefType,
                'RangeType' => $rangeType,
                'Sex' => '2',
                'LowSign' => 'GE',
                'Low' => 10,
                'HighSign' => 'LE',
                'High' => $numRefType === 'THOLD' ? 40 : 20,
                'AgeStart' => 0,
                'AgeEnd' => 120,
                'Flag' => 'N',
                'Interpretation' => 'Normal range',
            ],
        ];


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetResultResultStatus()
    {
        $result = ValueSet::getRaw('result_status');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $statusMap = array_combine($keys, $values);
        $this->assertEquals('Preliminary', $statusMap['PRELIMINARY']);
        $this->assertEquals('Final', $statusMap['FINAL']);
        $this->assertEquals('Corrected', $statusMap['CORRECTED']);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetAllContainsMetadata()
    {
        $result = ValueSet::getAll();
        $this->assertArrayHasKey('name', $result['sex']);
        $this->assertArrayHasKey('VSName', $result['sex']);
        $this->assertArrayHasKey('VCategory', $result['sex']);
        $this->assertArrayHasKey('values', $result['sex']);
    }

    public function testGetPatientSex()
    {
        $result = ValueSet::get('sex');
        $this->assertEquals('F', $result[0]['value']);
        $this->assertEquals('Female', $result[0]['label']);
        $this->assertEquals('M', $result[1]['value']);
        $this->assertEquals('Male', $result[1]['label']);
    }


value00: tests/_support/Traits/CreatesPatients.php
value01:
            'Religion'       => (string) $faker->numberBetween(206, 212),
            'Ethnic'         => (string) $faker->numberBetween(213, 220),
            'Citizenship'    => 'WNI',
            'DeathIndicator' => (string) $faker->numberBetween(16, 17),
            'PatIdt' => [
                'IdentifierType' => 'ID',
                'Identifier'     => $faker->numerify('################')
            ],
            'PatAtt' => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ]
            ]


value00: app/Views/errors/html/error_exception.php
value01:
foreach ($GLOBALS[$var] as $key => $value) : ?>
                            <tr>
                                <td><?= esc($key) ?></td>
                                <td>
                                    <?php if (is_string($value)) : ?>
                                        <?= esc($value) ?>
                                    <?php else: ?>
                                        <pre><?= esc(print_r($value, true)) ?></pre>
                                    <?php endif; ?>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                        </tbody>
                    </table>

                <?php endforeach


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetResultType()
    {
        $result = ValueSet::getRaw('result_type');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $typeMap = array_combine($keys, $values);
        $this->assertEquals('Numeric', $typeMap['NMRIC']);
        $this->assertEquals('Range', $typeMap['RANGE']);
        $this->assertEquals('Text', $typeMap['TEXT']);
        $this->assertEquals('Value set', $typeMap['VSET']);
    }

    public function testClearCacheDoesNotThrowError()
    {
        ValueSet::getAll();
        $result = ValueSet::clearCache();
        $this->assertTrue($result);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetPatEntityType()
    {
        $result = ValueSet::getRaw('entity_type');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $entityMap = array_combine($keys, $values);
        $this->assertEquals('HIS', $entityMap['HIS']);
        $this->assertEquals('Site', $entityMap['SITE']);
        $this->assertEquals('Workstation', $entityMap['WST']);
        $this->assertEquals('Equipment/Instrument', $entityMap['INST']);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testTransformLabels()
    {
        $data = [
            ['Gender' => 'F', 'Country' => 'IDN'],
            ['Gender' => 'M', 'Country' => 'USA']
        ];

        $result = ValueSet::transformLabels($data, [
            'Gender' => 'sex',
            'Country' => 'country'
        ]);

        $this->assertEquals('F', $result[0]['Gender']);
        $this->assertEquals('Female', $result[0]['GenderLabel']);
        $this->assertEquals('M', $result[1]['Gender']);
        $this->assertEquals('Male', $result[1]['GenderLabel']);
        $this->assertEquals('USA', $result[1]['Country']);
        $this->assertEquals('United States of America', $result[1]['CountryLabel']);
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);
        $response->assertStatus(201);
        $response->assertJSONFragment([
            'status' => 'created',
            'message' => 'Test created successfully',
        ]);

        $json = json_decode($response->getJSON(), true);
        $this->assertArrayHasKey('data', $json);
        $this->assertArrayHasKey('TestSiteId', $json['data']);
        $this->assertIsInt($json['data']['TestSiteId']);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetSpecimenType()
    {
        $result = ValueSet::getRaw('specimen_type');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $typeMap = array_combine($keys, $values);
        $this->assertEquals('Whole blood', $typeMap['BLD']);
        $this->assertEquals('Serum', $typeMap['SER']);
        $this->assertEquals('Plasma', $typeMap['PLAS']);
        $this->assertEquals('Urine', $typeMap['UR']);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetOrderRequestStatus()
    {
        $result = ValueSet::getRaw('request_status');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $statusMap = array_combine($keys, $values);
        $this->assertEquals('To be collected', $statusMap['STC']);
        $this->assertEquals('Collected', $statusMap['SCtd']);
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
        if ($multiple) {
            $entries[] = [
                'NumRefType' => $numRefType,
                'RangeType' => $rangeType,
                'Sex' => '1',
                'LowSign' => '>',
                'Low' => 5,
                'HighSign' => '<',
                'High' => $numRefType === 'THOLD' ? 50 : 15,
                'AgeStart' => 0,
                'AgeEnd' => 99,
                'Flag' => 'N',
                'Interpretation' => 'Alternate range',
            ];
        }

        return $entries;
    }


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
    public function testShowByNameOrderPriority()
    {
        $result = $this->call('get', 'api/valueset/order_priority');

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsArray($data['data']);

        $labels = array_column($data['data'], 'label');
        $this->assertContains('Stat', $labels);
        $this->assertContains('ASAP', $labels);
        $this->assertContains('Routine', $labels);
    }


value00: tests/unit/HealthTest.php
value01:
        if ($env) {
            // BaseURL in .env is a valid URL?
            // phpunit.xml.dist sets app.baseURL in $_SERVER
            // So if you set app.baseURL in .env, it takes precedence
            $config = new App();
            $this->assertTrue(
                $validation->check($config->baseURL, 'valid_url'),
                'baseURL "' . $config->baseURL . '" in .env is not valid URL',
            );
            return;
        }

        // If no baseURL in .env, check app/Config/App.php
        $reader = new ConfigReader();
        $this->assertTrue(
            $validation->check($reader->baseURL, 'valid_url'),
            'baseURL "' . $reader->baseURL . '" in app/Config/App.php is not valid URL',
        );
    }
}


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetContainerCapColor()
    {
        $result = ValueSet::getRaw('container_cap_color');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $colorMap = array_combine($keys, $values);
        $this->assertEquals('Purple', $colorMap['PRPL']);
        $this->assertEquals('Red', $colorMap['RED']);
        $this->assertEquals('Yellow', $colorMap['YLLW']);
        $this->assertEquals('Green', $colorMap['GRN']);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetPatientReligion()
    {
        $result = ValueSet::getRaw('religion');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $religionMap = array_combine($keys, $values);
        $this->assertEquals('Islam', $religionMap['ISLAM']);
        $this->assertEquals('Kristen', $religionMap['KRSTN']);
        $this->assertEquals('Katolik', $religionMap['KTLIK']);
        $this->assertEquals('Hindu', $religionMap['HINDU']);
        $this->assertEquals('Budha', $religionMap['BUDHA']);
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    public function testCreateTechnicalWithThresholdReferenceAndTestMap(): void
    {
        $refnum = $this->buildRefNumEntries('THOLD', true);
        $testmap = $this->buildTestMap(true, true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'NMRIC',
                'RefType' => 'THOLD',
            ], $refnum, null, $testmap);
        }
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    public function testCreateTechnicalWithValuesetReference(): void
    {
        $reftxt = $this->buildRefTxtEntries('VSET', true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'VSET',
                'RefType' => 'VSET',
            ], null, $reftxt);
        }
    }

    public function testCreateTechnicalWithNumericReferenceAndTestMap(): void
    {
        $refnum = $this->buildRefNumEntries('NMRC', true);
        $testmap = $this->buildTestMap(true, true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'NMRIC',
                'RefType' => 'RANGE',
            ], $refnum, null, $testmap);
        }
    }


value00: app/Views/errors/html/error_exception.php
value01:
($headers as $name => $value) : ?>
                            <tr>
                                <td><?= esc($name, 'html') ?></td>
                                <td>
                                <?php
                                if ($value instanceof Header) {
                                    echo esc($response->getHeaderLine($name), 'html');
                                } else {
                                    foreach ($value as $i => $header) {
                                        echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
                                    }
                                }
                                ?>
                                </td>
                            </tr>
                        <?php


value00: app/Views/errors/html/error_exception.php
value01:
esc($request->getIPAddress()) ?></td>
                        </tr>
                        <tr>
                            <td style="width: 10em">Is AJAX Request?</td>
                            <td><?= $request->isAJAX() ? 'yes' : 'no' ?></td>
                        </tr>
                        <tr>
                            <td>Is CLI Request?</td>
                            <td><?= $request->isCLI() ? 'yes' : 'no' ?></td>
                        </tr>
                        <tr>
                            <td>Is Secure Request?</td>
                            <td><?= $request->isSecure() ? 'yes' : 'no'


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetOrderPriority()
    {
        $result = ValueSet::getRaw('order_priority');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $this->assertContains('S', $keys);
        $this->assertContains('A', $keys);
        $this->assertContains('R', $keys);

        $priorityMap = array_combine($keys, $values);
        $this->assertEquals('Stat', $priorityMap['S']);
        $this->assertEquals('ASAP', $priorityMap['A']);
        $this->assertEquals('Routine', $priorityMap['R']);
        $this->assertEquals('Preop', $priorityMap['P']);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetTestType()
    {
        $result = ValueSet::get('test_type');
        $this->assertIsArray($result);
        $this->assertNotEmpty($result);
        
        $values = array_column($result, 'value');
        $this->assertContains('TEST', $values);
        $this->assertContains('PARAM', $values);
        $this->assertContains('CALC', $values);
        $this->assertContains('GROUP', $values);
        $this->assertContains('TITLE', $values);
    }


value00: app/Views/errors/html/error_exception.php
value01:
?>

                    <h3>$<?= esc($var) ?></h3>

                    <table style="width: 100%">
                        <thead>
                            <tr>
                                <th>Key</th>
                                <th>Value</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
<?php

declare(strict_types=1);

namespace Tests\Feature\Test;

use App\Models\Test\TestDefSiteModel;
use CodeIgniter\Test\CIUnitTestCase;
use CodeIgniter\Test\FeatureTestTrait;

class TestCreateVariantsTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    private const SITE_ID = 1;

    protected string $endpoint = 'api/test';

    private TestDefSiteModel $testModel;

    protected function setUp(): void
    {
        parent::setUp();
        $this->testModel = new TestDefSiteModel();
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    public function testCreateTechnicalWithTextReferenceAndTestMap(): void
    {
        $reftxt = $this->buildRefTxtEntries('TEXT', true);
        $testmap = $this->buildTestMap(true, true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'TEXT',
                'RefType' => 'TEXT',
            ], null, $reftxt, $testmap);
        }
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    public function testCreateTechnicalWithValuesetReferenceAndTestMap(): void
    {
        $reftxt = $this->buildRefTxtEntries('VSET', true);
        $testmap = $this->buildTestMap(true, true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'VSET',
                'RefType' => 'VSET',
            ], null, $reftxt, $testmap);
        }
    }

    public function testCreateTechnicalValuesetWithoutReferenceButWithMap(): void
    {
        $testmap = $this->buildTestMap(false, true);
        foreach (['TEST', 'PARAM'] as $type) {
            $this->assertTechnicalCreated($type, [
                'ResultType' => 'VSET',
                'RefType' => 'VSET',
            ], null, null, $testmap);
        }
    }


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
        if ($multipleDetails) {
            $map[0]['details'][] = [
                'HostTestCode' => 'CREA',
                'HostTestName' => 'Creatinine',
                'ConDefID' => 2,
                'ClientTestCode' => 'CREA_C',
                'ClientTestName' => 'Creatinine Client',
            ];
        }


value00: app/Views/errors/html/error_exception.php
value01:
                                ?>
                                </td>
                            </tr>
                        <?php endforeach; ?>
                        </tbody>
                    </table>

                <?php endif; ?>
            </div>

            <!-- Files -->
            <div class="content" id="files">
                <?php $files = get_included_files(); ?>

                <ol>
                <?php foreach ($files as $file) :?>
                    <li><?= esc(clean_path($file)) ?></li>
                <?php endforeach


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
    public function testShowByNameSpecimenStatus()
    {
        $result = $this->call('get', 'api/valueset/specimen_status');

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsArray($data['data']);

        $values = array_column($data['data'], 'value');
        $labels = array_column($data['data'], 'label');

        $this->assertContains('STC', $values);
        $this->assertContains('SCtd', $values);
        $this->assertContains('To be collected', $labels);
        $this->assertContains('Collected', $labels);
    }
}


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
        if ($multipleMaps) {
            $map[] = [
                'HostType' => 'WST',
                'HostID' => '3',
                'ClientType' => 'INST',
                'ClientID' => '2',
                'details' => [
                    [
                        'HostTestCode' => 'HB',
                        'HostTestName' => 'Hemoglobin',
                        'ConDefID' => 3,
                        'ClientTestCode' => 'HB_C',
                        'ClientTestName' => 'Hemoglobin Client',
                    ],
                ],
            ];
        }

        return $map;
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetRawReturnsArrayOfKeyValuePairs()
    {
        $result = ValueSet::getRaw('sex');
        $this->assertIsArray($result);
        $this->assertNotEmpty($result);
        $this->assertArrayHasKey('key', $result[0]);
        $this->assertArrayHasKey('value', $result[0]);
    }

    public function testGetRawPatientSexContainsExpectedData()
    {
        $result = ValueSet::getRaw('sex');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $this->assertContains('F', $keys);
        $this->assertContains('M', $keys);
        $this->assertContains('Female', $values);
        $this->assertContains('Male', $values);
    }


value00: tests/_support/Traits/CreatesPatients.php
value01:
<?php

namespace Tests\Support\Traits;

use App\Models\Patient\PatientModel;
use Faker\Factory;

trait CreatesPatients
{
    protected function createTestPatient(array $overrides = []): int
    {
        $faker = Factory::create('id_ID');
        $patientPayload = array_merge([
            'PatientID'      => 'PAT' . $faker->numerify('##########'),
            'AlternatePID'   => 'ALT' . $faker->numerify('##########')


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function buildTestMap(bool $multipleMaps = false, bool $multipleDetails = false): array
    {
        $map = [
            [
                'HostType' => 'SITE',
                'HostID' => '1',
                'ClientType' => 'WST',
                'ClientID' => '1',
                'details' => [
                    [
                        'HostTestCode' => 'GLU',
                        'HostTestName' => 'Glucose',
                        'ConDefID' => 1,
                        'ClientTestCode' => 'GLU_C',
                        'ClientTestName' => 'Glucose Client',
                    ],
                ],
            ],
        ];


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
    public function testRefreshClearsCache()
    {
        ValueSet::getAll();

        $result = $this->call('post', 'api/valueset/refresh');

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertEquals('Cache cleared', $data['message']);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetAllReturnsMultipleLookups()
    {
        $result = ValueSet::getAll();
        $this->assertIsArray($result);
        $this->assertArrayHasKey('sex', $result);
        $this->assertArrayHasKey('specimen_type', $result);
        $this->assertArrayHasKey('order_priority', $result);
        $this->assertArrayHasKey('specimen_status', $result);
    }

    public function testGetAllContainsValuesKey()
    {
        $result = ValueSet::getAll();
        $this->assertIsArray($result['sex']['values']);
        $this->assertNotEmpty($result['sex']['values']);
    }


value00: app/Views/errors/html/error_exception.php
value01:
esc(static::describeMemory(memory_get_peak_usage(true))) ?></td>
                        </tr>
                        <tr>
                            <td>Memory Limit:</td>
                            <td><?= esc(ini_get('memory_limit')) ?></td>
                        </tr>
                    </tbody>
                </table>

            </div>

        </div>  <!-- /tab-content -->

    </div> <!-- /container -->
    <?php endif; ?>

</body>
</html>


value00: app/Views/errors/html/error_exception.php
value01:
?>

                <?php if ($empty) : ?>

                    <div class="alert">
                        No $_GET, $_POST, or $_COOKIE Information to show.
                    </div>

                <?php endif; ?>

                <?php $headers = $request->headers(); ?>
                <?php if (! empty($headers)) : ?>

                    <h3>Headers</h3>

                    <table>
                        <thead>
                            <tr>
                                <th>Header</th>
                                <th>Value</th>
                            </tr>
                        </thead>
                        <tbody>
                        <?php foreach ($headers as $name => $value)


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function assertCalculatedCreated(
        bool $withDetails,
        ?array $refnum = null,
        ?array $testmap = null,
        array $members = []
    ): void {
        $payload = $this->buildCalculatedPayload($members);

        if ($withDetails && $refnum !== null) {
            $payload['refnum'] = $refnum;
        }
        if ($withDetails && $testmap !== null) {
            $payload['testmap'] = $testmap;
        }

        $response = $this->withBodyFormat('json')->call('post', $this->endpoint, $payload);


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetOptions()
    {
        $result = ValueSet::getOptions('sex');
        $this->assertIsArray($result);
        $this->assertNotEmpty($result);
        $this->assertArrayHasKey('key', $result[0]);
        $this->assertArrayHasKey('value', $result[0]);
    }
}


value00: app/Views/errors/html/error_exception.php
value01:
foreach ($files as $file) :?>
                    <li><?= esc(clean_path($file)) ?></li>
                <?php endforeach ?>
                </ol>
            </div>

            <!-- Memory -->
            <div class="content" id="memory">

                <table>
                    <tbody>
                        <tr>
                            <td>Memory Usage</td>
                            <td><?= esc(static::describeMemory(memory_get_usage(true))) ?></td>
                        </tr>
                        <tr>
                            <td style="width: 12em">Peak Memory Usage:</td>
                            <td><?= esc(static::describeMemory(memory_get_peak_usage(true)))


value00: tests/feature/Test/TestCreateVariantsTest.php
value01:
    private function buildTechnicalPayload(string $testType, array $details = []): array
    {
        $payload = [
            'SiteID' => self::SITE_ID,
            'TestSiteCode' => $this->generateTestCode($testType),
            'TestSiteName' => 'Auto ' . strtoupper($testType),
            'TestType' => $testType,
            'SeqScr' => 900,
            'SeqRpt' => 900,
            'VisibleScr' => 1,
            'VisibleRpt' => 1,
            'CountStat' => 1,
        ];

        $payload['details'] = $this->normalizeDetails($details);

        return $payload;
    }


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
        $this->assertIsArray($data['data']);
        
        // Find specific valuesets in the array
        $valuesets = [];
        foreach ($data['data'] as $item) {
            $valuesets[$item['value']] = $item;
        }
        
        $this->assertArrayHasKey('sex', $valuesets);
        $this->assertArrayHasKey('specimen_type', $valuesets);
        $this->assertArrayHasKey('order_priority', $valuesets);
        $this->assertArrayHasKey('specimen_status', $valuesets);


value00: tests/feature/ValueSet/ValueSetControllerTest.php
value01:
<?php

namespace Tests\Feature\ValueSet;

use CodeIgniter\Test\FeatureTestTrait;
use CodeIgniter\Test\CIUnitTestCase;
use App\Libraries\ValueSet;

class ValueSetControllerTest extends CIUnitTestCase
{
    use FeatureTestTrait;

    protected function setUp(): void
    {
        parent::setUp();
        ValueSet::clearCache();
    }

    public function testIndexReturnsAllLookupsWithCounts()
    {
        $result = $this->call('get', 'api/valueset');

        $result->assertStatus(200);

        $json = $result->getJSON();
        $data = json_decode($json, true);

        $this->assertEquals('success', $data['status']);
        $this->assertIsArray($data['data']);
        
        // Find specific valuesets in the array


value00: app/Views/errors/html/error_exception.php
value01:
as $name => $value) : ?>
                            <tr>
                                <td><?= esc($name, 'html') ?></td>
                                <td>
                                <?php
                                if ($value instanceof Header) {
                                    echo esc($value->getValueLine(), 'html');
                                } else {
                                    foreach ($value as $i => $header) {
                                        echo ' ('. $i+1 . ') ' . esc($header->getValueLine(), 'html');
                                    }
                                }
                                ?>
                                </td>
                            </tr>
                        <?php endforeach;


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetLabelReturnsNullForInvalidKey()
    {
        $this->assertNull(ValueSet::getLabel('sex', '99'));
        $this->assertNull(ValueSet::getLabel('sex', 'invalid'));
    }

    public function testGetLabelReturnsNullForInvalidLookup()
    {
        $this->assertNull(ValueSet::getLabel('nonexistent_lookup', '1'));
    }

    public function testGetReturnsNullForInvalidLookup()
    {
        $result = ValueSet::get('nonexistent_lookup');
        $this->assertNull($result);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetReturnsFormattedValues()
    {
        $result = ValueSet::get('sex');
        $this->assertEquals('F', $result[0]['value']);
        $this->assertEquals('Female', $result[0]['label']);
        $this->assertEquals('M', $result[1]['value']);
        $this->assertEquals('Male', $result[1]['label']);
    }

    public function testGetWithSpecialCharactersInKey()
    {
        $result = ValueSet::get('result_unit');
        $values = array_column($result, 'value');
        $this->assertContains('g/dL', $values);
        $this->assertContains('mg/dL', $values);
        $this->assertContains('x106/mL', $values);
    }


value00: tests/unit/ValueSet/ValueSetTest.php
value01:
    public function testGetOrganizationSiteType()
    {
        $result = ValueSet::getRaw('site_type');
        $keys = array_column($result, 'key');
        $values = array_column($result, 'value');

        $siteMap = array_combine($keys, $values);
        $this->assertEquals('Government Hospital', $siteMap['GH']);
        $this->assertEquals('Private Hospital', $siteMap['PH']);
        $this->assertEquals('Government Lab', $siteMap['GL']);
        $this->assertEquals('Private Lab', $siteMap['PL']);
    }


value00: tests/_support/Traits/CreatesPatients.php
value01:
            'PatAtt' => [
                [ 'Address' => '/api/upload/' . $faker->uuid . '.jpg' ]
            ],
            'PatCom' => $faker->sentence,
        ], $overrides);

        if ($patientPayload['DeathIndicator'] === '16') {
            $patientPayload['DeathDateTime'] = $faker->date('Y-m-d H:i:s');
        } else {
            $patientPayload['DeathDateTime'] = null;
        }

        $patientModel = new PatientModel();
        $internalPID = $patientModel->createPatient($patientPayload);
        if (!$internalPID) {
            throw new \RuntimeException('Failed to insert test patient');
        }

        return $internalPID;
    }
}


value00: tests/unit/HealthTest.php
value01:
<?php

use CodeIgniter\Test\CIUnitTestCase;
use Config\App;
use Tests\Support\Libraries\ConfigReader;

/**
 * @internal
 */
final class HealthTest extends CIUnitTestCase
{
    public function testIsDefinedAppPath(): void
    {
        $this->assertTrue(defined('APPPATH'));
    }

    public function testBaseUrlHasBeenSet(): void
    {
        $validation = service('validation');

        $env = false;

        // Check the baseURL in .env
        if (is_file(HOMEPATH . '.env')) {
            $env = preg_grep('/^app\.baseURL = ./', file(HOMEPATH . '.env')) !== false;
        }

        if ($env) {
            // BaseURL in .env is a valid URL?


value00: app/Views/errors/html/error_exception.php
value01:
$request->isSecure() ? 'yes' : 'no' ?></td>
                        </tr>
                        <tr>
                            <td>User Agent</td>
                            <td><?= esc($request->getUserAgent()->getAgentString()) ?></td>
                        </tr>

                    </tbody>
                </table>


                <?php $empty = true; ?>
                <?php foreach (['_GET', '_POST', '_COOKIE'] as $var) : ?>
                    <?php
                    if (empty($GLOBALS[$var]) || ! is_array($GLOBALS[$var])) {
                        continue;
                    } ?>

                    <?php $empty = false; ?>

                    <h3>$<?= esc($var)


