Balanced Mix Design (BMD) Module - Technical Specification¶
For Balanced Engineering Platform
Version: 1.0 Date: February 16, 2026 Status: Ready for Engineering Review
1. Overview¶
This document provides detailed technical specifications for implementing a Balanced Mix Design module within the existing Balanced Engineering platform. The module enables tracking of BMD projects, specimen management, performance testing, and compliance reporting.
2. User Stories & Requirements¶
2.1 Designer/Engineer Persona¶
Story 1: Create BMD Project
As a principal engineer, I want to create a new BMD project linked to an existing material test so that I can manage the complete design workflow in one place.
Acceptance Criteria: - [ ] User selects existing material test from project - [ ] Wizard collects: design approach (A/B/C/D), traffic category, climate zone, target air voids, PG grade - [ ] System generates project with unique ID (e.g., BMD-2026-001) - [ ] Project status initialized to "Design Phase" - [ ] Audit trail captures creation date/user
Story 2: Manage Mix Recipes
As an engineer, I want to create and version multiple mix designs so that I can iterate based on test results without losing previous versions.
Acceptance Criteria: - [ ] Create new recipe version (v1.0, v1.1, etc.) - [ ] Enter asphalt %, asphalt grade (PG), aggregate blend reference - [ ] Clone previous recipe as starting point for new version - [ ] View side-by-side comparison of recipe differences - [ ] View all tests associated with each recipe version
Story 3: Prepare Test Specimens
As a lab technician, I want to create specimen batches with automatic tracking of conditioning protocols so that specimens are ready for testing at the right time.
Acceptance Criteria: - [ ] Create batch of 3 specimens (standard replicate count) - [ ] Auto-generate specimen IDs (e.g., SPR-001-HWT-01, SPR-001-HWT-02, SPR-001-HWT-03) - [ ] Select specimen type (HWT, IDEAL-CT, SCB, etc.) - [ ] Record preparation date, compaction density, air voids - [ ] Set conditioning protocol (RTFO, PAV, Freeze-Thaw) - [ ] Display timeline: Prep → Conditioning → Ready for Test - [ ] Notifications when specimen ready for testing
Story 4: Record Test Results
As a lab technician, I want a simple form to enter test results specific to each test type so that data is captured accurately and consistently.
Acceptance Criteria: - [ ] Test-specific forms (separate UI for HWT vs IDEAL-CT vs SCB) - [ ] Form pre-populated with specimen info (temperature, density, air voids) - [ ] All required test parameters with units - [ ] Equipment selection dropdown - [ ] Auto-calculation of pass/fail against stored criteria - [ ] Color-coded result (green=pass, red=fail) - [ ] Upload photos of equipment/specimen - [ ] Digital signature for technician
Story 5: Analyze Performance
As an engineer, I want to see statistical analysis of my test results so that I can understand if my design meets criteria.
Acceptance Criteria: - [ ] Display all results for a specimen set (3 replicates) - [ ] Calculate average and standard deviation - [ ] Compare vs. specification limits (min/max) - [ ] Show pass/fail determination (e.g., average ≥ min OR all ≥ min) - [ ] Visual chart (spec range vs actual results) - [ ] Trend across recipe versions
Story 6: Generate Compliance Report
As a project manager, I want to generate a compliance report showing whether my design meets CDOT/UDOT specs so I can submit to the agency.
Acceptance Criteria: - [ ] Select state/region - [ ] System loads official specification criteria - [ ] Report shows: Design parameters → Test results → Pass/fail determination - [ ] Summary statement: "This design meets Colorado BMD requirements" - [ ] PDF export ready for agency submission - [ ] Signature block for PE approval
2.2 Laboratory Manager Persona¶
Story 7: Manage Equipment Calibrations
As a lab manager, I want visibility of all equipment calibrations and alerts when due so I can maintain ISO 17025 compliance.
Acceptance Criteria: - [ ] Equipment master list (HWT machines, IDEAL-CT, etc.) - [ ] Calibration calendar view - [ ] Auto-alert 30 days before calibration due - [ ] Upload calibration certificates - [ ] Pass/fail status - [ ] Corrective action tracking if failed - [ ] Compliance report for audits
3. Data Models (Detailed)¶
3.1 Core BMD Models¶
BalancedMixDesignProject¶
class BalancedMixDesignProject(db.Model):
__tablename__ = "balanced_mix_design_projects"
# Primary Key
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid4()))
# Foreign Keys
material_test_id = db.Column(db.String(36), db.ForeignKey('material_tests.id'),
nullable=False, unique=True)
# Project Identification
bmd_number = db.Column(db.String(50), unique=True) # BMD-2026-001
project_name = db.Column(db.String(200))
# Design Parameters
design_approach = db.Column(db.String(20), nullable=False) # Approach A, B, C, D
traffic_category = db.Column(db.String(50), nullable=False)
# Low-Volume, Medium, High, Very High
climate_zone = db.Column(db.String(100), nullable=False)
# Colorado-specific: High Country, Mountains, Transition, Valley, Southern
design_gyrations = db.Column(db.Integer) # Ndesign per AASHTO
pg_grade = db.Column(db.String(20)) # e.g., PG 58-28
# Design Targets
target_air_voids_min = db.Column(db.Float) # e.g., 3.5%
target_air_voids_max = db.Column(db.Float) # e.g., 4.5%
target_vma_min = db.Column(db.Float)
# Status & Workflow
status = db.Column(db.String(30), default="Design Phase")
# Design Phase, Lab Testing, Field Validation, Approved, Rejected
notes = db.Column(db.Text)
# Timestamps & Auditing
created_at = db.Column(db.DateTime, default=datetime.utcnow, nullable=False)
created_by_id = db.Column(db.String(36), db.ForeignKey('user.id'))
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
updated_by_id = db.Column(db.String(36), db.ForeignKey('user.id'))
# Relationships
material_test = db.relationship('MaterialTest', backref='bmd_projects')
recipes = db.relationship('BMDRecipe', back_populates='bmd_project',
cascade='all, delete-orphan')
specimens = db.relationship('BMDSpecimen', back_populates='bmd_project',
cascade='all, delete-orphan')
aggregate_blends = db.relationship('AggregateBlend', back_populates='bmd_project')
created_by = db.relationship('User', foreign_keys=[created_by_id])
updated_by = db.relationship('User', foreign_keys=[updated_by_id])
BMDRecipe¶
class BMDRecipe(db.Model):
__tablename__ = "bmd_recipes"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid4()))
# Foreign Keys
bmd_project_id = db.Column(db.String(36),
db.ForeignKey('balanced_mix_design_projects.id'),
nullable=False)
aggregate_blend_id = db.Column(db.String(36),
db.ForeignKey('aggregate_blends.id'))
created_by_id = db.Column(db.String(36), db.ForeignKey('user.id'))
# Recipe Identification
version = db.Column(db.String(10), nullable=False) # v1.0, v1.1, etc.
recipe_name = db.Column(db.String(200)) # Optional descriptive name
# Mix Design Composition
asphalt_content_percent = db.Column(db.Float, nullable=False) # e.g., 5.5%
asphalt_grade = db.Column(db.String(30)) # e.g., PG 58-28
asphalt_source = db.Column(db.String(200)) # Supplier name
# Aggregate Composition
coarse_aggregate_percent = db.Column(db.Float)
fine_aggregate_percent = db.Column(db.Float)
filler_percent = db.Column(db.Float)
rap_content_percent = db.Column(db.Float, default=0.0)
ras_content_percent = db.Column(db.Float, default=0.0)
# Performance Targets for this Recipe
target_density = db.Column(db.Float) # kg/m3
target_air_voids_percent = db.Column(db.Float)
target_vma = db.Column(db.Float)
target_jc_ideal_ct = db.Column(db.Float) # MPa·mm minimum
target_trd_hwt = db.Column(db.Float) # mm maximum
# Recipe Status
is_active = db.Column(db.Boolean, default=True)
notes = db.Column(db.Text)
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
# Relationships
bmd_project = db.relationship('BalancedMixDesignProject', back_populates='recipes')
aggregate_blend = db.relationship('AggregateBlend')
specimens = db.relationship('BMDSpecimen', back_populates='recipe')
created_by = db.relationship('User')
# Constraints
__table_args__ = (
db.UniqueConstraint('bmd_project_id', 'version',
name='unique_bmd_recipe_version'),
)
BMDSpecimen¶
class BMDSpecimen(db.Model):
__tablename__ = "bmd_specimens"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid4()))
# Foreign Keys
bmd_project_id = db.Column(db.String(36),
db.ForeignKey('balanced_mix_design_projects.id'),
nullable=False)
recipe_id = db.Column(db.String(36), db.ForeignKey('bmd_recipes.id'),
nullable=False)
# Specimen Identification
specimen_number = db.Column(db.String(50), unique=True) # SPR-001-HWT-01
specimen_type = db.Column(db.String(30), nullable=False)
# HWT, IDEAL-CT, SCB, Gyratory, Indirect-Tensile, etc.
replicate_number = db.Column(db.Integer) # 1, 2, or 3 (for sets of 3)
# Preparation Information
prepared_date = db.Column(db.DateTime)
prepared_by_id = db.Column(db.String(36), db.ForeignKey('user.id'))
compaction_method = db.Column(db.String(50)) # Gyratory, Marshall, etc.
compaction_gyrations = db.Column(db.Integer) # If gyratory compacted
# Physical Properties at Preparation
diameter_mm = db.Column(db.Float) # e.g., 150 for standard
height_mm = db.Column(db.Float) # e.g., 62
target_density = db.Column(db.Float) # kg/m3
actual_density = db.Column(db.Float) # kg/m3 (measured)
air_voids_percent = db.Column(db.Float) # Calculated from density
vma_percent = db.Column(db.Float) # Voids in Mineral Aggregate
bulk_specific_gravity = db.Column(db.Float)
# Conditioning
conditioning_protocol = db.Column(db.String(30)) # RTFO, PAV, Freeze-Thaw
conditioning_start_date = db.Column(db.DateTime)
conditioning_end_date = db.Column(db.DateTime)
simulated_age_years = db.Column(db.Float) # e.g., 7.0 for PAV
# Status Tracking
status = db.Column(db.String(30), default="Prepared")
# Prepared, Conditioning, Ready for Test, Testing, Complete, Failed
failure_reason = db.Column(db.Text) # If status is Failed
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Relationships
bmd_project = db.relationship('BalancedMixDesignProject', back_populates='specimens')
recipe = db.relationship('BMDRecipe', back_populates='specimens')
test_results = db.relationship('BMDTestResult', back_populates='specimen',
cascade='all, delete-orphan')
prepared_by = db.relationship('User', foreign_keys=[prepared_by_id])
BMDTestResult¶
class BMDTestResult(db.Model):
__tablename__ = "bmd_test_results"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid4()))
# Foreign Keys
specimen_id = db.Column(db.String(36), db.ForeignKey('bmd_specimens.id'),
nullable=False)
equipment_id = db.Column(db.String(36), db.ForeignKey('lab_equipment.id'))
pass_fail_criteria_id = db.Column(db.String(36),
db.ForeignKey('bmd_pass_fail_criteria.id'))
tested_by_id = db.Column(db.String(36), db.ForeignKey('user.id'),
nullable=False)
# Test Identification
test_date = db.Column(db.DateTime, nullable=False)
test_type = db.Column(db.String(30), nullable=False) # HWT, IDEAL-CT, SCB
test_number = db.Column(db.Integer) # Sequential test number
test_duration_minutes = db.Column(db.Float)
# Test Conditions
test_temperature_celsius = db.Column(db.Float)
ambient_temperature_celsius = db.Column(db.Float)
relative_humidity_percent = db.Column(db.Float)
# Test Data (Flexible JSON Structure)
test_data = db.Column(db.JSON) # See Section 3.3 for schemas
# Typical test results (denormalized for quick queries)
# Hamburg Wheel Tracker results
total_rut_depth_mm = db.Column(db.Float) # HWT
creep_slope_percent = db.Column(db.Float) # HWT
stripping_slope_percent = db.Column(db.Float) # HWT
stripping_inflection_point_mm = db.Column(db.Float) # HWT
# IDEAL-CT / SCB results
jc_critical_strain_energy = db.Column(db.Float) # MPa·mm for IDEAL-CT/SCB
flexibility_index = db.Column(db.Float) # I-FIT
# Pass/Fail Assessment
pass_fail_status = db.Column(db.String(20)) # Pass, Fail, Inconclusive
pass_fail_notes = db.Column(db.Text)
# Operator Notes
test_notes = db.Column(db.Text)
equipment_issues = db.Column(db.Text)
photo_paths = db.Column(db.JSON) # Array of file paths
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
# Relationships
specimen = db.relationship('BMDSpecimen', back_populates='test_results')
equipment = db.relationship('LabEquipment')
criteria = db.relationship('BMDPassFailCriteria')
tested_by = db.relationship('User')
LabEquipment¶
class LabEquipment(db.Model):
__tablename__ = "lab_equipment"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid4()))
# Equipment Identification
name = db.Column(db.String(100), nullable=False) # e.g., "Hamburg Wheel Tracker #1"
equipment_type = db.Column(db.String(50), nullable=False)
# HWT, IDEAL-CT, SCB-Machine, Gyratory-Compactor, DSR, etc.
manufacturer = db.Column(db.String(100))
model_number = db.Column(db.String(50))
serial_number = db.Column(db.String(50), unique=True)
# Equipment Details
purchase_date = db.Column(db.DateTime)
purchase_cost = db.Column(db.Numeric(12, 2))
location = db.Column(db.String(200)) # Lab A, Lab B, etc.
notes = db.Column(db.Text)
# Status
is_active = db.Column(db.Boolean, default=True)
last_calibration_date = db.Column(db.DateTime)
next_calibration_due = db.Column(db.DateTime)
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Relationships
calibrations = db.relationship('EquipmentCalibration', back_populates='equipment',
cascade='all, delete-orphan')
maintenance_records = db.relationship('EquipmentMaintenance',
back_populates='equipment',
cascade='all, delete-orphan')
test_results = db.relationship('BMDTestResult', back_populates='equipment')
EquipmentCalibration¶
class EquipmentCalibration(db.Model):
__tablename__ = "equipment_calibrations"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid4()))
# Foreign Key
equipment_id = db.Column(db.String(36), db.ForeignKey('lab_equipment.id'),
nullable=False)
# Calibration Details
calibration_date = db.Column(db.DateTime, nullable=False)
next_calibration_due = db.Column(db.DateTime, nullable=False)
calibration_frequency_months = db.Column(db.Integer, default=12)
calibrated_by = db.Column(db.String(200)) # Company or person
calibration_company = db.Column(db.String(200)) # External vendor if applicable
# Results
is_passing = db.Column(db.Boolean, nullable=False) # Pass or Fail
findings = db.Column(db.Text) # Issues found
remediation = db.Column(db.Text) # Corrective actions taken
# Documentation
certificate_path = db.Column(db.String(500)) # PDF file path
certificate_number = db.Column(db.String(100))
document_upload_date = db.Column(db.DateTime)
# Cost
cost = db.Column(db.Numeric(10, 2))
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Relationships
equipment = db.relationship('LabEquipment', back_populates='calibrations')
BMDPassFailCriteria¶
class BMDPassFailCriteria(db.Model):
__tablename__ = "bmd_pass_fail_criteria"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid4()))
# Geographic & Context
state = db.Column(db.String(20), nullable=False) # CO, UT, WY, etc.
traffic_category = db.Column(db.String(50)) # Low, Medium, High, Very High
climate_zone = db.Column(db.String(100))
# For CO: High Country, Mountains, Transition, Valley, Southern
pavement_location = db.Column(db.String(50)) # Surface, Intermediate, Base (if applicable)
# Test Criteria
test_type = db.Column(db.String(30), nullable=False) # HWT, IDEAL-CT, SCB
specification_standard = db.Column(db.String(100)) # AASHTO T 324, TP 105, etc.
# Limits (one or both may be set)
minimum_value = db.Column(db.Float) # e.g., 0.35 for Jc minimum
maximum_value = db.Column(db.Float) # e.g., 12.5 for TRD maximum
units = db.Column(db.String(50)) # mm, MPa·mm, %, etc.
# Metadata
description = db.Column(db.Text)
source = db.Column(db.String(200)) # CDOT, UDOT, AASHTO, etc.
effective_date = db.Column(db.DateTime)
superseded_date = db.Column(db.DateTime) # When this was replaced
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
# Relationships
test_results = db.relationship('BMDTestResult', back_populates='criteria')
AggregateBlend¶
class AggregateBlend(db.Model):
__tablename__ = "aggregate_blends"
id = db.Column(db.String(36), primary_key=True, default=lambda: str(uuid4()))
# Foreign Keys
bmd_project_id = db.Column(db.String(36),
db.ForeignKey('balanced_mix_design_projects.id'))
# Blend Identification
blend_version = db.Column(db.String(10)) # v1.0, v1.1, etc.
blend_name = db.Column(db.String(200))
# Blend Composition (% by weight)
coarse_aggregate_percent = db.Column(db.Float)
fine_aggregate_percent = db.Column(db.Float)
mineral_filler_percent = db.Column(db.Float)
# Gradation Data
sieve_analysis_data = db.Column(db.JSON) # See Section 3.3
# Optimization Tracking
optimization_method = db.Column(db.String(100))
# Manual, Linear Programming, Genetic Algorithm, Neural Network, etc.
optimization_status = db.Column(db.String(30))
# In Progress, Optimized, Lab Validated, Field Validated
ml_model_version = db.Column(db.String(50)) # Reference to ML model if used
# Performance Predictions (from ML model)
predicted_vma = db.Column(db.Float)
predicted_density = db.Column(db.Float)
predicted_binder_content = db.Column(db.Float)
predicted_compaction_difficulty = db.Column(db.String(20)) # Easy, Moderate, Difficult
# Notes
notes = db.Column(db.Text)
# Timestamps
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, onupdate=datetime.utcnow)
# Relationships
bmd_project = db.relationship('BalancedMixDesignProject',
back_populates='aggregate_blends')
recipes = db.relationship('BMDRecipe', backref='blend')
3.2 Integration with Existing Models¶
MaterialTest model changes:
class MaterialTest(db.Model):
# ... existing fields ...
# Add new field
bmd_project_id = db.Column(db.String(36),
db.ForeignKey('balanced_mix_design_projects.id'))
# Relationship
bmd_project = db.relationship('BalancedMixDesignProject')
3.3 JSON Data Schemas¶
Hamburg Wheel Tracker Test Data Schema¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Hamburg Wheel Tracker Test Result",
"type": "object",
"properties": {
"wheel_passes": {
"type": "array",
"items": {
"type": "object",
"properties": {
"pass_number": { "type": "integer" },
"rut_depth_mm": { "type": "number" }
}
},
"description": "Array of wheel passes and corresponding rut depths"
},
"total_rut_depth_mm": {
"type": "number",
"description": "Final rut depth at end of test"
},
"creep_slope_percent": {
"type": "number",
"description": "Slope of secondary creep phase"
},
"stripping_slope_percent": {
"type": "number",
"description": "Slope of stripping phase"
},
"stripping_inflection_point_mm": {
"type": "number",
"description": "Rut depth at stripping inflection point"
},
"wheel_passes_at_inflection": {
"type": "integer",
"description": "Number of passes when stripping occurs"
},
"test_temperature_celsius": {
"type": "number",
"description": "Temperature maintained during test (typically 50)"
}
},
"required": ["total_rut_depth_mm", "test_temperature_celsius"]
}
IDEAL-CT Test Data Schema¶
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "IDEAL-CT Test Result",
"type": "object",
"properties": {
"load_displacement_curve": {
"type": "array",
"items": {
"type": "object",
"properties": {
"time_seconds": { "type": "number" },
"load_kn": { "type": "number" },
"displacement_mm": { "type": "number" }
}
},
"description": "Complete load vs displacement curve data"
},
"peak_load_kn": {
"type": "number",
"description": "Maximum load reached during test"
},
"jc_critical_strain_energy_mpa_mm": {
"type": "number",
"description": "Critical strain energy release rate (J-integral)"
},
"post_peak_slope": {
"type": "number",
"description": "Slope of load-displacement curve after peak"
},
"flexibility_index": {
"type": "number",
"description": "Post-peak slope divided by peak load (cracking resistance)"
},
"test_temperature_celsius": {
"type": "number",
"description": "Temperature during test (typically 20)"
},
"specimen_diameter_mm": {
"type": "number"
},
"specimen_height_mm": {
"type": "number"
}
},
"required": ["jc_critical_strain_energy_mpa_mm", "test_temperature_celsius"]
}
4. API Endpoints¶
4.1 BMD Project Endpoints¶
POST /api/bmd/projects
GET /api/bmd/projects
GET /api/bmd/projects/<project_id>
PUT /api/bmd/projects/<project_id>
DELETE /api/bmd/projects/<project_id>
Example Request - Create BMD Project:
POST /api/bmd/projects
{
"material_test_id": "mt-123",
"design_approach": "Approach D",
"traffic_category": "High Volume",
"climate_zone": "Mountain",
"pg_grade": "PG 58-28",
"target_air_voids_min": 3.5,
"target_air_voids_max": 4.5,
"design_gyrations": 100
}
4.2 Recipe Management Endpoints¶
POST /api/bmd/projects/<project_id>/recipes
GET /api/bmd/projects/<project_id>/recipes
GET /api/bmd/recipes/<recipe_id>
PUT /api/bmd/recipes/<recipe_id>
POST /api/bmd/recipes/<recipe_id>/compare
Example Request - Create Recipe:
POST /api/bmd/projects/bmd-001/recipes
{
"version": "v1.0",
"asphalt_content_percent": 5.5,
"asphalt_grade": "PG 58-28",
"coarse_aggregate_percent": 45.0,
"fine_aggregate_percent": 48.5,
"filler_percent": 6.5,
"rap_content_percent": 0.0,
"target_density": 2350.0,
"target_air_voids_percent": 4.0
}
4.3 Specimen Management Endpoints¶
POST /api/bmd/projects/<project_id>/specimens/batch
GET /api/bmd/projects/<project_id>/specimens
GET /api/bmd/specimens/<specimen_id>
PUT /api/bmd/specimens/<specimen_id>
POST /api/bmd/specimens/<specimen_id>/start-conditioning
Example Request - Create Specimen Batch:
POST /api/bmd/projects/bmd-001/specimens/batch
{
"recipe_id": "recipe-001",
"specimen_type": "HWT",
"batch_size": 3,
"conditioning_protocol": "PAV",
"target_density": 2350.0,
"prepared_by_id": "user-123"
}
4.4 Test Result Endpoints¶
POST /api/bmd/specimens/<specimen_id>/test-results
GET /api/bmd/specimens/<specimen_id>/test-results
GET /api/bmd/test-results/<result_id>
PUT /api/bmd/test-results/<result_id>
Example Request - Submit HWT Result:
POST /api/bmd/specimens/spr-001/test-results
{
"test_type": "HWT",
"test_date": "2026-02-16T14:30:00Z",
"equipment_id": "hwt-001",
"test_temperature_celsius": 50,
"test_data": {
"total_rut_depth_mm": 10.5,
"creep_slope_percent": 0.6,
"stripping_slope_percent": 0.4,
"stripping_inflection_point_mm": 1.8,
"wheel_passes_at_inflection": 4200
},
"tested_by_id": "user-456",
"test_notes": "Test completed successfully"
}
4.5 Equipment Calibration Endpoints¶
POST /api/equipment/calibrations
GET /api/equipment/calibrations?status=due
GET /api/equipment/<equipment_id>/calibration-history
PUT /api/equipment/calibrations/<calibration_id>
4.6 Compliance & Reporting Endpoints¶
GET /api/bmd/projects/<project_id>/compliance-status
GET /api/bmd/projects/<project_id>/compliance-report
POST /api/bmd/projects/<project_id>/generate-pdf-report
5. UI/UX Specifications¶
5.1 Page: BMD Project Dashboard¶
Route: /bmd/projects/<project_id>
Components: - Header Section - Project name and number (BMD-2026-001) - Status badge (Design Phase, Lab Testing, Approved) - Progress bar showing completion of 8 FHWA tasks - Quick actions: Edit, Delete, Generate Report
- Design Parameters Panel
- Design Approach, Traffic Category, Climate Zone (readonly)
- Target Air Voids range
-
PG Grade
-
Workflow Progress
- Task checklist: [ ] Leadership Planning [ ] Test Selection [ ] Mix Design [ ] Baseline Data ...
-
Timeline visualization
-
Active Recipe Section
- Current recipe version (v1.0, v1.1, etc.)
- Recipe composition breakdown
-
Link to create new version
-
Specimen Status Overview
- Count: Total specimens, Ready for test, Testing, Complete, Failed
- Grouped by specimen type (HWT, IDEAL-CT, SCB)
-
Timeline: Conditioning progress
-
Test Results Summary
- Summary table: Test Type | Specimen Count | Pass | Fail | Inconclusive
- Pass rate % with green/red indicator
-
Latest results with pass/fail status
-
Compliance Status
- Selected state/region
- Summary: "Meets CDOT criteria" or "Does not meet criteria (3 of 5 tests failed)"
- Link to full compliance report
5.2 Page: Test Data Entry Form (HWT)¶
Route: /bmd/specimens/<specimen_id>/enter-results/hwt
Form Sections:
- Specimen Information (readonly)
- Specimen ID, Type, Recipe Version, Density, Air Voids
-
Conditioning Protocol, Preparation Date
-
Test Setup
- Test Date/Time picker
- Equipment dropdown (HWT-001, HWT-002, etc.)
- Temperature input (default 50°C)
-
Ambient conditions (optional)
-
Hamburg Wheel Tracker Results
- Total Rut Depth (mm) - required
- Creep Slope (%) - optional but recommended
- Stripping Slope (%) - optional but recommended
- Stripping Inflection Point (mm) - optional
-
Alternative: Upload data file from equipment (CSV/JSON)
-
Validation & Pass/Fail
- Auto-display selected criteria (e.g., "Max TRD: 12.5 mm")
- Real-time pass/fail indicator
-
Explanation: "TRD 10.5 mm < 12.5 mm PASS"
-
Additional Information
- Test notes (textarea)
- Equipment issues (if any)
-
Photo upload (before/after)
-
Digital Signature
- Tested by (dropdown, pre-selected current user)
- Digital signature (or checkbox: "I certify this data is accurate")
5.3 Page: Performance Analysis Dashboard¶
Route: /bmd/projects/<project_id>/analysis
Sections:
- Design Validation Scorecard
- Design targets vs. test results
- Visual: Spec range (green zone) with actual results plotted
-
Pass rate: x/y tests passing
-
Test Results by Specimen
- Table format:
- Specimen ID | Test Type | Result Value | Spec Min/Max | Status
-
Sortable, filterable by specimen type
-
Statistical Summary (for replicate sets)
- Specimen 01 value: 10.2 mm
- Specimen 02 value: 10.8 mm
- Specimen 03 value: 10.5 mm
- Average: 10.5 mm
- Std Dev: 0.31 mm
- Coefficient of Variation: 3.0%
-
Pass/Fail determination (if average ≥ min)
-
Recipe Version Comparison
- Line chart: Recipe v1.0 vs v1.1 performance
-
Key deltas highlighted
-
Trend Analysis
- Time-series chart of test results over time
- Shows progression of recipe refinements
5.4 Page: Equipment Calibration Manager¶
Route: /lab/equipment/calibrations
Sections:
- Equipment Master List
- Table: Equipment Name | Type | Model | Next Cal Due | Status
- Color coding: Green (OK), Yellow (Due within 30 days), Red (Overdue)
-
Quick actions: View calibration, Schedule new
-
Calibration Calendar
- Month view
- Equipment highlighted on calibration due dates
-
Auto-reminders 30 days prior
-
Overdue Alerts
- Red banner if any equipment overdue
-
Escalation: Email lab manager if >1 piece overdue
-
Calibration Record
- Calibration Date, Next Due, Frequency
- Calibrated By (vendor name)
- Pass/Fail Status with findings
- Certificate upload/view
6. Database Migrations¶
Initial Migration¶
# migrations/versions/xxxxx_add_bmd_module.py
def upgrade():
# Create all BMD tables
op.create_table(
'balanced_mix_design_projects',
sa.Column('id', sa.String(36), primary_key=True),
sa.Column('material_test_id', sa.String(36), sa.ForeignKey('material_tests.id')),
sa.Column('bmd_number', sa.String(50), unique=True),
# ... all other columns
)
op.create_table(
'bmd_recipes',
# ... columns
)
# ... other tables ...
op.create_table(
'lab_equipment',
# ... columns
)
def downgrade():
op.drop_table('balanced_mix_design_projects')
# ... drop other tables ...
7. Implementation Notes¶
7.1 Code Organization¶
app/
├── models/
│ ├── bmd/
│ │ ├── __init__.py
│ │ ├── project.py # BalancedMixDesignProject
│ │ ├── recipe.py # BMDRecipe
│ │ ├── specimen.py # BMDSpecimen
│ │ ├── test_result.py # BMDTestResult
│ │ ├── criteria.py # BMDPassFailCriteria
│ │ ├── blend.py # AggregateBlend
│ │ └── equipment.py # LabEquipment, EquipmentCalibration
│
├── blueprints/
│ └── bmd/
│ ├── __init__.py
│ ├── routes.py # Web routes
│ ├── forms.py # WTForms for specimen, test entry
│ └── validators.py # Custom validators
│
├── services/
│ ├── bmd/
│ │ ├── __init__.py
│ │ ├── project_service.py # BMD project logic
│ │ ├── specimen_service.py # Specimen lifecycle
│ │ ├── test_service.py # Test result processing
│ │ ├── compliance_service.py # Pass/fail evaluation
│ │ └── report_generator.py # PDF compliance reports
│
├── templates/
│ └── bmd/
│ ├── project_dashboard.html
│ ├── test_entry_hwt.html
│ ├── test_entry_ideal_ct.html
│ ├── analysis_dashboard.html
│ ├── equipment_calibration.html
│ └── compliance_report.html
7.2 Type Hints¶
# Use Python 3.10+ type hints consistently
def create_specimen(
bmd_project: BalancedMixDesignProject,
recipe: BMDRecipe,
specimen_type: str,
target_density: float,
prepared_by: User
) -> BMDSpecimen:
"""Create a new test specimen for BMD project."""
specimen = BMDSpecimen(
bmd_project=bmd_project,
recipe=recipe,
specimen_type=specimen_type,
target_density=target_density,
prepared_by=prepared_by
)
db.session.add(specimen)
db.session.commit()
return specimen
7.3 Testing Strategy¶
Unit Tests: - Test pass/fail logic for each criteria type - Test specimen lifecycle state machine - Test JSON schema validation for test data
Integration Tests: - Create BMD project → Create recipe → Create specimens → Submit results → Generate report - Equipment calibration workflow
End-to-End Tests (Selenium): - User creates BMD project in UI - User enters test results via form - User views compliance report
8. Performance Considerations¶
8.1 Database Indexing¶
# In models, add indexes for common queries
__table_args__ = (
db.Index('idx_bmd_project_material_test', 'material_test_id'),
db.Index('idx_specimen_bmd_project', 'bmd_project_id', 'specimen_type'),
db.Index('idx_test_result_specimen', 'specimen_id', 'test_date'),
db.Index('idx_equipment_calibration_due', 'next_calibration_due'),
)
8.2 Query Optimization¶
- Use
.select_in_load()for relationships to avoid N+1 queries - Cache pass/fail criteria database (rarely changes)
- Lazy-load photos/documents
9. Security & Compliance¶
9.1 Access Control¶
- BMD project access: Only project manager, assigned engineers, principal engineers
- Equipment calibration: Lab manager + principal engineers only
- Audit trail: All changes to projects/results logged to AuditHistory
9.2 Data Integrity¶
- Specimen data immutable once test completed
- Test results can be amended but audit trail shows changes
- Approved reports cannot be deleted
10. Configuration¶
Regional Criteria Database¶
Pre-populate with Colorado initial release, add others in subsequent releases:
# scripts/seed_bmd_criteria.py
def seed_colorado_criteria():
criteria_data = [
{
'state': 'CO',
'traffic_category': 'High Volume',
'climate_zone': 'Mountain',
'test_type': 'HWT',
'max_trd_mm': 12.5,
'source': 'CDOT 2026 Specifications'
},
# ... more criteria
]
for crit in criteria_data:
db.session.add(BMDPassFailCriteria(**crit))
db.session.commit()
11. Acceptance Criteria Checklist¶
- [ ] All data models created and migrated
- [ ] API endpoints functional and tested
- [ ] BMD project creation wizard complete
- [ ] Test data entry forms (HWT, IDEAL-CT) working
- [ ] Pass/fail evaluation logic correct
- [ ] Equipment calibration module operational
- [ ] Compliance report generation working
- [ ] Colorado criteria database seeded
- [ ] UI responsive on mobile/tablet
- [ ] Audit trail functional
- [ ] Performance acceptable (<2s page loads)
- [ ] All forms validated
- [ ] Documentation complete
Document Status: Ready for Engineering Implementation Next Step: Schedule architecture review with development team