Skip to content

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:

  1. Specimen Information (readonly)
  2. Specimen ID, Type, Recipe Version, Density, Air Voids
  3. Conditioning Protocol, Preparation Date

  4. Test Setup

  5. Test Date/Time picker
  6. Equipment dropdown (HWT-001, HWT-002, etc.)
  7. Temperature input (default 50°C)
  8. Ambient conditions (optional)

  9. Hamburg Wheel Tracker Results

  10. Total Rut Depth (mm) - required
  11. Creep Slope (%) - optional but recommended
  12. Stripping Slope (%) - optional but recommended
  13. Stripping Inflection Point (mm) - optional
  14. Alternative: Upload data file from equipment (CSV/JSON)

  15. Validation & Pass/Fail

  16. Auto-display selected criteria (e.g., "Max TRD: 12.5 mm")
  17. Real-time pass/fail indicator
  18. Explanation: "TRD 10.5 mm < 12.5 mm PASS"

  19. Additional Information

  20. Test notes (textarea)
  21. Equipment issues (if any)
  22. Photo upload (before/after)

  23. Digital Signature

  24. Tested by (dropdown, pre-selected current user)
  25. Digital signature (or checkbox: "I certify this data is accurate")

5.3 Page: Performance Analysis Dashboard

Route: /bmd/projects/<project_id>/analysis

Sections:

  1. Design Validation Scorecard
  2. Design targets vs. test results
  3. Visual: Spec range (green zone) with actual results plotted
  4. Pass rate: x/y tests passing

  5. Test Results by Specimen

  6. Table format:
    • Specimen ID | Test Type | Result Value | Spec Min/Max | Status
  7. Sortable, filterable by specimen type

  8. Statistical Summary (for replicate sets)

  9. Specimen 01 value: 10.2 mm
  10. Specimen 02 value: 10.8 mm
  11. Specimen 03 value: 10.5 mm
  12. Average: 10.5 mm
  13. Std Dev: 0.31 mm
  14. Coefficient of Variation: 3.0%
  15. Pass/Fail determination (if average ≥ min)

  16. Recipe Version Comparison

  17. Line chart: Recipe v1.0 vs v1.1 performance
  18. Key deltas highlighted

  19. Trend Analysis

  20. Time-series chart of test results over time
  21. Shows progression of recipe refinements

5.4 Page: Equipment Calibration Manager

Route: /lab/equipment/calibrations

Sections:

  1. Equipment Master List
  2. Table: Equipment Name | Type | Model | Next Cal Due | Status
  3. Color coding: Green (OK), Yellow (Due within 30 days), Red (Overdue)
  4. Quick actions: View calibration, Schedule new

  5. Calibration Calendar

  6. Month view
  7. Equipment highlighted on calibration due dates
  8. Auto-reminders 30 days prior

  9. Overdue Alerts

  10. Red banner if any equipment overdue
  11. Escalation: Email lab manager if >1 piece overdue

  12. Calibration Record

  13. Calibration Date, Next Due, Frequency
  14. Calibrated By (vendor name)
  15. Pass/Fail Status with findings
  16. 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