diff --git a/application/controllers/Awards.php b/application/controllers/Awards.php index 59ff6dd8e..3e1bdf8f6 100644 --- a/application/controllers/Awards.php +++ b/application/controllers/Awards.php @@ -2374,4 +2374,133 @@ class Awards extends CI_Controller { $this->load->view('awards/wpx/wpx_details', $data); } + /* + Handles displaying the Polska Award (Polish Award) + Tracks contacts with Polish voivodeships (provinces) for the Poland award program + Uses COL_STATE field for voivodeship codes (16 Polish voivodeships) + */ + public function pl_polska() { + $footerData = []; + $footerData['scripts'] = [ + 'assets/js/sections/award_pl_polska.js?' . filemtime(realpath(__DIR__ . "/../../assets/js/sections/award_pl_polska.js")), + 'assets/js/leaflet/L.Maidenhead.js', + ]; + + $this->load->model('logbooks_model'); + $this->load->model('stations'); + + // Get station profiles for multiselect + $data['station_profile'] = $this->stations->all_of_user(); + $data['active_station_id'] = $this->session->userdata('active_station_logbook'); + + $this->load->model('award_pl_polska'); + $this->load->model('bands'); + + // Define valid bands for Polska award (per PZK rules) + // https://awards.pzk.org.pl/polish-awards/polska.html + // SAT is explicitly excluded (no satellite/repeater contacts allowed) + $data['worked_bands'] = array('160M', '80M', '40M', '30M', '20M', '17M', '15M', '12M', '10M', '6M', '2M'); + + if($this->input->method() === 'post') { + $postdata['qsl'] = $this->security->xss_clean($this->input->post('qsl')); + $postdata['lotw'] = $this->security->xss_clean($this->input->post('lotw')); + $postdata['eqsl'] = $this->security->xss_clean($this->input->post('eqsl')); + $postdata['qrz'] = $this->security->xss_clean($this->input->post('qrz')); + $postdata['clublog'] = $this->security->xss_clean($this->input->post('clublog')); + + // Always use active logbook (no multiselect) + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + } else { + $postdata['qsl'] = 1; + $postdata['lotw'] = 1; + $postdata['eqsl'] = 0; + $postdata['qrz'] = 0; + $postdata['clublog'] = 0; + + // Default to active logbook + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + } + + // Add confirmed key for gen_qsl_from_postdata function compatibility + $postdata['confirmed'] = 1; + + if ($logbooks_locations_array) { + $location_list = "'".implode("','",$logbooks_locations_array)."'"; + + // Simplified data - just confirmed counts + $data['polska_array'] = $this->award_pl_polska->get_polska_simple_by_modes($postdata, $location_list); + $data['polska_totals'] = $this->award_pl_polska->get_polska_totals_by_modes($postdata, $location_list); + + // Band-based data (simplified) + $data['polska_array_bands'] = $this->award_pl_polska->get_polska_simple_by_bands($data['worked_bands'], $postdata, $location_list); + $data['polska_totals_bands'] = $this->award_pl_polska->get_polska_totals_by_bands($data['worked_bands'], $postdata, $location_list); + + // Calculate award classes for each mode category + $data['polska_classes'] = array(); + $mode_categories = array('MIXED', 'PHONE', 'CW', 'DIGI'); + foreach ($mode_categories as $category) { + $postdata_temp = $postdata; + $postdata_temp['mode'] = $category; + $postdata_temp['band'] = 'All'; + $data['polska_classes'][$category] = $this->award_pl_polska->getPolskaClassByCategory($location_list, $category, $postdata_temp, true); + } + + // Calculate award classes for each band + $data['polska_classes_bands'] = array(); + $valid_bands = array('160M', '80M', '40M', '30M', '20M', '17M', '15M', '12M', '10M', '6M', '2M'); + foreach ($valid_bands as $band) { + $postdata_temp = $postdata; + $postdata_temp['band'] = $band; + $postdata_temp['mode'] = 'All'; + $data['polska_classes_bands'][$band] = $this->award_pl_polska->getPolskaClassByBand($location_list, $band, $postdata_temp, true); + } + } else { + $location_list = null; + $data['polska_array'] = null; + $data['polska_totals'] = null; + $data['polska_array_bands'] = null; + $data['polska_totals_bands'] = null; + $data['polska_classes'] = null; + $data['polska_classes_bands'] = null; + } + + // Render page + $data['page_title'] = sprintf(__("Awards - %s"), __('"Polska" Award')); + $data['user_map_custom'] = $this->optionslib->get_map_custom(); + $this->load->view('interface_assets/header', $data); + $this->load->view('awards/pl_polska/index'); + $this->load->view('interface_assets/footer', $footerData); + } + + /* + function polska_map + Returns JSON data for Polska Award map visualization + */ + public function polska_map() { + $this->load->model('award_pl_polska'); + + // Get category (MIXED, PHONE, CW, DIGI, or band like 20M) + $category = $this->security->xss_clean($this->input->post('category')); + if (!$category) { + $category = 'MIXED'; + } + + $postdata['qsl'] = $this->input->post('qsl') == 0 ? NULL: 1; + $postdata['lotw'] = $this->input->post('lotw') == 0 ? NULL: 1; + $postdata['eqsl'] = $this->input->post('eqsl') == 0 ? NULL: 1; + $postdata['qrz'] = $this->input->post('qrz') == 0 ? NULL: 1; + $postdata['clublog'] = $this->input->post('clublog') == 0 ? NULL: 1; + + // Get location list for active station + $this->load->model('logbooks_model'); + $logbooks_locations_array = $this->logbooks_model->list_logbook_relationships($this->session->userdata('active_station_logbook')); + $location_list = "'" . implode("','", $logbooks_locations_array) . "'"; + + // Get map status directly from model + $voivodeships = $this->award_pl_polska->get_polska_map_status($category, $postdata, $location_list); + + header('Content-Type: application/json'); + echo json_encode($voivodeships); + } + } diff --git a/application/models/Award_pl_polska.php b/application/models/Award_pl_polska.php new file mode 100644 index 000000000..6c20fd68d --- /dev/null +++ b/application/models/Award_pl_polska.php @@ -0,0 +1,329 @@ + 'Dolnośląskie', + 'P' => 'Kujawsko-Pomorskie', + 'B' => 'Lubuskie', + 'L' => 'Lubelskie', + 'C' => 'Łódzkie', + 'M' => 'Małopolskie', + 'R' => 'Mazowieckie', + 'U' => 'Opolskie', + 'K' => 'Podkarpackie', + 'O' => 'Podlaskie', + 'F' => 'Pomorskie', + 'G' => 'Śląskie', + 'S' => 'Świętokrzyskie', + 'J' => 'Warmińsko-Mazurskie', + 'W' => 'Wielkopolskie', + 'Z' => 'Zachodniopomorskie' + ); + + function __construct() { + $this->load->library('Genfunctions'); + } + + /** + * Get voivodeship codes + */ + function getVoivodeshipCodes() { + return array_keys($this->voivodeship_names); + } + + /** + * Get voivodeship name from code + */ + function getVoivodeshipName($code) { + return isset($this->voivodeship_names[$code]) ? $this->voivodeship_names[$code] : $code; + } + + /** + * Build base SQL query + */ + private function buildBaseQuery($location_list, $withCount = false) { + $select = $withCount + ? "SELECT UPPER(COL_STATE) as COL_STATE, COUNT(*) as qso_count" + : "SELECT DISTINCT UPPER(COL_STATE) as COL_STATE"; + + $sql = $select . " + FROM " . $this->config->item('table_name') . " thcv + WHERE station_id IN (" . $location_list . ") + AND COL_DXCC = '" . $this->DXCC_POLAND . "' + AND COL_TIME_ON >= '" . $this->AWARD_START_DATE . "' + AND (COL_PROP_MODE != 'SAT' OR COL_PROP_MODE IS NULL) + AND COL_BAND IN ('" . implode("','", $this->VALID_BANDS) . "') + AND COL_STATE IS NOT NULL AND COL_STATE != '' + AND UPPER(COL_STATE) IN ('" . implode("','", $this->getVoivodeshipCodes()) . "')"; + + return $sql; + } + + /** + * Add band filter to query + */ + private function addBandFilter($sql, $band) { + if ($band != 'All') { + $sql .= " AND COL_BAND = '" . $band . "'"; + } + return $sql; + } + + /** + * Add mode category filter to query + */ + private function addModeCategoryFilter($sql, $mode_category) { + if ($mode_category == 'PHONE') { + $sql .= " AND (UPPER(COL_MODE) IN ('SSB','USB','LSB','AM','FM','SSTV') OR UPPER(COL_SUBMODE) IN ('SSB','USB','LSB','AM','FM','SSTV'))"; + } elseif ($mode_category == 'CW') { + $sql .= " AND (UPPER(COL_MODE) = 'CW' OR UPPER(COL_SUBMODE) = 'CW')"; + } elseif ($mode_category == 'DIGI') { + $digi_modes = "'RTTY','PSK','PSK31','PSK63','PSK125','PSKR','FSK','FSK441','FT4','FT8','JS8','JT4','JT6M','JT9','JT65','MFSK','OLIVIA','OPERA','PAX','PAX2','PKT','Q15','QRA64','ROS','T10','THOR','THRB','TOR','VARA','WSPR'"; + $sql .= " AND (UPPER(COL_MODE) IN (" . $digi_modes . ") OR UPPER(COL_SUBMODE) IN (" . $digi_modes . "))"; + } + return $sql; + } + + /** + * Finalize query with GROUP BY + */ + private function finalizeQuery($sql, $withCount = false) { + if ($withCount) { + $sql .= " GROUP BY UPPER(COL_STATE)"; + } + return $sql; + } + + /** + * Execute voivodeship query + */ + private function queryVoivodeships($location_list, $options = array()) { + $band = isset($options['band']) ? $options['band'] : 'All'; + $mode_category = isset($options['mode_category']) ? $options['mode_category'] : null; + $confirmed = isset($options['confirmed']) ? $options['confirmed'] : false; + $withCount = isset($options['withCount']) ? $options['withCount'] : false; + $postdata = isset($options['postdata']) ? $options['postdata'] : array(); + + $sql = $this->buildBaseQuery($location_list, $withCount); + $sql = $this->addBandFilter($sql, $band); + + if ($mode_category) { + $sql = $this->addModeCategoryFilter($sql, $mode_category); + } + + if ($confirmed && !empty($postdata)) { + $sql .= $this->genfunctions->addQslToQuery($postdata); + } + + $sql = $this->finalizeQuery($sql, $withCount); + + $query = $this->db->query($sql); + return $query->result(); + } + + /** + * Calculate award class based on minimum QSOs per voivodeship + */ + private function calculateAwardClass($counts) { + $voiv_codes = $this->getVoivodeshipCodes(); + $min_count = PHP_INT_MAX; + + foreach ($voiv_codes as $code) { + if (!isset($counts[$code]) || $counts[$code] == 0) { + return null; + } + $min_count = min($min_count, $counts[$code]); + } + + if ($min_count >= 12) return 'gold'; + if ($min_count >= 7) return 'silver'; + if ($min_count >= 3) return 'bronze'; + if ($min_count >= 1) return 'basic'; + + return null; + } + + /** + * Get confirmed QSO counts by mode categories + */ + function get_polska_simple_by_modes($postdata, $location_list) { + $result = array(); + + foreach ($this->voivodeship_names as $code => $name) { + $result[$code] = array_fill_keys($this->MODE_CATEGORIES, 0); + } + + foreach ($this->MODE_CATEGORIES as $category) { + $voivData = $this->queryVoivodeships($location_list, array( + 'mode_category' => $category, + 'confirmed' => true, + 'withCount' => true, + 'postdata' => $postdata + )); + + foreach ($voivData as $line) { + if (isset($result[$line->COL_STATE])) { + $result[$line->COL_STATE][$category] = (int)$line->qso_count; + } + } + } + + return $result; + } + + /** + * Get confirmed QSO counts by bands + */ + function get_polska_simple_by_bands($bands, $postdata, $location_list) { + $result = array(); + + foreach ($this->voivodeship_names as $code => $name) { + $result[$code] = array_fill_keys($bands, 0); + } + + foreach ($bands as $band) { + $voivData = $this->queryVoivodeships($location_list, array( + 'band' => $band, + 'confirmed' => true, + 'withCount' => true, + 'postdata' => $postdata + )); + + foreach ($voivData as $line) { + if (isset($result[$line->COL_STATE])) { + $result[$line->COL_STATE][$band] = (int)$line->qso_count; + } + } + } + + return $result; + } + + /** + * Get total confirmed voivodeship counts by mode categories + */ + function get_polska_totals_by_modes($postdata, $location_list) { + $totals = array(); + + foreach ($this->MODE_CATEGORIES as $category) { + $voivData = $this->queryVoivodeships($location_list, array( + 'mode_category' => $category, + 'confirmed' => true, + 'postdata' => $postdata + )); + $totals[$category] = count($voivData); + } + + return $totals; + } + + /** + * Get total confirmed voivodeship counts by bands + */ + function get_polska_totals_by_bands($bands, $postdata, $location_list) { + $totals = array(); + + foreach ($bands as $band) { + $voivData = $this->queryVoivodeships($location_list, array( + 'band' => $band, + 'confirmed' => true, + 'postdata' => $postdata + )); + $totals[$band] = count($voivData); + } + + return $totals; + } + + /** + * Get award class by mode category + */ + function getPolskaClassByCategory($location_list, $mode_category, $postdata, $confirmed = false) { + $voivData = $this->queryVoivodeships($location_list, array( + 'mode_category' => $mode_category, + 'confirmed' => $confirmed, + 'withCount' => true, + 'postdata' => $postdata + )); + + $counts = array(); + foreach ($voivData as $row) { + $counts[$row->COL_STATE] = $row->qso_count; + } + + return $this->calculateAwardClass($counts); + } + + /** + * Get award class by band + */ + function getPolskaClassByBand($location_list, $band, $postdata, $confirmed = false) { + $voivData = $this->queryVoivodeships($location_list, array( + 'band' => $band, + 'confirmed' => $confirmed, + 'withCount' => true, + 'postdata' => $postdata + )); + + $counts = array(); + foreach ($voivData as $row) { + $counts[$row->COL_STATE] = $row->qso_count; + } + + return $this->calculateAwardClass($counts); + } + + /** + * Get map status (W=worked, C=confirmed, -=not worked) + */ + function get_polska_map_status($category, $postdata, $location_list) { + $result = array(); + + foreach ($this->voivodeship_names as $code => $name) { + $result[$code] = '-'; + } + + $options = array('withCount' => true); + + if (in_array($category, $this->MODE_CATEGORIES)) { + $options['mode_category'] = $category; + } elseif (in_array($category, $this->VALID_BANDS)) { + $options['band'] = $category; + } + + // Get worked voivodeships + $workedData = $this->queryVoivodeships($location_list, $options); + $workedVoivs = array(); + foreach ($workedData as $line) { + $workedVoivs[$line->COL_STATE] = true; + } + + // Get confirmed voivodeships + $options['confirmed'] = true; + $options['postdata'] = $postdata; + $confirmedData = $this->queryVoivodeships($location_list, $options); + $confirmedVoivs = array(); + foreach ($confirmedData as $line) { + $confirmedVoivs[$line->COL_STATE] = true; + } + + // Build result + foreach ($this->voivodeship_names as $code => $name) { + if (isset($confirmedVoivs[$code])) { + $result[$code] = 'C'; + } elseif (isset($workedVoivs[$code])) { + $result[$code] = 'W'; + } + } + + return $result; + } +} diff --git a/application/models/Logbook_model.php b/application/models/Logbook_model.php index 622a0c192..5d3409ece 100644 --- a/application/models/Logbook_model.php +++ b/application/models/Logbook_model.php @@ -648,6 +648,40 @@ class Logbook_model extends CI_Model { $this->db->where('COL_STATE', $searchphrase); $this->db->where_in('COL_DXCC', ['287']); break; + case 'POLSKA': + $this->db->where('COL_STATE', $searchphrase); + $this->db->where('COL_DXCC', '269'); + $this->db->where('COL_TIME_ON >=', '1999-01-01 00:00:00'); + + // Exclude satellite contacts for Polska Award + $this->db->group_start(); + $this->db->where('COL_PROP_MODE !=', 'SAT'); + $this->db->or_where('COL_PROP_MODE IS NULL'); + $this->db->group_end(); + + // Only count allowed bands for Polska Award + $this->db->where_in('COL_BAND', ['160M','80M','40M','30M','20M','17M','15M','12M','10M','6M','2M']); + + // Handle mode categories for Polska Award + if (strtoupper($mode) == 'PHONE') { + $this->db->group_start(); + $this->db->where_in('UPPER(COL_MODE)', ['SSB','USB','LSB','AM','FM','SSTV']); + $this->db->or_where_in('UPPER(COL_SUBMODE)', ['SSB','USB','LSB','AM','FM','SSTV']); + $this->db->group_end(); + $mode = ''; // Clear mode so it's not processed again later + } elseif (strtoupper($mode) == 'DIGI') { + $this->db->group_start(); + $this->db->where_in('UPPER(COL_MODE)', ['RTTY','PSK','PSK31','PSK63','PSK125','PSKR','FSK','FSK441','FT4','FT8','JS8','JT4','JT6M','JT9','JT65','MFSK','OLIVIA','OPERA','PAX','PAX2','PKT','Q15','QRA64','ROS','T10','THOR','THRB','TOR','VARA','WSPR']); + $this->db->or_where_in('UPPER(COL_SUBMODE)', ['RTTY','PSK','PSK31','PSK63','PSK125','PSKR','FSK','FSK441','FT4','FT8','JS8','JT4','JT6M','JT9','JT65','MFSK','OLIVIA','OPERA','PAX','PAX2','PKT','Q15','QRA64','ROS','T10','THOR','THRB','TOR','VARA','WSPR']); + $this->db->group_end(); + $mode = ''; // Clear mode so it's not processed again later + } elseif (strtoupper($mode) == 'CW') { + $this->db->where('UPPER(COL_MODE)', 'CW'); + $mode = ''; // Clear mode so it's not processed again later + } elseif (strtoupper($mode) == 'MIXED') { + $mode = 'All'; // MIXED means all modes + } + break; case 'JCC': $this->db->where('COL_CNTY', $searchphrase); $this->db->where('COL_DXCC', '339'); diff --git a/application/views/awards/pl_polska/index.php b/application/views/awards/pl_polska/index.php new file mode 100644 index 000000000..f08fa3af0 --- /dev/null +++ b/application/views/awards/pl_polska/index.php @@ -0,0 +1,379 @@ + + + + +
' . __('Polska Award categories are based on the minimum number of confirmed QSOs with each of all 16 voivodeships:') . '
'; + echo '