Add new gridsquares by band to monthly report

Introduces per-band breakdown of new gridsquares in the monthly report, including model, controller, and view changes. The report now displays new gridsquares grouped by band, as well as improved details for new DXCCs and satellite activity breakdowns.
This commit is contained in:
Peter Goodhall 2025-11-14 17:49:25 +00:00
parent 4e657094f8
commit 45528ee293
3 changed files with 287 additions and 94 deletions

View file

@ -121,6 +121,7 @@ class Monthlyreport extends CI_Controller {
'new_countries_satellite' => $report['new_dxcc_satellite'],
'new_countries_eme' => $report['new_dxcc_eme'],
'new_gridsquares' => $report['new_grids'],
'new_gridsquares_by_band' => $report['new_grids_by_band'],
'new_gridsquares_hf' => $report['new_grids_hf'],
'new_gridsquares_satellite' => $report['new_grids_satellite'],
'new_gridsquares_eme' => $report['new_grids_eme'],
@ -129,7 +130,8 @@ class Monthlyreport extends CI_Controller {
'bands_used' => $report['bands'],
'continents_worked' => $report['continents'],
'satellite_qsos' => $report['satellite_qsos'],
'eme_qsos' => $report['eme_qsos']
'eme_qsos' => $report['eme_qsos'],
'satellite_breakdown' => $report['satellite_breakdown']
),
'statistics' => array(
'qsos_per_day' => round($report['total_qsos'] / date('t', strtotime("$year-$month-01")), 2),
@ -202,7 +204,15 @@ class Monthlyreport extends CI_Controller {
foreach ($report['new_dxcc_by_band'] as $band => $dxcc_list) {
$text .= "\n{$band} (" . count($dxcc_list) . " new):\n";
foreach ($dxcc_list as $dxcc) {
$text .= " - {$dxcc['name']}\n";
$text .= " - {$dxcc['name']}";
if (!empty($dxcc['callsign'])) {
$text .= " (worked {$dxcc['callsign']}";
if (!empty($dxcc['mode'])) {
$text .= " on {$dxcc['mode']}";
}
$text .= ")";
}
$text .= "\n";
}
}
$text .= "\n";
@ -212,7 +222,15 @@ class Monthlyreport extends CI_Controller {
$text .= "NEW COUNTRIES VIA SATELLITE\n";
$text .= "---------------------------\n";
foreach ($report['new_dxcc_satellite'] as $dxcc) {
$text .= "- {$dxcc['name']}\n";
$text .= "- {$dxcc['name']}";
if (!empty($dxcc['callsign'])) {
$text .= " (worked {$dxcc['callsign']}";
if (!empty($dxcc['mode'])) {
$text .= " on {$dxcc['mode']}";
}
$text .= ")";
}
$text .= "\n";
}
$text .= "\n";
}
@ -221,7 +239,15 @@ class Monthlyreport extends CI_Controller {
$text .= "NEW COUNTRIES VIA EME (MOONBOUNCE)\n";
$text .= "----------------------------------\n";
foreach ($report['new_dxcc_eme'] as $dxcc) {
$text .= "- {$dxcc['name']}\n";
$text .= "- {$dxcc['name']}";
if (!empty($dxcc['callsign'])) {
$text .= " (worked {$dxcc['callsign']}";
if (!empty($dxcc['mode'])) {
$text .= " on {$dxcc['mode']}";
}
$text .= ")";
}
$text .= "\n";
}
$text .= "\n";
}
@ -230,37 +256,27 @@ class Monthlyreport extends CI_Controller {
$text .= "NEW GRIDSQUARES THIS MONTH\n";
$text .= "--------------------------\n";
if (count($report['new_grids_hf']) > 0) {
$text .= "HF/VHF Terrestrial (" . count($report['new_grids_hf']) . "):\n";
$grids_per_line = 10;
$grid_list = array_column($report['new_grids_hf'], 'grid');
for ($i = 0; $i < count($grid_list); $i += $grids_per_line) {
$text .= implode(', ', array_slice($grid_list, $i, $grids_per_line)) . "\n";
}
$text .= "\n";
}
if (count($report['new_grids_satellite']) > 0) {
$text .= "Satellite (" . count($report['new_grids_satellite']) . "):\n";
$grids_per_line = 10;
$grid_list = array_column($report['new_grids_satellite'], 'grid');
for ($i = 0; $i < count($grid_list); $i += $grids_per_line) {
$text .= implode(', ', array_slice($grid_list, $i, $grids_per_line)) . "\n";
}
$text .= "\n";
}
if (count($report['new_grids_eme']) > 0) {
$text .= "EME (Moonbounce) (" . count($report['new_grids_eme']) . "):\n";
$grids_per_line = 10;
$grid_list = array_column($report['new_grids_eme'], 'grid');
for ($i = 0; $i < count($grid_list); $i += $grids_per_line) {
$text .= implode(', ', array_slice($grid_list, $i, $grids_per_line)) . "\n";
if (!empty($report['new_grids_by_band'])) {
foreach ($report['new_grids_by_band'] as $band => $grids_list) {
$text .= "\n{$band} (" . count($grids_list) . " new):\n";
foreach ($grids_list as $grid) {
$text .= " {$grid['grid']}";
if (!empty($grid['callsign'])) {
$text .= " ({$grid['callsign']}";
if (!empty($grid['satellite'])) {
$text .= " via {$grid['satellite']}";
}
if (!empty($grid['mode'])) {
$text .= " - {$grid['mode']}";
}
$text .= ")";
}
$text .= "\n";
}
}
}
}
$text .= "MODES USED\n";
$text .= "\n";
} $text .= "MODES USED\n";
$text .= "----------\n";
foreach ($report['modes'] as $mode => $count) {
$text .= sprintf("%-10s %d QSOs\n", $mode, $count);
@ -295,7 +311,15 @@ class Monthlyreport extends CI_Controller {
if ($report['satellite_qsos'] > 0) {
$text .= "SATELLITE ACTIVITY\n";
$text .= "------------------\n";
$text .= "Total Satellite QSOs: {$report['satellite_qsos']}\n\n";
$text .= "Total Satellite QSOs: {$report['satellite_qsos']}\n";
if (!empty($report['satellite_breakdown'])) {
$text .= "\nQSOs per Satellite:\n";
foreach ($report['satellite_breakdown'] as $sat) {
$text .= sprintf(" %-20s %3d QSOs (%s%%)\n", $sat['satellite'], $sat['qso_count'], $sat['percentage']);
}
}
$text .= "\n";
}
if ($report['eme_qsos'] > 0) {

View file

@ -32,6 +32,7 @@ class Monthlyreport_model extends CI_Model {
'new_dxcc_satellite' => $this->get_new_dxcc_by_prop($logbooks_locations_array, $start_date, $end_date, $prev_month_end, 'SAT'),
'new_dxcc_eme' => $this->get_new_dxcc_by_prop($logbooks_locations_array, $start_date, $end_date, $prev_month_end, 'EME'),
'new_grids' => $this->get_new_gridsquares($logbooks_locations_array, $start_date, $end_date, $prev_month_end),
'new_grids_by_band' => $this->get_new_gridsquares_by_band($logbooks_locations_array, $start_date, $end_date, $prev_month_end),
'new_grids_satellite' => $this->get_new_gridsquares_by_prop($logbooks_locations_array, $start_date, $end_date, $prev_month_end, 'SAT'),
'new_grids_eme' => $this->get_new_gridsquares_by_prop($logbooks_locations_array, $start_date, $end_date, $prev_month_end, 'EME'),
'new_grids_hf' => $this->get_new_gridsquares_hf($logbooks_locations_array, $start_date, $end_date, $prev_month_end),
@ -432,6 +433,152 @@ class Monthlyreport_model extends CI_Model {
return $new_dxcc;
}
/**
* Get new gridsquares by band
*/
private function get_new_gridsquares_by_band($locations, $start_date, $end_date, $prev_month_end) {
$new_grids_by_band = array();
// Get all bands worked this month, plus propagation mode to separate SAT/EME
$this->db->distinct();
$this->db->select('COL_BAND, COL_PROP_MODE');
$this->db->from($this->config->item('table_name'));
$this->db->where_in('station_id', $locations);
$this->db->where('COL_TIME_ON >=', $start_date);
$this->db->where('COL_TIME_ON <=', $end_date);
$this->db->where('COL_BAND IS NOT NULL');
$this->db->where('COL_BAND !=', '');
$bands_query = $this->db->get();
foreach ($bands_query->result() as $band_row) {
$band = $band_row->COL_BAND;
$prop_mode = $band_row->COL_PROP_MODE;
// Group SAT and EME separately, not by frequency band
if ($prop_mode == 'SAT') {
$band_key = 'Satellite';
} elseif ($prop_mode == 'EME') {
$band_key = 'EME';
} else {
$band_key = $band;
}
if (!isset($new_grids_by_band[$band_key])) {
$new_grids_by_band[$band_key] = array();
}
// Get all gridsquares worked on this band/prop this month
$this->db->select('COL_CALL, COL_GRIDSQUARE, COL_VUCC_GRIDS, COL_TIME_ON, COL_MODE, COL_SUBMODE, COL_SAT_NAME');
$this->db->from($this->config->item('table_name'));
$this->db->where_in('station_id', $locations);
$this->db->where('COL_TIME_ON >=', $start_date);
$this->db->where('COL_TIME_ON <=', $end_date);
$this->db->where('COL_BAND', $band);
// Filter by propagation mode
if ($prop_mode) {
$this->db->where('COL_PROP_MODE', $prop_mode);
} else {
// For terrestrial, exclude SAT and EME
$this->db->where('(COL_PROP_MODE IS NULL OR (COL_PROP_MODE != "SAT" AND COL_PROP_MODE != "EME"))', NULL, FALSE);
}
$this->db->order_by('COL_TIME_ON', 'ASC');
$query = $this->db->get();
$grid_data = array();
foreach ($query->result() as $row) {
$mode = !empty($row->COL_SUBMODE) ? $row->COL_SUBMODE : $row->COL_MODE;
// Process main gridsquare (first 4 characters only)
if (!empty($row->COL_GRIDSQUARE) && strlen($row->COL_GRIDSQUARE) >= 4) {
$grid_4char = strtoupper(substr($row->COL_GRIDSQUARE, 0, 4));
if (!isset($grid_data[$grid_4char])) {
$grid_data[$grid_4char] = array(
'callsign' => $row->COL_CALL,
'satellite' => !empty($row->COL_SAT_NAME) ? $row->COL_SAT_NAME : '',
'mode' => $mode,
'time' => $row->COL_TIME_ON
);
}
}
// Process VUCC grids (first 4 characters only)
if (!empty($row->COL_VUCC_GRIDS)) {
$vucc_grids = explode(',', $row->COL_VUCC_GRIDS);
foreach ($vucc_grids as $grid) {
$grid = trim($grid);
if (strlen($grid) >= 4) {
$grid_4char = strtoupper(substr($grid, 0, 4));
if (!isset($grid_data[$grid_4char])) {
$grid_data[$grid_4char] = array(
'callsign' => $row->COL_CALL,
'satellite' => !empty($row->COL_SAT_NAME) ? $row->COL_SAT_NAME : '',
'mode' => $mode,
'time' => $row->COL_TIME_ON
);
}
}
}
}
}
// Check each grid to see if it was worked before on this band/prop
foreach ($grid_data as $grid => $data) {
// Check if this grid was worked before this month on this band/prop
$this->db->select('COUNT(*) as count');
$this->db->from($this->config->item('table_name'));
$this->db->where_in('station_id', $locations);
$this->db->where('COL_BAND', $band);
if ($prop_mode) {
$this->db->where('COL_PROP_MODE', $prop_mode);
} else {
$this->db->where('(COL_PROP_MODE IS NULL OR (COL_PROP_MODE != "SAT" AND COL_PROP_MODE != "EME"))', NULL, FALSE);
}
$this->db->where('COL_TIME_ON <', $start_date);
$this->db->group_start();
$this->db->where('UPPER(SUBSTR(COL_GRIDSQUARE, 1, 4)) =', $grid);
$this->db->or_like('COL_VUCC_GRIDS', $grid);
$this->db->group_end();
$prev_query = $this->db->get();
$prev_row = $prev_query->row();
// If not worked before on this band/prop, it's new
if ($prev_row->count == 0) {
// Avoid duplicates in the same band_key
$already_added = false;
foreach ($new_grids_by_band[$band_key] as $existing) {
if ($existing['grid'] == $grid) {
$already_added = true;
break;
}
}
if (!$already_added) {
$new_grids_by_band[$band_key][] = array(
'grid' => $grid,
'callsign' => $data['callsign'],
'satellite' => $data['satellite'],
'mode' => $data['mode']
);
}
}
}
}
// Remove bands with no new grids
$new_grids_by_band = array_filter($new_grids_by_band, function($arr) {
return !empty($arr);
});
// Sort bands in proper amateur radio order
$new_grids_by_band = $this->sort_bands_array($new_grids_by_band);
return $new_grids_by_band;
}
/**
* Get new gridsquares worked this month (first time ever) - All modes
*/

View file

@ -213,77 +213,99 @@
<?php } // End of new countries section ?>
<!-- New Gridsquares -->
<?php if (count($report['new_grids']) > 0) { ?>
<!-- New Gridsquares by Band -->
<?php
// Filter out Satellite and EME from band list to see if there's anything to display
$terrestrial_grids = array();
if (!empty($report['new_grids_by_band'])) {
foreach ($report['new_grids_by_band'] as $band => $grids_list) {
if ($band != 'Satellite' && $band != 'EME') {
$terrestrial_grids[$band] = $grids_list;
}
}
}
if (!empty($terrestrial_grids)) {
?>
<div class="card mb-4 border-info">
<div class="card-header bg-info bg-opacity-10 border-info">
<h5 class="mb-0"><i class="fas fa-th text-info"></i> New Gridsquares This Month</h5>
<h5 class="mb-0"><i class="fas fa-th text-info"></i> New Gridsquares by Band</h5>
</div>
<div class="card-body">
<?php if (count($report['new_grids_hf']) > 0) { ?>
<div class="mb-4">
<h6 class="text-muted"><i class="fas fa-tower-broadcast"></i> HF/VHF Terrestrial (<?php echo count($report['new_grids_hf']); ?>)</h6>
<div class="row">
<?php foreach ($report['new_grids_hf'] as $grid) { ?>
<div class="col-md-4 col-lg-3 mb-2">
<div class="d-flex align-items-center">
<span class="badge bg-primary me-2"><?php echo $grid['grid']; ?></span>
<small class="text-muted">
<?php echo $grid['callsign']; ?>
<?php if (!empty($grid['mode'])) echo ' • ' . $grid['mode']; ?>
</small>
<?php
// Bands already sorted in model
foreach ($terrestrial_grids as $band => $grids_list) {
$icon = '<i class="fas fa-signal"></i>';
?>
<div class="mb-3">
<h6 class="text-muted"><?php echo $icon; ?> <?php echo $band; ?> (<?php echo count($grids_list); ?> new)</h6>
<div class="row">
<?php foreach ($grids_list as $grid) { ?>
<div class="col-md-4 col-lg-3 mb-2">
<div class="d-flex align-items-center">
<span class="badge bg-primary me-2"><?php echo $grid['grid']; ?></span>
<small class="text-muted">
<?php echo $grid['callsign']; ?>
<?php if (!empty($grid['mode'])) echo ' • ' . $grid['mode']; ?>
</small>
</div>
</div>
</div>
<?php } ?>
<?php } ?>
</div>
</div>
</div>
<?php } ?>
<?php if (count($report['new_grids_satellite']) > 0) { ?>
<div class="mb-4">
<h6 class="text-muted"><i class="fas fa-satellite-dish"></i> Satellite (<?php echo count($report['new_grids_satellite']); ?>)</h6>
<div class="row">
<?php foreach ($report['new_grids_satellite'] as $grid) { ?>
<div class="col-md-6 col-lg-4 mb-2">
<div class="d-flex align-items-center">
<span class="badge bg-success me-2"><?php echo $grid['grid']; ?></span>
<small class="text-muted">
<?php echo $grid['callsign']; ?>
<?php if (!empty($grid['satellite'])) echo ' • ' . $grid['satellite']; ?>
<?php if (!empty($grid['mode'])) echo ' • ' . $grid['mode']; ?>
</small>
</div>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
<?php if (count($report['new_grids_eme']) > 0) { ?>
<div class="mb-3">
<h6 class="text-muted"><i class="fas fa-moon"></i> EME (Moonbounce) (<?php echo count($report['new_grids_eme']); ?>)</h6>
<div class="row">
<?php foreach ($report['new_grids_eme'] as $grid) { ?>
<div class="col-md-4 col-lg-3 mb-2">
<div class="d-flex align-items-center">
<span class="badge bg-warning text-dark me-2"><?php echo $grid['grid']; ?></span>
<small class="text-muted">
<?php echo $grid['callsign']; ?>
<?php if (!empty($grid['mode'])) echo ' • ' . $grid['mode']; ?>
</small>
</div>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
</div>
</div>
<?php } ?>
<!-- Modes and Bands -->
<!-- New Gridsquares via Satellite -->
<?php if (count($report['new_grids_satellite']) > 0) { ?>
<div class="card mb-4 border-info">
<div class="card-header bg-info bg-opacity-10 border-info">
<h5 class="mb-0"><i class="fas fa-satellite-dish text-info"></i> New Gridsquares via Satellite (<?php echo count($report['new_grids_satellite']); ?>)</h5>
</div>
<div class="card-body">
<div class="row">
<?php foreach ($report['new_grids_satellite'] as $grid) { ?>
<div class="col-md-6 col-lg-4 mb-2">
<div class="d-flex align-items-center">
<span class="badge bg-success me-2"><?php echo $grid['grid']; ?></span>
<small class="text-muted">
<?php echo $grid['callsign']; ?>
<?php if (!empty($grid['satellite'])) echo ' • ' . $grid['satellite']; ?>
<?php if (!empty($grid['mode'])) echo ' • ' . $grid['mode']; ?>
</small>
</div>
</div>
<?php } ?>
</div>
</div>
</div>
<?php } ?>
<!-- New Gridsquares via EME -->
<?php if (count($report['new_grids_eme']) > 0) { ?>
<div class="card mb-4 border-info">
<div class="card-header bg-info bg-opacity-10 border-info">
<h5 class="mb-0"><i class="fas fa-moon text-info"></i> New Gridsquares via EME (Moonbounce) (<?php echo count($report['new_grids_eme']); ?>)</h5>
</div>
<div class="card-body">
<div class="row">
<?php foreach ($report['new_grids_eme'] as $grid) { ?>
<div class="col-md-4 col-lg-3 mb-2">
<div class="d-flex align-items-center">
<span class="badge bg-warning text-dark me-2"><?php echo $grid['grid']; ?></span>
<small class="text-muted">
<?php echo $grid['callsign']; ?>
<?php if (!empty($grid['mode'])) echo ' • ' . $grid['mode']; ?>
</small>
</div>
</div>
<?php } ?>
</div>
</div>
</div>
<?php } ?> <!-- Modes and Bands -->
<div class="row mb-4">
<div class="col-md-6 mb-3">
<div class="card h-100">