Skip to main content

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:

  1. Raspberry Pi reads data files from the pre-expander machine
  2. Pi parses expansion data (silo start/end times, lot names, bag numbers)
  3. Pi sends HTTP POST requests to the Shelter Enterprises API
  4. API creates/updates pre-expansion jobs and marks inventory as used
  5. 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:

  1. Update API Response (src/app/Controllers/Api/Preexpand.php:137): Add the new data property.
  2. 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 operation
  • silo_num - Silo number (1-N)
  • start_time - When expansion started
  • end_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.