ποΈ 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.
π₯οΈ 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
- Request arrives at
/api/prints/{metric}/{comparison}/{value} - PrintsController constructor calls
EnsureCache() - Cache check: Compare file's
LastWriteTimeUtcwith cached timestamp - If changed: reload + re-parse JSON, filter nulls, pre-compute stats (under lock)
- If unchanged: fast path β use existing cached array
- Route to QueryMetric(): Look up metric in
MetricRegistry - Aggregation? Return pre-computed stat in O(1)
- Comparison? Scan array with selector + operator in O(n)
- Return integer count or double value
Frontend Data Flow
- Page loads β
fetch('bioprint-data.json') - Parse JSON array into memory
- Compute summary statistics and populate dashboard
- User interaction triggers client-side filtering/aggregation
- 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:
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
| Class | Properties | Description |
|---|---|---|
Print | user_info, print_info, print_data | Root record |
UserInfo | serial (int), email (string) | Printer owner |
PrintInfo | files, pressure, crosslinking, resolution, wellplate | Print parameters |
PrintData | livePercent, deadPercent, elasticity | Print results |
Pressure | extruder1, extruder2 | Extruder pressures |
Crosslinking | cl_enabled, cl_duration, cl_intensity | Photocrosslinking params |
Resolution | layerNum, layerHeight | Print resolution |
Files | input, output | GCODE 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
| Page | Purpose | Key Feature |
|---|---|---|
index.html | Query Tool | Interactive query builder with result display |
explorer.html | Data Explorer | Histograms + scatter plots with regression |
table.html | Data Table | Sortable, searchable, filterable table with export |
compare.html | Print Comparison | Radar 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
| Tool | Purpose |
|---|---|
| Jest | Test runner + assertion library |
| jest-environment-jsdom | DOM simulation for browser-targeting code |
| Codecov | Coverage 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
| Metric | Threshold |
|---|---|
| Lines | 70% |
| Functions | 70% |
| Statements | 70% |
| Branches | 60% |
CI/CD Pipeline
Workflows
| Workflow | Trigger | Purpose |
|---|---|---|
ci.yml | push, PR | Build + Test + Lint |
coverage.yml | push, PR | Code coverage + Codecov upload |
codeql.yml | push, PR, schedule | Security vulnerability scanning |
docker.yml | push, PR | Docker image build + GHCR push |
pages.yml | push | Deploy docs to GitHub Pages |
nuget-publish.yml | release | NuGet package to GitHub Packages |
auto-labeler.yml | PR | Automatic PR labeling |