'residential'|'commercial'|'industrial'|'plot', * 'entrance' => 'E', 'facing' => 'E', 'city' => 'Zirakpur', 'country' => 'india', * 'rooms' => [['id' => 'kitchen', 'name' => 'Kitchen', 'dir' => 'SE'], ...], * 'plot' => ['road' => 'E', 'slope' => 'NE', 'shape' => 'square', 'len' => 40, 'wid' => 60, 'water' => 'none', * 'roads' => ['E','N'], 'shoola' => 'none', 'ext' => 'none', 'wall' => 'all 4', 'trees' => 'none'], * ]); */ defined( 'ABSPATH' ) || exit; require_once __DIR__ . '/bootstrap.php'; const VASTUIQ_SCORING_VERSION = '2.0.0'; const VASTUIQ_DIRS = ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']; const VASTUIQ_DIR_NAMES = ['N' => 'North', 'NE' => 'North-East', 'E' => 'East', 'SE' => 'South-East', 'S' => 'South', 'SW' => 'South-West', 'W' => 'West', 'NW' => 'North-West', 'C' => 'Centre']; /** Free Vastu Analyzer room ids → the name the home engine reads (residential). */ function vastuiq_residential_name(string $id): ?string { static $m = ['living' => 'Living Room', 'master' => 'Master Bedroom', 'kitchen' => 'Kitchen', 'dining' => 'Dining Room', 'pooja' => 'Pooja Room', 'study' => 'Study', 'guest' => 'Guest Room', 'store' => 'Store Room', 'servant' => 'Guest Room', 'garage' => 'Garage', 'terrace' => 'Terrace', 'staircase' => 'Staircase']; if (isset($m[$id])) return $m[$id]; if (preg_match('/^bed([2-9]|10)$/', $id)) return 'Bedroom'; if (preg_match('/^toilet([1-9]|10)$/', $id)) return 'Bathroom'; if (preg_match('/^balcony[12]$/', $id)) return 'Balcony'; return null; } /** AI Floor Plan labels the home engine does not know by name → a name it reads the same way. */ function vastuiq_residential_label(string $name): string { $base = strtolower(trim(preg_replace('/\s*\(.*\)$/', '', $name))); static $m = ['drawing room' => 'Living Room', 'servant room' => 'Guest Room', "servant's room" => 'Guest Room', 'servants room' => 'Guest Room']; return isset($m[$base]) ? $m[$base] . (preg_match('/\(.*\)$/', $name, $x) ? ' ' . $x[0] : '') : $name; } /** Free Vastu Analyzer room ids → business room types (commercial / industrial). */ function vastuiq_business_type(string $id): ?string { static $m = [ // commercial 'reception' => 'reception', 'director' => 'owner_cabin', 'manager' => 'manager_cabin', 'openoffice' => 'open_office', 'conference' => 'conference', 'accounts' => 'accounts', 'pantry' => 'pantry', 'server' => 'electrical', 'washcom' => 'washroom', 'storecom' => 'store', 'hrroom' => 'hr_admin', 'lounge' => 'reception', 'parkingcom' => 'parking', 'showroom' => 'showroom', 'stairscom' => 'staircase', 'liftcom' => 'lift', 'securitycom' => 'security', // industrial 'mdcabin' => 'owner_cabin', 'adminoffice' => 'factory_admin', 'factoryfloor' => 'heavy_machinery', 'warehouse' => 'raw_material', 'receptionind' => 'reception', 'cafeteria' => 'canteen', 'security' => 'gate_house', 'generator' => 'fire', 'loadingbay' => 'dispatch', 'washind' => 'washroom', 'boiler' => 'fire', 'parkingind' => 'parking', 'lightmach' => 'light_machinery', 'rawliquid' => 'raw_liquid', 'ugtank' => 'ug_tank', 'ohtank' => 'oh_tank', 'fuelstore' => 'fuel_chemical', 'restarea' => 'rest_area', 'effluent' => 'effluent', 'stairsind' => 'staircase', ]; return $m[$id] ?? null; } function vastuiq_dir(?string $d): string { $d = strtoupper(trim((string)$d)); $d = str_replace(['NORTH-EAST', 'NORTH EAST', 'NORTHEAST'], 'NE', $d); $d = str_replace(['SOUTH-EAST', 'SOUTH EAST', 'SOUTHEAST'], 'SE', $d); $d = str_replace(['SOUTH-WEST', 'SOUTH WEST', 'SOUTHWEST'], 'SW', $d); $d = str_replace(['NORTH-WEST', 'NORTH WEST', 'NORTHWEST'], 'NW', $d); $d = ['NORTH' => 'N', 'EAST' => 'E', 'SOUTH' => 'S', 'WEST' => 'W', 'CENTRE' => 'C', 'CENTER' => 'C'][$d] ?? $d; return in_array($d, VASTUIQ_DIRS, true) || $d === 'C' ? $d : ''; } /** Rough coordinates for the country (the score never depends on them; they only label the sun notes). */ function vastuiq_country_point(string $country): array { static $pts = ['india' => [22.0, 79.0, 'IN'], 'nepal' => [28.2, 84.0, 'NP'], 'uae' => [24.4, 54.4, 'AE'], 'usa' => [39.8, -98.6, 'US'], 'uk' => [53.0, -1.5, 'GB'], 'canada' => [56.1, -106.3, 'CA'], 'australia' => [-25.3, 133.8, 'AU'], 'singapore' => [1.35, 103.8, 'SG']]; $k = strtolower(trim($country)); return $pts[$k] ?? $pts['india']; } /** Directions that score 85+ for a room (best first), or the single best one. */ function vastuiq_best_dirs(array $scores): array { if (!$scores) return []; arsort($scores); $best = array_keys(array_filter($scores, fn($s) => $s >= 85)); return $best ?: [array_key_first($scores)]; } function vastuiq_score_answers(array $a): array { $type = strtolower(trim((string)($a['type'] ?? 'residential'))); if (!in_array($type, ['residential', 'commercial', 'industrial', 'plot'], true)) throw new \InvalidArgumentException('Unknown property type'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- message is never printed. $notes = []; $facing = vastuiq_dir($a['facing'] ?? ''); if ($type !== 'plot' && $facing) { $notes[] = ['id' => 'facing', 'text' => 'Facing ' . VASTUIQ_DIR_NAMES[$facing] . '. Facing is shown for information: Vastu judges a building by its main entrance and where each room sits, so facing is not part of the score.']; } if ($type === 'plot') return vastuiq_score_plot($a, $notes); if ($type === 'residential') return vastuiq_score_home($a, $notes); return vastuiq_score_business($type, $a, $notes); } function vastuiq_rooms_in(array $a): array { $out = []; foreach (array_slice(is_array($a['rooms'] ?? null) ? $a['rooms'] : [], 0, 60) as $r) { if (!is_array($r)) continue; $id = preg_replace('/[^a-z0-9_]/', '', strtolower((string)($r['id'] ?? ''))); $name = trim(mb_substr(strip_tags((string)($r['name'] ?? $r['room'] ?? '')), 0, 60)); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- plain text from the plugin's own forms. $dir = vastuiq_dir($r['dir'] ?? $r['direction'] ?? ''); if ($dir === '' || ($id === '' && $name === '')) continue; $out[] = ['id' => $id, 'name' => $name !== '' ? $name : $id, 'dir' => $dir]; } return $out; } function vastuiq_score_home(array $a, array $notes): array { $rooms = vastuiq_rooms_in($a); $entrance = vastuiq_dir($a['entrance'] ?? ''); [$lat, $lon, $cc] = vastuiq_country_point((string)($a['country'] ?? 'india')); $city = trim(mb_substr(strip_tags((string)($a['city'] ?? '')), 0, 60)); // phpcs:ignore WordPress.WP.AlternativeFunctions.strip_tags_strip_tags -- plain text from the plugin's own forms. $engineRooms = []; $back = []; $unmatched = []; foreach ($rooms as $i => $r) { $canon = $r['id'] !== '' ? vastuiq_residential_name($r['id']) : null; if ($canon === null) $canon = vastuiq_residential_label($r['name']); $key = $canon . ' ' . ($i + 1); // unique name; the engine ignores the trailing number $engineRooms[] = ['name' => $key, 'direction' => $r['dir']]; $back[$key] = $r; } if (!$engineRooms && !$entrance) throw new \InvalidArgumentException('Add the main entrance or at least one room'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- message is never printed. if (!$engineRooms) $engineRooms[] = ['name' => 'Living Room 0', 'direction' => $entrance]; // never reached from the tools $in = \VastuIQWP\Engine\Engine::fromDirections([ 'location' => ['lat' => $lat, 'lon' => $lon, 'label' => $city ?: 'India', 'country' => $cc], 'property' => ['type' => 'house', 'ownership' => 'own'], 'floors' => [['level' => 'main', 'rooms' => $engineRooms, 'entrance_direction' => $entrance]], ]); $rep = (new \VastuIQWP\Engine\Engine())->analyze($in); $out = []; $rules = new \VastuIQWP\Engine\Rules(); foreach ($rep['floors'][0]['rooms'] as $er) { $src = $back[$er['name']] ?? null; if (!$src) continue; $out[] = ['id' => $src['id'], 'name' => $src['name'], 'type' => $er['type'], 'type_label' => $er['label'] ?? '', 'dir' => $er['direction'], 'dir_name' => VASTUIQ_DIR_NAMES[$er['direction']] ?? $er['direction'], 'score' => $er['score'] ?? null, 'ideal' => $er['ideal'] ?? '', 'ideal_dirs' => vastuiq_best_dirs($rules->room($er['type'])['scores'] ?? []), 'basis' => $er['basis'] ?? 'classical', 'major' => false, 'weight' => (float)($rules->room($er['type'])['weight'] ?? 1.0), 'centre_penalty' => $er['direction'] === 'C' ? (int)(['high' => 6, 'medium' => 3, 'low' => 1][$rules->centre()['flag'][$er['type']] ?? ''] ?? 0) : 0, 'zone_meaning' => $er['zone_meaning'] ?? '', 'placement' => $er['placement'] ?? '', 'remedies' => array_values($er['remedies'] ?? [])]; } foreach ($rep['unmatched'] ?? [] as $u) { $src = $back[is_array($u) ? ($u['name'] ?? '') : $u] ?? null; $unmatched[] = $src ? $src['name'] : (is_array($u) ? ($u['name'] ?? '') : $u); } $majors = []; foreach ($rep['score']['major_defects'] as $m) { $txt = is_array($m) ? ($m['text'] ?? $m['title'] ?? '') : (string)$m; $mt = is_array($m) ? ($m['type'] ?? '') : ''; $md = is_array($m) ? ($m['direction'] ?? '') : ''; $named = ''; foreach ($out as &$o) if ($o['type'] === $mt && $o['dir'] === $md) { $o['major'] = true; if ($named === '') $named = $o['name'] . ($md === 'C' ? ' in the centre' : ' in the ' . $o['dir_name']); } unset($o); if ($mt === 'entrance') $named = 'Main entrance in the ' . (VASTUIQ_DIR_NAMES[$md] ?? $md); if ($named !== '') $txt = $named; $majors[] = ['type' => $mt, 'dir' => $md, 'text' => $txt]; } $ent = $rep['floors'][0]['entrance'] ?? null; return vastuiq_result('residential', [ 'score' => $rep['score']['overall'], 'uncapped' => $rep['score']['uncapped'], 'capped' => $rep['score']['capped'], 'cap' => $rep['score']['cap'], 'band_id' => $rep['score']['band']['id'], 'centre_penalty' => $rep['score']['centre_penalty'], 'entrance' => $ent ? ['dir' => $ent['direction'], 'dir_name' => $ent['direction_name'], 'score' => $ent['score'], 'ideal' => $ent['ideal'] ?? '', 'weight' => (float)($rules->entrance()['weight'] ?? 1.3), 'placement' => $ent['placement'] ?? '', 'remedies' => array_values($ent['remedies'] ?? []), 'major' => $ent['direction'] === 'SW'] : null, 'rooms' => $out, 'majors' => $majors, 'unmatched' => $unmatched, 'notes' => $notes, 'swaps' => vastuiq_home_swaps($rep, $back), 'method' => $rep['score']['method'], 'engine_version' => $rep['engine']['version'] ?? '', 'rules_version' => $rep['engine']['rules_version'] ?? '', ]); } /** The engine's "best use of your rooms" (real swaps only), with the page's room names. */ function vastuiq_home_swaps(array $rep, array $back): array { $sw = $rep['room_swaps'] ?? []; $moves = []; foreach ($sw['floors'] ?? [] as $f) foreach ($f['moves'] as $mv) { $src = $back[$mv['room']] ?? null; $moves[] = ['room' => $src ? $src['name'] : preg_replace('/\s+\d+$/', '', $mv['room']), 'dir' => $mv['direction'], 'new_use' => $mv['new_use'], 'before' => (int)$mv['use_score_before'], 'after' => (int)$mv['new_score']]; } return ['moves' => $moves, 'score_now' => (int)($sw['score_now'] ?? $rep['score']['overall']), 'score_after' => (int)($sw['score_after'] ?? $rep['score']['overall'])]; } function vastuiq_score_business(string $type, array $a, array $notes): array { $ps = new \VastuIQWP\Engine\PropertyScore(); $rooms = vastuiq_rooms_in($a); $in = []; foreach ($rooms as $r) { $t = $r['id'] !== '' ? vastuiq_business_type($r['id']) : null; $in[] = ['name' => $r['name'], 'type' => $t ?? '', 'direction' => $r['dir']]; } $res = $ps->building(['kind' => $type, 'entrance' => vastuiq_dir($a['entrance'] ?? '') ?: null, 'rooms' => $in]); $ids = array_column($rooms, 'id'); $out = []; $k = 0; foreach ($res['rooms'] as $r) { // rooms come back in the same order as sent (unmatched ones are skipped), so walk the input to find the id while ($k < count($rooms) && $rooms[$k]['name'] !== $r['name']) $k++; $out[] = ['id' => $ids[$k] ?? '', 'name' => $r['name'], 'type' => $r['type'], 'type_label' => $r['type_label'], 'dir' => $r['direction'], 'dir_name' => $r['direction_name'], 'score' => $r['score'], 'ideal' => implode(', ', array_map(fn($d) => VASTUIQ_DIR_NAMES[$d] ?? $d, $r['ideal'])), 'ideal_dirs' => $r['ideal'], 'basis' => $r['evidence'], 'basis_text' => $r['basis'], 'major' => $r['major'], 'weight' => (float)$r['weight'], 'centre_penalty' => (int)$r['centre_penalty'], 'remedies' => $r['remedies'] ?? [], 'placement' => $r['placement'] ?? '']; $k++; } $majors = array_map(fn($m) => ['type' => $m['type'], 'dir' => $m['direction'], 'text' => $m['text']], $res['major_defects']); $e = $res['entrance']; $notes2 = $notes; foreach ($res['notices'] as $n) if ($n['id'] === 'unmatched') $notes2[] = $n; return vastuiq_result($type, [ 'score' => $res['score']['overall'], 'uncapped' => $res['score']['uncapped'], 'capped' => $res['score']['capped'], 'cap' => $res['score']['cap'], 'band_id' => $res['score']['band_id'], 'centre_penalty' => $res['score']['centre_penalty'], 'entrance' => $e ? ['dir' => $e['direction'], 'dir_name' => $e['direction_name'], 'score' => $e['score'], 'ideal' => implode(', ', array_map(fn($d) => VASTUIQ_DIR_NAMES[$d] ?? $d, $e['ideal'])), 'weight' => (float)$e['weight'], 'major' => $e['major'], 'basis_text' => $e['basis'], 'remedies' => array_map(fn($x) => ['tier' => $x[0], 'text' => $x[1]], (new \VastuIQWP\Engine\Rules())->entrance()['remedies'] ?? []), 'placement' => (new \VastuIQWP\Engine\Rules())->entrance()['placement'] ?? ''] : null, 'swaps' => ['moves' => [], 'score_now' => $res['score']['overall'], 'score_after' => $res['score']['overall']], 'rooms' => $out, 'majors' => $majors, 'unmatched' => $res['unmatched'], 'notes' => $notes2, 'method' => $res['method'], 'engine_version' => $res['engine_version'], 'rules_version' => $res['rules_version'], ]); } function vastuiq_score_plot(array $a, array $notes): array { $p = is_array($a['plot'] ?? null) ? $a['plot'] : []; $road = vastuiq_dir($p['road'] ?? ''); if ($road === '' || $road === 'C') throw new \InvalidArgumentException('Choose the main road direction'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- message is never printed. // roads on the other sides (the main road itself is scored separately) $others = []; foreach ((array)($p['roads'] ?? []) as $r) { $d = vastuiq_dir((string)$r); if ($d && $d !== 'C' && $d !== $road) $others[$d] = true; } $n = !empty($others['N']) || !empty($others['NE']); $e = !empty($others['E']) || !empty($others['NE']); if (!$others) $roads = 'none'; elseif (!empty($others['N']) && !empty($others['E'])) $roads = 'N+E'; elseif ($n || $e) $roads = 'N or E'; else $roads = 'S or W'; $wall = ['all4' => 'all 4', 'three' => '3 sides', 'two' => '2 sides', 'none' => 'none'][(string)($p['wall'] ?? '')] ?? (string)($p['wall'] ?? ''); $ans = [ 'road' => $road, 'slope' => (string)($p['slope'] ?? ''), 'shape' => (string)($p['shape'] ?? ''), 'length' => (float)($p['len'] ?? 0), 'width' => (float)($p['wid'] ?? 0), 'water' => (string)($p['water'] ?? 'none'), 'roads' => $roads, 'shoola' => (string)($p['shoola'] ?? 'none'), 'ext' => (string)($p['ext'] ?? 'none'), 'wall' => $wall, 'trees' => (string)($p['trees'] ?? 'none'), ]; if ($ans['length'] <= 0 || $ans['width'] <= 0) throw new \InvalidArgumentException('Enter the plot length and width'); // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- message is never printed. $res = (new \VastuIQWP\Engine\PropertyScore())->plot($ans); return vastuiq_result('plot', [ 'score' => $res['score']['overall'], 'uncapped' => $res['score']['uncapped'], 'capped' => $res['score']['capped'], 'cap' => $res['score']['cap'], 'band_id' => $res['score']['band_id'], 'centre_penalty' => 0, 'entrance' => null, 'rooms' => [], 'factors' => $res['factors'], 'majors' => array_map(fn($m) => ['type' => $m['factor'], 'dir' => '', 'text' => $m['text']], $res['major_defects']), 'unmatched' => [], 'notes' => $notes, 'method' => $res['method'], 'engine_version' => $res['engine_version'], 'rules_version' => $res['rules_version'], ]); } function vastuiq_result(string $type, array $r): array { $labels = ['strong' => 'Strong', 'good' => 'Good', 'attention' => 'Needs attention', 'priority' => 'Priority']; $r['band'] = $labels[$r['band_id']] ?? $r['band_id']; return ['ok' => true, 'type' => $type, 'scoring_version' => VASTUIQ_SCORING_VERSION] + $r; }