πŸ—οΈ Architecture

System design, data flow, caching strategy, and technical decisions behind the BioBots analytics platform.

System Overview

BioBots Tool is a dual-layer application: a C# ASP.NET Web API backend that serves RESTful queries over bioprinting data, and a zero-dependency JavaScript frontend that provides interactive analytics tools.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ GitHub Pages β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ Query β”‚ β”‚ Explorer β”‚ β”‚ Table β”‚ β”‚ Compare β”‚ β”‚ β”‚ β”‚ Tool β”‚ β”‚ (Charts) β”‚ β”‚ View β”‚ β”‚ (Radar) β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ fetch() β”‚ β”‚ β–Ό β”‚ β”‚ bioprint-data.json β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ ASP.NET Web API β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ PrintsController β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ MetricRegistryβ”‚ β”‚ Pre-computed Stats β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ (11 metrics) β”‚ β”‚ (min/max/avg per metric)β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β”‚ β–² β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ β”‚ β”‚ β”‚ β”‚ File-Watch Cache Layer β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ (double-checked locking, LastWriteUtc) β”‚ β”‚ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ β”‚ β–Ό β”‚ β”‚ bioprint-data.json β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

πŸ–₯️ Backend

C# / ASP.NET Web API 2 on .NET Framework 4.x. Single controller with registry-based metric routing.

🌐 Frontend

Pure HTML/CSS/JS with zero dependencies (except jQuery for the legacy query tool). Canvas-based charting.

πŸ“Š Data

JSON flat file with ~400+ bioprinting records. Loaded once, cached in memory, auto-reloaded on change.

πŸš€ Deploy

Frontend on GitHub Pages. Backend via IIS or Docker container (GHCR).

Data Flow

Backend Request Pipeline

  1. Request arrives at /api/prints/{metric}/{comparison}/{value}
  2. PrintsController constructor calls EnsureCache()
  3. Cache check: Compare file's LastWriteTimeUtc with cached timestamp
  4. If changed: reload + re-parse JSON, filter nulls, pre-compute stats (under lock)
  5. If unchanged: fast path β€” use existing cached array
  6. Route to QueryMetric(): Look up metric in MetricRegistry
  7. Aggregation? Return pre-computed stat in O(1)
  8. Comparison? Scan array with selector + operator in O(n)
  9. Return integer count or double value

Frontend Data Flow

  1. Page loads β†’ fetch('bioprint-data.json')
  2. Parse JSON array into memory
  3. Compute summary statistics and populate dashboard
  4. User interaction triggers client-side filtering/aggregation
  5. Canvas API renders charts (histograms, scatter plots, radar)

Caching Strategy

The backend uses a file-watch cache with double-checked locking to balance performance with data freshness:

Request β†’ EnsureCache() β”‚ β”œβ”€ File exists? ──No──→ throw FileNotFoundException β”‚ β”œβ”€ Get LastWriteTimeUtc β”‚ β”œβ”€ Same as cached? ──Yes──→ Return (fast path) β”‚ └─ Enter lock β”œβ”€ Re-check timestamp (double-check) β”‚ └─ Same? β†’ Return β”‚ └─ Different β†’ LoadAndFilterPrints() β”œβ”€ Stream-deserialize JSON (Newtonsoft) β”œβ”€ Filter null/incomplete records β”œβ”€ PrecomputeStats() for all metrics └─ Update cache + timestamp
Why double-checked locking? The fast path (timestamp check) runs lock-free for maximum throughput under concurrent requests. The lock only activates when the file actually changes β€” which is rare in production.

Pre-computed Statistics

When the cache is built, a single O(n) pass computes min/max/average for all 11 metrics. This means aggregation queries (Maximum, Minimum, Average) return in O(1) β€” just a dictionary lookup.

Memory Efficiency

JSON is deserialized using Newtonsoft.Json streaming (JsonTextReader) rather than loading the entire file into a string. This reduces peak memory usage by ~50% for the 8MB+ data file compared to the legacy JavaScriptSerializer approach.

Data Model

Each print record contains three nested objects:

{
  "user_info": {
    "serial": 42,
    "email": "researcher@lab.edu"
  },
  "print_info": {
    "files": { "input": "sample.gcode", "output": "sample_out.gcode" },
    "pressure": { "extruder1": 45.0, "extruder2": 60.0 },
    "crosslinking": { "cl_enabled": true, "cl_duration": 15000, "cl_intensity": 50 },
    "resolution": { "layerHeight": 0.5, "layerNum": 48 },
    "wellplate": 6
  },
  "print_data": {
    "livePercent": 72.3,
    "deadPercent": 20.0,
    "elasticity": 80.5
  }
}

C# Model Classes

ClassPropertiesDescription
Printuser_info, print_info, print_dataRoot record
UserInfoserial (int), email (string)Printer owner
PrintInfofiles, pressure, crosslinking, resolution, wellplatePrint parameters
PrintDatalivePercent, deadPercent, elasticityPrint results
Pressureextruder1, extruder2Extruder pressures
Crosslinkingcl_enabled, cl_duration, cl_intensityPhotocrosslinking params
ResolutionlayerNum, layerHeightPrint resolution
Filesinput, outputGCODE filenames

Metric Registry

The MetricRegistry is a static dictionary that maps URL parameter names to selector functions and type info. Adding a new queryable metric is a one-line change β€” no new endpoint method needed:

// In MetricRegistry initialization
{ "myNewMetric", new MetricDescriptor(p => p.some_field, isInteger: false) }

Frontend Architecture

Pages

PagePurposeKey Feature
index.htmlQuery ToolInteractive query builder with result display
explorer.htmlData ExplorerHistograms + scatter plots with regression
table.htmlData TableSortable, searchable, filterable table with export
compare.htmlPrint ComparisonRadar charts + side-by-side metric breakdown

Design Decisions

  • Zero dependencies: All charts use the Canvas API directly β€” no Chart.js, D3, or other libraries. This keeps the pages fast and self-contained.
  • Client-side computation: The frontend loads the full JSON dataset and performs all filtering, aggregation, and visualization locally. This eliminates API round-trips for the interactive tools.
  • Dark theme: CSS custom properties with a slate-based dark palette, consistent across all pages.
  • Responsive: Grid layout adapts to mobile with single-column fallback.

Testing Strategy

Test Stack

ToolPurpose
JestTest runner + assertion library
jest-environment-jsdomDOM simulation for browser-targeting code
CodecovCoverage reporting and tracking

Test Suites

  • runMethod.test.js (87 tests) β€” Tests the jQuery-based API client: input validation, URL construction, button state management, response handling, integration scenarios
  • compare.test.js (40+ tests) β€” Tests the comparison tool logic: metrics, formatting, selection management, search, radar normalization, insight generation

Coverage Thresholds

MetricThreshold
Lines70%
Functions70%
Statements70%
Branches60%

CI/CD Pipeline

Push to master β”‚ β”œβ”€β†’ CI Workflow β”‚ β”œβ”€ Build (.NET/MSBuild on Windows) β”‚ β”œβ”€ Test (Jest on Ubuntu) β”‚ └─ Lint (JSON/YAML validation) β”‚ β”œβ”€β†’ Coverage Workflow β”‚ β”œβ”€ Run tests with --coverage β”‚ β”œβ”€ Upload to Codecov β”‚ └─ Generate job summary β”‚ β”œβ”€β†’ CodeQL Workflow β”‚ └─ Security scanning β”‚ β”œβ”€β†’ Docker Workflow β”‚ β”œβ”€ Build multi-stage image β”‚ └─ Push to ghcr.io β”‚ └─→ Pages Workflow └─ Deploy docs/ to GitHub Pages Release tag (v*) β”‚ β”œβ”€β†’ NuGet Publish β”‚ └─ Pack + push BioBots.Models to GitHub Packages β”‚ └─→ GitHub Release └─ Changelog + assets

Workflows

WorkflowTriggerPurpose
ci.ymlpush, PRBuild + Test + Lint
coverage.ymlpush, PRCode coverage + Codecov upload
codeql.ymlpush, PR, scheduleSecurity vulnerability scanning
docker.ymlpush, PRDocker image build + GHCR push
pages.ymlpushDeploy docs to GitHub Pages
nuget-publish.ymlreleaseNuGet package to GitHub Packages
auto-labeler.ymlPRAutomatic PR labeling