Skip to main content

Pre-Expand

The Pre-Expansion function tracks expandable polystyrene (EPS) beads as they are processed through the pre-expander machine and moved into inventory. This page serves as a monitoring dashboard displaying all pre-expansion jobs that are currently in progress or have been completed.

Pre-expansion is a critical step in the EPS manufacturing process where raw beads are heated and expanded to the desired density before molding. The system automatically records this data via integration with the pre-expander machine through a Raspberry Pi device that sends updates in real-time.

Key Features:

  • Automated Data Import - Pre-expansion data is automatically imported from the machine via API
  • Real-Time Monitoring - Table refreshes every 30 seconds to show current operations
  • Inventory Tracking - Links pre-expansion jobs to specific lot inventory consumed
  • Job Details - View silo number, lot types, bags used, start/end times
  • Lot Mismatch Resolution - Handle discrepancies between machine data and inventory records

Access Requirements: Users must belong to one of the following groups:

  • super_admin
  • admin
  • preexpand

![INSERT SCREENSHOT HERE: Full view of the Pre-Expansion In Progress page showing the paginated table with multiple pre-expansion jobs listing bags used, start/end times, silo numbers, and lot types.]


Front-End Behavior

Page Layout

The Pre-Expansion page uses a simple, data-focused interface:

Header Section:

  • Page title: "Pre-Expansion In Progress"
  • Go Back button (top-right) - Returns user to /user/view
  • Loading spinner indicator (hidden by default, shows during data refresh)
  • "Changes Saved" checkmark icon (appears when modifications are saved)

Data Table: The main content is a Tabulator-based data table displaying pre-expansion jobs with the following columns:

ColumnDescriptionFormatWidth
Lot IDUnique job identifierHiddenN/A
Status IconWarning icon if disconnectedYellow circle icon50px
Bags UsedVisual display of lot names and bag numbersHTML formatted chips3x
Started ExpansionWhen pre-expansion beganM/D/YY h:mm AM/PM2x
Finished ExpansionWhen pre-expansion completedM/D/YY h:mm AM/PM2x
Silo #Silo number used for expansionInteger1x
Lot TypeType/density of beads expandedText1x

Table Features:

  • Pagination: Server-side pagination with configurable page sizes (25, 50, or 100 rows)
  • Sorting: Default sort by "Started Expansion" descending (most recent first)
  • Auto-refresh: Table data reloads every 30 seconds automatically
  • Responsive Layout: Columns auto-fit to available width
  • Max Height: Table scrolls vertically at 85vh to prevent page overflow
  • Row Click: Clicking any row opens a detailed modal view

![INSERT SCREENSHOT HERE: Close-up of the table showing the "Bags Used" column with multiple lot name/bag number chips displayed as small bordered boxes.]

Bags Used Display

The "Bags Used" column displays consumed inventory as HTML-formatted chips:

  • Each chip shows: [Lot Name] | [Bag Number]
  • Chips have black borders with rounded corners (5px)
  • Maximum width prevents text overflow with ellipsis
  • Tooltip shows full lot name on hover
  • Multiple chips displayed side-by-side in a responsive row

Example Display:

[Lot123 | 1] [Lot123 | 2] [Lot456 | 1]

Clicking on any table row opens a modal dialog with detailed information:

Modal Structure:

  • Title: "Modify Preexpansion Job"
  • Left Column (66% width):
    • Job summary table showing:
      • Started (timestamp)
      • Finished (timestamp)
      • Silo # (integer)
      • Lot Type (density label)
    • Inventory Used section with two sub-columns:
      • "Lots Entered" - Shows up to 3 lot names from the pre-expansion file
      • "Inventory Used" - Shows corresponding inventory records from database
  • Right Column (33% width):
    • Additional inventory details (dynamically populated HTML)
  • Footer:
    • Close button

The modal provides a detailed view for verification and troubleshooting, allowing users to see exactly which inventory was consumed and how it matches the machine's recorded data.

![INSERT SCREENSHOT HERE: Modal dialog showing pre-expansion job details with the job summary table at top and the inventory used section below showing lot names entered and matching inventory records.]

Disconnected Status Indicator

Jobs with a disconnected status display a warning icon (yellow circle) in the leftmost column. This indicates:

  • The machine may have experienced connectivity issues during the job
  • Data may be incomplete or require verification
  • Manual review recommended

Responsive Design

  • Desktop: Full table with all columns visible, comfortable spacing
  • Tablet: Columns maintain proportions, some text truncation may occur
  • Mobile: Table remains scrollable horizontally, maintains data integrity

Loading States

Loading Spinner:

  • Appears when saving data modifications
  • Bootstrap spinner-border component in primary blue color
  • Hidden by default, shown during AJAX operations

Saved Confirmation:

  • Green checkmark icon with "Changes Saved" text
  • Appears briefly after successful save operations
  • Auto-hides when new data loads

Back-End Logic

Controller: Preexpand::view()

File Location: src/app/Controllers/Preexpand.php:26

The view() method handles rendering the pre-expansion monitoring page:

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');
}
}

Key Logic:

  1. Checks user has appropriate group membership (super_admin, admin, or preexpand)
  2. Loads all lot types for reference
  3. Retrieves available lots (not yet used) grouped by lot name
  4. Renders page with header, main content, and footer
  5. Logs unauthorized access attempts and redirects with error message

Route: /preexpand/view

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
foreach ($rows as $job) {
// Collect bags associated with this job
$bags = [];
if ($job->bag_id_1 !== null) {
$bags[] = Lot_model::find($job->bag_id_1);
}
if ($job->bag_id_2 !== null) {
$bags[] = Lot_model::find($job->bag_id_2);
}
if ($job->bag_id_3 !== null) {
$bags[] = Lot_model::find($job->bag_id_3);
}

// Build HTML for bags display
$html = '<div class="row col-12 p-0">';
foreach ($bags as $bag) {
$html .= '<div style="border: 1px solid black; border-radius: 5px;
max-width: 12em; width: 30%;" class="row m-2 p-0">' .
'<div style="width: 70%;" class="text-left m-0 p-1"
title="' . $bag->lot_name . '">' . $bag->lot_name . '</div>' .
'<div style="border-left: 1px solid black; width: 30%;"
class="text-center m-0 p-1">' . $bag->bag_num . '</div></div>';
}
$html .= '</div>';

// Populate row data
$row['id'] = $job->id;
$row['bags_used'] = $html;
$row['silo_num'] = $job->silo_num;
$row['start_date_used'] = $job->start_time;
$row['end_date_used'] = $job->end_time;
$row['lot_type'] = $bags[0]->lot_type->label ?? '';

$data['data'][] = $row;
}

return $this->respond($data);
} else {
log_message("error", "Validation errors: " .
json_encode($this->validation->getErrors(), JSON_PRETTY_PRINT));
return $this->failValidationErrors($this->validation->getErrors());
}
}

Request Parameters:

  • page (required, numeric, > 0) - Current page number
  • size (required, numeric, > 0) - Number of rows per page

Response Structure:

{
"last_page": 10,
"data": [
{
"id": 123,
"bags_used": "<div>...</div>",
"start_date_used": "2025-10-25 08:30:00",
"end_date_used": "2025-10-25 09:15:00",
"silo_num": 1,
"lot_type": "1.0 PCF White"
}
]
}

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;
}

Purpose:

  • Prevents duplicate job creation through key_string lookup
  • Handles edge cases like incomplete silo fills
  • Maintains data integrity across machine updates

Helper Function: build_available_lot_array()

File Location: src/app/Controllers/Api/Preexpand.php:98

Builds a map of available inventory for lot matching:

private function build_available_lot_array(): array
{
$map = [];

$query = Lot_model::with('lot_type')
->whereNull('start_date_used')
->whereNull('end_date_used')
->whereNull('bag_num');

foreach ($query->get() as $lot) {
if (array_key_exists($lot->lot_name, $map)) {
$map[$lot->lot_name]['count']++;
} else {
$map[$lot->lot_name]['name'] = $lot->lot_name;
$map[$lot->lot_name]['type'] = Lot_type_model::find($lot->lot_type_id)->label;
$map[$lot->lot_name]['count'] = 1;
}
}

return $map;
}

Returns: Array indexed by lot name containing type and count information

View File

File Location: src/app/Views/preexpansion/preexpand_in_progress.php

The view file sets up the page structure and includes:

  • Card container for table
  • Empty div (#lot_table) where Tabulator initializes
  • Loading spinner and saved confirmation icons
  • Modal dialog structure for job details
  • JavaScript include for bead_in_progress.js

JavaScript: Tabulator Configuration

File Location: src/public/js/preexpansion/bead_in_progress.js

The client-side logic handles table initialization and interaction:

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);

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
  • created_at, updated_at - Timestamps

Relationships:

  • Belongs to multiple Lot records via bag_id columns

Authentication & Authorization

Required Permissions: Users must belong to one of these groups:

  • super_admin - Full system access
  • admin - Administrative access
  • preexpand - Specific access to pre-expansion monitoring

Permission Check: src/app/Controllers/Preexpand.php:28

if (auth()->user()->inGroup('super_admin', 'admin', 'preexpand')) {
// Allow access
}

Database Tables Affected

Primary Tables:

  • preexp_job - Pre-expansion job records
  • lot - Inventory lot records marked as used
  • lot_type - Lot type/density reference data

Key Operations:

  • Read pre-expansion jobs with pagination
  • Read lot inventory records for display
  • Update lot weights when corrections are made

Developer Notes

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.

Lot Mismatch Resolution

When the pre-expander machine records a lot name that doesn't exist in inventory:

Automated Handling:

  1. System detects mismatch during data import
  2. Sends email notification to administrators with details
  3. Email contains link to resolution page
  4. Resolution page offers two options:
    • Create New Lot - Adds missing lot to inventory and marks as used
    • Match to Existing - Maps machine lot name to closest matching inventory lot

Closest Match Algorithm:

  • System suggests lot names with highest similarity score
  • Uses string comparison algorithms (Levenshtein distance)
  • Presents top 3-5 matches for manual selection

Why Mismatches Occur:

  • Manual data entry errors when adding inventory
  • Typos in pre-expander input
  • Different naming conventions between systems
  • Missing inventory that should have been added

Handling "No Full" Silos

Sometimes a silo doesn't reach full capacity during pre-expansion:

if (strtolower($expansion['silo_end']) !== "no full" && $expansion['silo_end'] !== "") {
$job->end_time = Date('Y-m-d H:i:s', strtotime($expansion['silo_end']));
}

When This Happens:

  • Operator may have stopped expansion early
  • Machine malfunction or power interruption
  • Insufficient bead supply to complete fill

System Behavior:

  • Job end_time remains NULL
  • Job still appears in table with "(invalid date)" for Finished Expansion
  • Inventory is still marked as consumed
  • Administrators can review and correct if needed

Cost Calculations

Pre-expansion contributes to job costing through two components:

Labor Cost:

Labor Cost = Pre-Expander Rate of Pay × Pre-defined Pre-Expansion Time
  • Pre-Expander Rate of Pay: Hourly wage of employees with 'Pre-Expand' job type
  • Pre-defined Pre-Expansion Time: Standard time per density (configured per lot type)

Material Cost:

Material Cost = Cost per lb × Total Weight Expanded
  • Cost per lb: Stamped cost from lot records at time of inventory addition
  • Total Weight Expanded: Sum of weights from all bags consumed

Where Used:

  • Job costing reports
  • Profitability analysis
  • Production efficiency metrics

Weight Corrections

Users can correct bag weights directly from the table (though not visible in current screenshot, this feature exists):

Process:

  1. Click on weight field in table
  2. Field becomes editable inline
  3. Enter correct weight value
  4. Blur field or press Enter
  5. JavaScript calls /api/lot/update with new weight
  6. System validates and saves
  7. "Changes Saved" icon appears
  8. Inventory records updated immediately

Use Cases:

  • Scale calibration errors
  • Incorrect manual entries
  • Typos from pre-expander input

Tabulator Library

The page uses Tabulator for advanced table functionality:

Key Features Used:

  • Remote pagination with server-side data loading
  • Custom HTML formatters for complex cell content
  • DateTime formatters with locale support
  • Row click handlers for modal display
  • Auto-refresh capability
  • Responsive column sizing

Version: Check package.json or included script tag

Pagination Performance

Server-Side Benefits:

  • Prevents loading thousands of job records at once
  • Reduces initial page load time
  • Minimizes memory usage
  • Scales to large datasets

Implementation:

  • SQL LIMIT/OFFSET queries for efficiency
  • Total page count calculated once per request
  • Client maintains current page state
  • Seamless navigation between pages

Adding New Columns

To add a new column to the pre-expansion table:

  1. Update API Response (src/app/Controllers/Api/Preexpand.php:137):

    $row['new_field'] = $job->new_attribute;
  2. Update Tabulator Config (src/public/js/preexpansion/bead_in_progress.js):

    columns: [
    // ... existing columns
    {title: "New Column", field: "new_field", widthGrow: 1}
    ]
  3. Test Pagination - Ensure new field appears across all pages

The populateModal() function (defined elsewhere in the JS file) handles filling the modal with job details:

Expected Behavior:

  • Extracts job data from clicked row
  • Populates summary fields (Started, Finished, Silo, Lot Type)
  • Displays up to 3 lots entered from machine
  • Shows corresponding inventory records from database
  • Highlights mismatches if any exist

Example Usage

Example 1: Monitoring Active Pre-Expansion

  1. Operator with preexpand group membership logs in
  2. Clicks "Pre-Expansion" button on home page
  3. System navigates to /preexpand/view
  4. Page displays "Pre-Expansion In Progress" table
  5. Table shows current and recent pre-expansion jobs:
    • Row 1: Silo 1, started 8:30 AM, finished 9:15 AM, used Lot123 bags 1-3
    • Row 2: Silo 2, started 9:00 AM, not finished yet, using Lot456 bags 1-2
    • Row 3: Silo 3, started yesterday, completed, used multiple lots
  6. Table automatically refreshes every 30 seconds
  7. New jobs from Raspberry Pi appear automatically
  8. Operator can see real-time status of all silos

Example 2: Reviewing Job Details

  1. User on pre-expansion page sees recently completed job
  2. User clicks on table row for the job
  3. Modal dialog opens showing:
    • Job Summary:
      • Started: 10/25/25 8:30 AM
      • Finished: 10/25/25 9:15 AM
      • Silo #: 1
      • Lot Type: 1.0 PCF White
    • Inventory Used:
      • Lots Entered: Lot123, Lot456
      • Inventory Used: Shows specific bag records consumed
        • Lot123: bags 1, 2, 3
        • Lot456: bags 1, 2
  4. User verifies data is correct
  5. User clicks "Close" to return to table

Example 3: Handling Lot Mismatch

  1. Raspberry Pi sends pre-expansion data with lot name "Lot789"
  2. System searches inventory for "Lot789"
  3. No matching lot found in database
  4. System sends email to admins:
    Subject: Pre-Expansion Lot Mismatch

    The pre-expander reported using lot "Lot789" but this lot
    does not exist in inventory. Please resolve this mismatch.

    [Click here to resolve]
  5. Admin clicks link in email
  6. Resolution page shows:
    • Lot reported by machine: "Lot789"
    • Suggested matches:
      • Lot7891 (95% match)
      • Lot7890 (90% match)
      • Lot788 (75% match)
  7. Admin recognizes "Lot7891" is correct (typo in machine input)
  8. Admin clicks "Match to Lot7891"
  9. System updates pre-expansion job to use Lot7891
  10. Inventory records for Lot7891 marked as used
  11. Pre-expansion page now shows correct lot name

Example 4: Admin Reviewing Weekly Production

  1. Factory manager logs in as admin
  2. Navigates to Pre-Expansion page
  3. Changes page size to 100 rows for comprehensive view
  4. Reviews week's production:
    • Monday: 15 pre-expansion jobs, all silos active
    • Tuesday: 12 jobs, Silo 3 had "No Full" condition
    • Wednesday: 18 jobs, high production day
    • Thursday: 10 jobs, planned maintenance
    • Friday: 16 jobs, normal operation
  5. Manager notices Silo 2 used primarily for 1.0 PCF density
  6. Manager clicks several jobs to verify inventory consumption
  7. Manager exports data for production report (if export feature available)

Example 5: Troubleshooting Disconnected Job

  1. Operator notices yellow warning icon on recent job
  2. Operator clicks row to open modal
  3. Modal shows:
    • Started: 10/25/25 2:00 PM
    • Finished: (invalid date)
    • Status: Disconnected
    • Only 1 lot entered, expected 3
  4. Operator realizes power outage occurred at 2:15 PM
  5. Operator notes job incomplete due to outage
  6. Operator contacts supervisor to review actual silo contents
  7. Supervisor manually verifies inventory and makes corrections if needed
  8. Note added to production log for incident tracking

Inventory Management

Production Processes

  • Mold - Next step after pre-expansion where expanded beads are molded into blocks
  • Lines 1 & 2 - Cutting operations that use molded blocks

Administrative Functions

Integration & API

Home Page