Pre-Expand
Raspberry Pi Integration
The pre-expansion data is automatically imported from the pre-expander machine via a Raspberry Pi device running a separate application.
Integration Repository: shelter-preexp-app
How It Works:
- Raspberry Pi reads data files from the pre-expander machine
- Pi parses expansion data (silo start/end times, lot names, bag numbers)
- Pi sends HTTP POST requests to the Shelter Enterprises API
- API creates/updates pre-expansion jobs and marks inventory as used
- Dashboard reflects changes in real-time (30-second refresh)
Key String Generation:
Each pre-expansion operation generates a unique key_string based on:
- Silo number
- Start timestamp
- Lot names involved
This prevents duplicate job creation when the Pi sends multiple updates for the same expansion.
Back-End Logic
Controller: Preexpand::view()
File Location: src/app/Controllers/Preexpand.php:26
Route: /preexpand/view
The view() method handles rendering the pre-expansion monitoring page and enforces user group permissions:
public function view(): string|RedirectResponse
{
if (auth()->user()->inGroup('super_admin', 'admin', 'preexpand'))
{
$title['title'] = "Pre-Expand Lots";
$data['lot_types'] = Lot_type_model::all();
$data['available_lots'] = Lot_model::selectRaw('lot_name')
->whereNull('start_date_used')
->whereNull('end_date_used')
->groupBy('lot_name')
->get();
return view('templates/header', $title) .
view('preexpansion/preexpand_in_progress', $data) .
view('templates/footer');
} else {
$user = auth()->user();
log_message('error', $user->first_name . " " . $user->last_name .
" tried to access the pre-expansion page without proper permissions");
return redirect()->back()->with('error', 'You must be an admin to view this page');
}
}
API Endpoint: Preexpand::get_preexpansion_data()
File Location: src/app/Controllers/Api/Preexpand.php:137
This endpoint provides paginated pre-expansion job data for the Tabulator table:
public function get_preexpansion_data(): ResponseInterface
{
$this->validation->setRules([
'page' => 'required|numeric|greater_than[0]',
'size' => 'required|numeric|greater_than[0]',
]);
if ($this->validation->withRequest($this->request)->run()) {
$valid_data = $this->validation->getValidated();
$data = [];
// Calculate offset for pagination
$offset = $valid_data['size'] * ($valid_data['page'] - 1);
// Calculate total pages
$data['last_page'] = ceil(Preexp_job_model::count() / $valid_data['size']);
// Retrieve jobs with pagination
$rows = Preexp_job_model::orderByDesc('created_at')
->offset($offset)
->limit($valid_data['size'])
->get();
// Build response data for each job
// ... (Constructs HTML for bags and populates row data)
return $this->respond($data);
}
}
Helper Function: find_or_start_job()
File Location: src/app/Controllers/Api/Preexpand.php:57
This private helper function manages pre-expansion job creation and updates.
private function find_or_start_job(array $expansion, int $silo_number): Preexp_job_model
{
// Generate unique key for this expansion operation
$key_string = get_preexpand_job_key($expansion, $silo_number);
$existing_job = Preexp_job_model::where('key_string', $key_string);
if ($existing_job->exists()) {
$job = $existing_job->first();
} else {
$job = new Preexp_job_model();
log_message('debug', 'Created Pre-Expansion job with key: ' . $key_string);
}
$job->key_string = $key_string;
$job->silo_num = $silo_number;
$job->start_time = Date('Y-m-d H:i:s', strtotime($expansion['silo_start']));
// Handle "No Full" condition (silo didn't reach capacity)
if (strtolower($expansion['silo_end']) !== "no full" && $expansion['silo_end'] !== "") {
$job->end_time = Date('Y-m-d H:i:s', strtotime($expansion['silo_end']));
}
return $job;
}
Helper Function: build_available_lot_array()
File Location: src/app/Controllers/Api/Preexpand.php:98
Builds a map of available inventory for lot matching by grouping lots that have not been marked as used.
Front-End Javascript Configuration
Tabulator Configuration
File Location: src/public/js/preexpansion/bead_in_progress.js
The client-side logic handles table initialization, server-side pagination, and row click handling:
let lot_table = new Tabulator("#lot_table", {
placeholder: "No lots have been created",
pagination: true,
paginationMode: "remote",
paginationSize: 25,
maxHeight: '85vh',
ajaxURL: "/api/preexpand/get_preexpansion_data",
ajaxConfig: "POST",
initialSort: [{column: "start_date_used", dir: "desc"}],
paginationSizeSelector: [25, 50, 100],
columns: [
{title: "Lot ID", field: "id", visible: false},
{field: 'disconnected', width: 50, formatter: function(cell) {
return cell.getValue() ? '<i class="fa fa-circle text-warning"></i>' : '';
}},
{title: "Bags Used", field: "bags_used", widthGrow: 3, formatter: 'html'},
{title: "Started Expansion", field: "start_date_used", widthGrow: 2,
formatter: "datetime", formatterParams: {
inputFormat: "yyyy-LL-dd HH:mm:ss",
outputFormat: "L/d/yy h:mm a"
}},
{title: "Finished Expansion", field: "end_date_used", widthGrow: 2,
formatter: "datetime"},
{title: "Silo #", field: "silo_num", widthGrow: 1},
{title: "Lot Type", field: "lot_type", widthGrow: 1}
],
rowClick: function(e, row) {
preexp_job_id = row.getData().id;
$('#add_bags_modal').modal('toggle');
populateModal(row);
}
});
// Auto-refresh every 30 seconds
setInterval(function() {
lot_table.replaceData();
}, 30000);
Adding New Columns
To add a new column to the pre-expansion table:
- Update API Response (
src/app/Controllers/Api/Preexpand.php:137): Add the new data property. - Update Tabulator Config (
src/public/js/preexpansion/bead_in_progress.js): Add the column object definition.
Database Schema
Database Model: Preexp_job_model
File Location: src/app/Models/Preexp_job_model.php
The model represents pre-expansion jobs with the following key fields:
key_string- Unique identifier for this expansion operationsilo_num- Silo number (1-N)start_time- When expansion startedend_time- When expansion completed (nullable for incomplete jobs)bag_id_1,bag_id_2,bag_id_3- Foreign keys to lot records consumed
Pagination Performance
The table utilizes server-side data loading (SQL LIMIT/OFFSET queries) to:
- Prevent loading thousands of job records at once
- Reduce initial page load time and minimize memory usage
API Endpoints & Lot Matching
Lot Controller: Api/Lot.php
get_missing_lots(): Identifies and suggests corrections for uncategorized lots by returning missing lots and potential matches.get_likeness(): Calculates the text similarity percentage (similar_text()) between two lot names.correct_by_name(): Corrects a misidentified lot by matching it with the correct lot name and transferring the data.