adding clqms01 wst-concept wst-database
This commit is contained in:
parent
f99b72daeb
commit
b3a1323368
@ -57,6 +57,13 @@ module.exports = function (eleventyConfig) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// CLQMS collection sorted by order
|
||||||
|
eleventyConfig.addCollection("clqms", function (collectionApi) {
|
||||||
|
return collectionApi.getFilteredByTag("clqms").sort((a, b) => {
|
||||||
|
return (Number(a.data.order) || 99) - (Number(b.data.order) || 99);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dir: {
|
dir: {
|
||||||
input: "src",
|
input: "src",
|
||||||
|
|||||||
@ -3,11 +3,11 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<meta name="description" content="{{ description | default('5Panda - Project Proposals') }}">
|
<meta name="description" content="{{ description | default('5Panda - Portfolio & Documentation') }}">
|
||||||
<meta
|
<meta
|
||||||
name="author" content="5Panda"> <!-- Open Graph -->
|
name="author" content="5Panda"> <!-- Open Graph -->
|
||||||
<meta property="og:title" content="{{ title | default('5Panda') }}">
|
<meta property="og:title" content="{{ title | default('5Panda') }}">
|
||||||
<meta property="og:description" content="{{ description | default('Project Proposals') }}">
|
<meta property="og:description" content="{{ description | default('Portfolio & Documentation') }}">
|
||||||
<meta property="og:type" content="{{ ogType | default('website') }}">
|
<meta property="og:type" content="{{ ogType | default('website') }}">
|
||||||
<title>{{ title | default("5Panda") }}</title>
|
<title>{{ title | default("5Panda") }}</title>
|
||||||
<!-- Fonts -->
|
<!-- Fonts -->
|
||||||
@ -21,12 +21,12 @@
|
|||||||
<body
|
<body
|
||||||
class="min-h-screen bg-base-100 text-base-content">
|
class="min-h-screen bg-base-100 text-base-content">
|
||||||
<!-- Navbar -->
|
<!-- Navbar -->
|
||||||
<div class="navbar bg-base-100/80 backdrop-blur-xl border-b border-white/5 sticky top-0 z-50">
|
<div class="navbar min-h-12 bg-base-100/80 backdrop-blur-xl border-b border-white/5 sticky top-0 z-50">
|
||||||
<div class="navbar-start">
|
<div class="navbar-start">
|
||||||
<a href="/" class="btn btn-ghost text-xl font-bold gradient-text">5Panda</a>
|
<a href="/" class="text-xl font-bold gradient-text hover:opacity-80 transition-opacity px-2">5Panda</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="navbar-center hidden lg:flex">
|
<div class="navbar-center hidden lg:flex">
|
||||||
<ul class="menu menu-horizontal px-1 gap-1">
|
<ul class="menu menu-sm menu-horizontal px-1 gap-1">
|
||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="/"
|
href="/"
|
||||||
@ -38,17 +38,43 @@
|
|||||||
<li>
|
<li>
|
||||||
<a
|
<a
|
||||||
href="/blog/"
|
href="/blog/"
|
||||||
class="rounded-lg {% if '/blog' in page.url %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10
|
class="rounded-lg {% if '/blog/' in page.url %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10
|
||||||
hover:text-primary transition-colors">
|
hover:text-primary transition-colors">
|
||||||
Proposals
|
Blog
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a
|
||||||
|
href="/team/"
|
||||||
|
class="rounded-lg {% if '/team/' in page.url %}bg-primary/10 text-primary{% endif %} hover:bg-primary/10
|
||||||
|
hover:text-primary transition-colors">
|
||||||
|
Team
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<details class="group">
|
||||||
|
<summary class="rounded-lg hover:bg-primary/10 hover:text-primary transition-colors">
|
||||||
|
Project
|
||||||
|
</summary>
|
||||||
|
<ul class="p-2 bg-base-100 rounded-t-none bg-base-100/95 backdrop-blur-xl border border-white/5 shadow-xl w-60 z-[100]">
|
||||||
|
{% for post in collections.posts %}
|
||||||
|
{% if 'clqms' not in post.data.tags %}
|
||||||
|
<li>
|
||||||
|
<a href="{{ post.url }}" class="{% if page.url == post.url %}text-primary bg-primary/10{% endif %} hover:text-primary hover:bg-primary/10">
|
||||||
|
{{ post.data.title }}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="navbar-end">
|
class="navbar-end">
|
||||||
<!-- Theme toggle -->
|
<!-- Theme toggle -->
|
||||||
<label class="swap swap-rotate btn btn-ghost btn-circle">
|
<label class="swap swap-rotate btn btn-ghost btn-circle btn-sm">
|
||||||
<input type="checkbox" class="theme-controller" value="pandaLight"/>
|
<input type="checkbox" class="theme-controller" value="pandaLight"/>
|
||||||
<svg class="swap-off h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
|
<svg class="swap-off h-5 w-5 fill-current" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 24 24">
|
||||||
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/>
|
<path d="M5.64,17l-.71.71a1,1,0,0,0,0,1.41,1,1,0,0,0,1.41,0l.71-.71A1,1,0,0,0,5.64,17ZM5,12a1,1,0,0,0-1-1H3a1,1,0,0,0,0,2H4A1,1,0,0,0,5,12Zm7-7a1,1,0,0,0,1-1V3a1,1,0,0,0-2,0V4A1,1,0,0,0,12,5ZM5.64,7.05a1,1,0,0,0,.7.29,1,1,0,0,0,.71-.29,1,1,0,0,0,0-1.41l-.71-.71A1,1,0,0,0,4.93,6.34Zm12,.29a1,1,0,0,0,.7-.29l.71-.71a1,1,0,1,0-1.41-1.41L17,5.64a1,1,0,0,0,0,1.41A1,1,0,0,0,17.66,7.34ZM21,11H20a1,1,0,0,0,0,2h1a1,1,0,0,0,0-2Zm-9,8a1,1,0,0,0-1,1v1a1,1,0,0,0,2,0V20A1,1,0,0,0,12,19ZM18.36,17A1,1,0,0,0,17,18.36l.71.71a1,1,0,0,0,1.41,0,1,1,0,0,0,0-1.41ZM12,6.5A5.5,5.5,0,1,0,17.5,12,5.51,5.51,0,0,0,12,6.5Zm0,9A3.5,3.5,0,1,1,15.5,12,3.5,3.5,0,0,1,12,15.5Z"/>
|
||||||
@ -58,7 +84,7 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</label>
|
</label>
|
||||||
<!-- GitHub link -->
|
<!-- GitHub link -->
|
||||||
<a href="https://github.com" target="_blank" class="btn btn-ghost btn-circle">
|
<a href="https://github.com" target="_blank" class="btn btn-ghost btn-circle btn-sm">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="currentColor" viewbox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="currentColor" viewbox="0 0 24 24">
|
||||||
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207
|
<path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207
|
||||||
11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729
|
11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729
|
||||||
@ -80,7 +106,7 @@
|
|||||||
<footer class="footer footer-center bg-base-200 text-base-content p-10 border-t border-white/5">
|
<footer class="footer footer-center bg-base-200 text-base-content p-10 border-t border-white/5">
|
||||||
<aside>
|
<aside>
|
||||||
<p class="font-bold text-xl gradient-text mb-2">5Panda</p>
|
<p class="font-bold text-xl gradient-text mb-2">5Panda</p>
|
||||||
<p class="text-base-content/60">Project Proposals & Ideas</p>
|
<p class="text-base-content/60">Portfolio & Documentation</p>
|
||||||
</aside>
|
</aside>
|
||||||
<nav>
|
<nav>
|
||||||
<div class="grid grid-flow-col gap-4">
|
<div class="grid grid-flow-col gap-4">
|
||||||
|
|||||||
82
src/_layouts/clqms-post.njk
Normal file
82
src/_layouts/clqms-post.njk
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
---
|
||||||
|
layout: base.njk
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="section-container py-12 animate-slide-up">
|
||||||
|
<div
|
||||||
|
class="flex flex-col lg:flex-row gap-12">
|
||||||
|
<!-- Sidebar Navigation -->
|
||||||
|
<aside class="lg:w-1/4">
|
||||||
|
<div class="sticky top-24 bg-base-200/50 backdrop-blur-xl border border-white/5 rounded-2xl p-6">
|
||||||
|
<h3 class="font-bold text-lg mb-4 flex items-center gap-2">
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
class="h-5 w-5 text-primary"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2
|
||||||
|
2 0 012-2h6a2 2 0 012 2v2M7 7h10"/>
|
||||||
|
</svg>
|
||||||
|
Updates
|
||||||
|
</h3>
|
||||||
|
<nav class="space-y-1">
|
||||||
|
<a
|
||||||
|
href="/blog/clqms01/"
|
||||||
|
class="block px-3 py-2 rounded-lg text-sm transition-colors {% if '/blog/clqms01/' == page.url %}bg-primary/10
|
||||||
|
text-primary font-medium border-l-2 border-primary{% else %}text-base-content/70 hover:bg-base-300
|
||||||
|
hover:text-base-content{% endif %}">
|
||||||
|
Overview
|
||||||
|
</a>
|
||||||
|
{% for post in collections.clqms %}
|
||||||
|
<a href="{{ post.url }}" class="block px-3 py-2 rounded-lg text-sm transition-colors {% if page.url == post.url %}bg-primary/10 text-primary font-medium border-l-2 border-primary{% else %}text-base-content/70 hover:bg-base-300 hover:text-base-content{% endif %}">
|
||||||
|
{{ post.data.title }}
|
||||||
|
</a>
|
||||||
|
{% endfor %}
|
||||||
|
</nav>
|
||||||
|
</div>
|
||||||
|
</aside>
|
||||||
|
<!-- Main Content -->
|
||||||
|
<main class="lg:w-3/4 min-w-0">
|
||||||
|
<article>
|
||||||
|
<!-- Post header -->
|
||||||
|
<header class="mb-10">
|
||||||
|
<div class="flex flex-wrap gap-2 mb-4">
|
||||||
|
{% for tag in tags %}
|
||||||
|
{% if tag != "post" and tag != "posts" and tag != "clqms" %}
|
||||||
|
<span class="badge badge-primary badge-outline">{{ tag }}</span>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
<h1 class="text-4xl md:text-5xl font-bold mb-4">{{ title }}</h1>
|
||||||
|
<div class="flex flex-wrap items-center gap-4 text-base-content/60">
|
||||||
|
<time datetime="{{ date | dateFormat('iso') }}" class="flex items-center gap-2">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"/>
|
||||||
|
</svg>
|
||||||
|
{{ date | dateFormat('full') }}
|
||||||
|
</time>
|
||||||
|
<span class="flex items-center gap-2">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z"/>
|
||||||
|
</svg>
|
||||||
|
{{ content | readingTime }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<!-- Post content -->
|
||||||
|
<div class="prose-custom max-w-none">
|
||||||
|
{{ content | safe }}
|
||||||
|
</div>
|
||||||
|
</article>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
BIN
src/assets/images/team.png
Normal file
BIN
src/assets/images/team.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 608 KiB |
BIN
src/assets/images/team_v2.png
Normal file
BIN
src/assets/images/team_v2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 681 KiB |
BIN
src/assets/images/team_v3.png
Normal file
BIN
src/assets/images/team_v3.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 360 KiB |
20
src/blog/clqms-frontend-stack.md
Normal file
20
src/blog/clqms-frontend-stack.md
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
---
|
||||||
|
title: "CLQMS: Frontend Stack Decision"
|
||||||
|
description: "Choosing SvelteKit 5 and Tailwind CSS for the client-side application."
|
||||||
|
date: 2025-12-22
|
||||||
|
order: 3
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
- clqms
|
||||||
|
layout: clqms-post.njk
|
||||||
|
---
|
||||||
|
|
||||||
|
# Frontend Stack
|
||||||
|
|
||||||
|
After evaluating various frameworks including React and Vue, we have decided to proceed with **SvelteKit 5** for the frontend dashboard.
|
||||||
|
|
||||||
|
## Why SvelteKit 5?
|
||||||
|
- **Runes:** The new reactivity system simplifies state management significantly.
|
||||||
|
- **Performance:** Compile-time optimizations result in smaller bundles and faster hydration.
|
||||||
|
- **Developer Experience:** Less boilerplate and a more intuitive syntax.
|
||||||
|
- **DaisyUI + Tailwind CSS:** Seamless integration for our design system.
|
||||||
19
src/blog/clqms-module-auth.md
Normal file
19
src/blog/clqms-module-auth.md
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
title: "CLQMS: JWT Authentication Module"
|
||||||
|
description: "Implementing secure authentication using JSON Web Tokens (JWT) for the API."
|
||||||
|
date: 2025-12-21
|
||||||
|
order: 2
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
- clqms
|
||||||
|
layout: clqms-post.njk
|
||||||
|
---
|
||||||
|
|
||||||
|
# Authentication Strategy
|
||||||
|
|
||||||
|
Security is paramount for medical data. We are implementing a stateless JWT authentication mechanism.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- **Access Tokens:** Short-lived (15 min)
|
||||||
|
- **Refresh Tokens:** Long-lived (7 days) with rotation
|
||||||
|
- **Role-Based Access Control (RBAC):** Granular permissions for Lab Techs, Managers, and Admins.
|
||||||
374
src/blog/clqms-review-Opus.md
Normal file
374
src/blog/clqms-review-Opus.md
Normal file
@ -0,0 +1,374 @@
|
|||||||
|
---
|
||||||
|
title: "Database Design Review: Claude Opus"
|
||||||
|
description: "A critical technical assessment of the current database schema."
|
||||||
|
date: 2025-12-12
|
||||||
|
order: 4
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
- clqms
|
||||||
|
layout: clqms-post.njk
|
||||||
|
---
|
||||||
|
|
||||||
|
# CLQMS Database Design Review Report
|
||||||
|
|
||||||
|
**Prepared by:** Claude OPUS
|
||||||
|
**Date:** December 12, 2025
|
||||||
|
**Subject:** Technical Assessment of Current Database Schema
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
This report presents a technical review of the CLQMS (Clinical Laboratory Quality Management System) database schema based on analysis of 16 migration files containing approximately 45+ tables. While the current design is functional, several critical issues have been identified that impact data integrity, development velocity, and long-term maintainability.
|
||||||
|
|
||||||
|
**Overall Assessment:** The application will function, but the design causes significant developer friction and will create increasing difficulties as the system scales.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Issues
|
||||||
|
|
||||||
|
### 1. Missing Foreign Key Constraints
|
||||||
|
|
||||||
|
**Severity:** 🔴 Critical
|
||||||
|
|
||||||
|
The database schema defines **zero foreign key constraints**. All relationships are implemented as integer columns without referential integrity.
|
||||||
|
|
||||||
|
| Impact | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| Data Integrity | Orphaned records when parent records are deleted |
|
||||||
|
| Data Corruption | Invalid references can be inserted without validation |
|
||||||
|
| Performance | Relationship logic must be enforced in application code |
|
||||||
|
| Debugging | Difficult to trace data lineage across tables |
|
||||||
|
|
||||||
|
**Example:** A patient can be deleted while their visits, orders, and results still reference the deleted `InternalPID`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Test Definition Tables: Broken Relationships
|
||||||
|
|
||||||
|
**Severity:** 🔴 Critical — Impacts API Development
|
||||||
|
|
||||||
|
This issue directly blocks backend development. The test definition system spans **6 tables** with unclear and broken relationships:
|
||||||
|
|
||||||
|
```
|
||||||
|
testdef → Master test catalog (company-wide definitions)
|
||||||
|
testdefsite → Site-specific test configurations
|
||||||
|
testdeftech → Technical settings (units, decimals, methods)
|
||||||
|
testdefcal → Calculated test formulas
|
||||||
|
testgrp → Test panel/profile groupings
|
||||||
|
testmap → Host/Client analyzer code mappings
|
||||||
|
```
|
||||||
|
|
||||||
|
#### The Core Problem: Missing Link Between `testdef` and `testdefsite`
|
||||||
|
|
||||||
|
**`testdef` table structure:**
|
||||||
|
```
|
||||||
|
TestID (PK), Parent, TestCode, TestName, Description, DisciplineID, Method, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
**`testdefsite` table structure:**
|
||||||
|
```
|
||||||
|
TestSiteID (PK), SiteID, TestSiteCode, TestSiteName, TestType, Description, ...
|
||||||
|
```
|
||||||
|
|
||||||
|
> [!CAUTION]
|
||||||
|
> **There is NO `TestID` column in `testdefsite`!**
|
||||||
|
> The relationship between master tests and site-specific configurations is undefined.
|
||||||
|
|
||||||
|
The assumed relationship appears to be matching `TestCode` = `TestSiteCode`, which is:
|
||||||
|
- **Fragile** — codes can change or differ
|
||||||
|
- **Non-performant** — string matching vs integer FK lookup
|
||||||
|
- **Undocumented** — developers must guess
|
||||||
|
|
||||||
|
#### Developer Impact
|
||||||
|
|
||||||
|
**Cannot create sample JSON payloads for API development.**
|
||||||
|
|
||||||
|
To return a complete test with all configurations, we need to JOIN:
|
||||||
|
```
|
||||||
|
testdef
|
||||||
|
→ testdefsite (HOW? No FK exists!)
|
||||||
|
→ testdeftech (via TestSiteID)
|
||||||
|
→ testdefcal (via TestSiteID)
|
||||||
|
→ testgrp (via TestSiteID)
|
||||||
|
→ testmap (via TestSiteID)
|
||||||
|
→ refnum/refthold/refvset/reftxt (via TestSiteID)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### What a Complete Test JSON Should Look Like
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"test": {
|
||||||
|
"id": 1,
|
||||||
|
"code": "GLU",
|
||||||
|
"name": "Glucose",
|
||||||
|
"discipline": "Chemistry",
|
||||||
|
"method": "Hexokinase",
|
||||||
|
"sites": [
|
||||||
|
{
|
||||||
|
"siteId": 1,
|
||||||
|
"siteName": "Main Lab",
|
||||||
|
"unit": "mg/dL",
|
||||||
|
"decimalPlaces": 0,
|
||||||
|
"referenceRange": { "low": 70, "high": 100 },
|
||||||
|
"equipment": [
|
||||||
|
{ "name": "Cobas 6000", "hostCode": "GLU" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"panelMemberships": ["BMP", "CMP"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### What We're Forced to Create Instead
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"testdef": { "TestID": 1, "TestCode": "GLU", "TestName": "Glucose" },
|
||||||
|
"testdefsite": { "TestSiteID": 1, "SiteID": 1, "TestSiteCode": "GLU" },
|
||||||
|
"testdeftech": { "TestTechID": 1, "TestSiteID": 1, "Unit1": "mg/dL" },
|
||||||
|
"refnum": { "RefNumID": 1, "TestSiteID": 1, "Low": 70, "High": 100 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Problem:** How does the API consumer know `testdef.TestID=1` connects to `testdefsite.TestSiteID=1`? The relationship is implicit and undocumented.
|
||||||
|
|
||||||
|
#### Recommended Fix
|
||||||
|
|
||||||
|
Add `TestID` foreign key to `testdefsite`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
ALTER TABLE testdefsite ADD COLUMN TestID INT NOT NULL;
|
||||||
|
ALTER TABLE testdefsite ADD CONSTRAINT fk_testdefsite_testdef
|
||||||
|
FOREIGN KEY (TestID) REFERENCES testdef(TestID);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Deeper Problem: Over-Engineered Architecture
|
||||||
|
|
||||||
|
> [!WARNING]
|
||||||
|
> **Even with `TestID` added, the test table design remains excessively complex and confusing.**
|
||||||
|
|
||||||
|
Adding the missing foreign key fixes the broken link, but does not address the fundamental over-engineering. To retrieve ONE complete test for ONE site, developers must JOIN across **10 tables**:
|
||||||
|
|
||||||
|
```
|
||||||
|
testdef ← "What is this test?"
|
||||||
|
└── testdefsite ← "Is it available at site X?"
|
||||||
|
└── testdeftech ← "What units/decimals at site X?"
|
||||||
|
└── testdefcal ← "Is it calculated at site X?"
|
||||||
|
└── testgrp ← "What panels is it in at site X?"
|
||||||
|
└── testmap ← "What analyzer codes at site X?"
|
||||||
|
└── refnum ← "Numeric reference ranges"
|
||||||
|
└── refthold ← "Threshold reference ranges"
|
||||||
|
└── refvset ← "Value set references"
|
||||||
|
└── reftxt ← "Text references"
|
||||||
|
```
|
||||||
|
|
||||||
|
**10 tables for one test at one site.**
|
||||||
|
|
||||||
|
This design assumes maximum flexibility (every site configures everything differently), but creates:
|
||||||
|
- **Excessive query complexity** — Simple lookups require 5+ JOINs
|
||||||
|
- **Developer confusion** — Which table holds which data?
|
||||||
|
- **Maintenance burden** — Changes ripple across multiple tables
|
||||||
|
- **API design friction** — Difficult to create clean, intuitive endpoints
|
||||||
|
|
||||||
|
#### What a Simpler Design Would Look Like
|
||||||
|
|
||||||
|
| Current (10 tables) | Proposed (4 tables) |
|
||||||
|
|---------------------|---------------------|
|
||||||
|
| `testdef` | `tests` |
|
||||||
|
| `testdefsite` + `testdeftech` + `testdefcal` | `test_configurations` |
|
||||||
|
| `refnum` + `refthold` + `refvset` + `reftxt` | `test_reference_ranges` (with `type` column) |
|
||||||
|
| `testgrp` | `test_panel_members` |
|
||||||
|
| `testmap` | (merged into `test_configurations`) |
|
||||||
|
|
||||||
|
#### Recommendation
|
||||||
|
|
||||||
|
For long-term maintainability, consider a phased refactoring:
|
||||||
|
|
||||||
|
1. **Phase 1:** Add `TestID` FK (immediate unblock)
|
||||||
|
2. **Phase 2:** Create database VIEWs that flatten the structure for API consumption
|
||||||
|
3. **Phase 3:** Evaluate consolidation of `testdefsite`/`testdeftech`/`testdefcal` into single table
|
||||||
|
4. **Phase 4:** Consolidate 4 reference range tables into one with discriminator column
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Data Type Mismatches Across Tables
|
||||||
|
|
||||||
|
**Severity:** 🔴 Critical
|
||||||
|
|
||||||
|
The same logical field uses different data types in different tables, making JOINs impossible.
|
||||||
|
|
||||||
|
| Field | Table A | Type | Table B | Type |
|
||||||
|
|-------|---------|------|---------|------|
|
||||||
|
| `SiteID` | `ordertest` | `VARCHAR(15)` | `site` | `INT` |
|
||||||
|
| `OccupationID` | `contactdetail` | `VARCHAR(50)` | `occupation` | `INT` |
|
||||||
|
| `SpcType` | `testdeftech` | `INT` | `refnum` | `VARCHAR(10)` |
|
||||||
|
| `Country` | `patient` | `INT` | `account` | `VARCHAR(50)` |
|
||||||
|
| `City` | `locationaddress` | `INT` | `account` | `VARCHAR(150)` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## High-Priority Issues
|
||||||
|
|
||||||
|
### 4. Inconsistent Naming Conventions
|
||||||
|
|
||||||
|
| Issue | Examples |
|
||||||
|
|-------|----------|
|
||||||
|
| Mixed case styles | `InternalPID`, `CreateDate` vs `AreaCode`, `Parent` |
|
||||||
|
| Cryptic abbreviations | `patatt`, `patcom`, `patidt`, `patvisitadt` |
|
||||||
|
| Inconsistent ID naming | `InternalPID`, `PatientID`, `PatIdtID`, `PatComID` |
|
||||||
|
| Unclear field names | `VSet`, `VValue`, `AspCnt`, `ME`, `DIDType` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. Inconsistent Soft-Delete Strategy
|
||||||
|
|
||||||
|
Multiple date fields used inconsistently:
|
||||||
|
|
||||||
|
| Table | Fields Used |
|
||||||
|
|-------|-------------|
|
||||||
|
| `patient` | `CreateDate`, `DelDate` |
|
||||||
|
| `patvisit` | `CreateDate`, `EndDate`, `ArchivedDate`, `DelDate` |
|
||||||
|
| `patcom` | `CreateDate`, `EndDate` |
|
||||||
|
| `testdef` | `CreateDate`, `EndDate` |
|
||||||
|
|
||||||
|
**No documented standard** for determining record state (active/deleted/archived/ended).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. Duplicate Log Table Design
|
||||||
|
|
||||||
|
Three nearly identical audit tables exist:
|
||||||
|
- `patreglog`
|
||||||
|
- `patvisitlog`
|
||||||
|
- `specimenlog`
|
||||||
|
|
||||||
|
**Recommendation:** Consolidate into single `audit_log` table.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Medium Priority Issues
|
||||||
|
|
||||||
|
### 7. Redundant Data Storage
|
||||||
|
|
||||||
|
| Table | Redundancy |
|
||||||
|
|-------|------------|
|
||||||
|
| `patres` | Stores both `InternalSID` AND `SID` |
|
||||||
|
| `patres` | Stores both `TestSiteID` AND `TestSiteCode` |
|
||||||
|
| `patrestatus` | Duplicates `SID` from parent table |
|
||||||
|
|
||||||
|
### 8. Incomplete Table Designs
|
||||||
|
|
||||||
|
**`patrelation` table:** Missing `RelatedPatientID`, `RelationType`
|
||||||
|
**`users` table:** Missing `email`, `created_at`, `updated_at`, `status`, `last_login`
|
||||||
|
|
||||||
|
### 9. Migration Script Bugs
|
||||||
|
|
||||||
|
| File | Issue |
|
||||||
|
|------|-------|
|
||||||
|
| `Specimen.php` | Creates `specimen`, drops `specimens` |
|
||||||
|
| `CRMOrganizations.php` | Creates `account`/`site`, drops `accounts`/`sites` |
|
||||||
|
| `PatRes.php` | Drops non-existent `patrestech` table |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendations
|
||||||
|
|
||||||
|
### Immediate (Sprint 1-2)
|
||||||
|
1. **Add `TestID` to `testdefsite`** — Unblocks API development
|
||||||
|
2. **Fix migration script bugs** — Correct table names in `down()` methods
|
||||||
|
3. **Document existing relationships** — Create ERD with assumed relationships
|
||||||
|
|
||||||
|
### Short-Term (Sprint 3-6)
|
||||||
|
4. **Add foreign key constraints** — Prioritize patient → visit → order → result chain
|
||||||
|
5. **Fix data type mismatches** — Create migration scripts for type alignment
|
||||||
|
6. **Standardize soft-delete** — Use `deleted_at` only, everywhere
|
||||||
|
|
||||||
|
### Medium-Term (Sprint 7-12)
|
||||||
|
7. **Consolidate audit logs** — Single polymorphic audit table
|
||||||
|
8. **Normalize addresses** — Single `addresses` table
|
||||||
|
9. **Rename cryptic columns** — Document and rename for clarity
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Appendix: Tables by Migration
|
||||||
|
|
||||||
|
| Migration | Tables |
|
||||||
|
|-----------|--------|
|
||||||
|
| PatientReg | `patient`, `patatt`, `patcom`, `patidt`, `patreglog`, `patrelation` |
|
||||||
|
| PatVisit | `patvisit`, `patdiag`, `patvisitadt`, `patvisitlog` |
|
||||||
|
| Location | `location`, `locationaddress` |
|
||||||
|
| Users | `users` |
|
||||||
|
| Contact | `contact`, `contactdetail`, `occupation`, `medicalspecialty` |
|
||||||
|
| ValueSet | `valueset`, `valuesetdef` |
|
||||||
|
| Counter | `counter` |
|
||||||
|
| Specimen | `containerdef`, `specimen`, `specimenstatus`, `specimencollection`, `specimenprep`, `specimenlog` |
|
||||||
|
| OrderTest | `ordertest`, `ordercom`, `orderatt`, `orderstatus` |
|
||||||
|
| Test | `testdef`, `testdefsite`, `testdeftech`, `testdefcal`, `testgrp`, `testmap` |
|
||||||
|
| RefRange | `refnum`, `refthold`, `refvset`, `reftxt` |
|
||||||
|
| CRMOrganizations | `account`, `site` |
|
||||||
|
| Organization | `discipline`, `department`, `workstation` |
|
||||||
|
| Equipment | `equipmentlist`, `comparameters`, `devicelist` |
|
||||||
|
| AreaGeo | `areageo` |
|
||||||
|
| PatRes | `patres`, `patresflag`, `patrestatus`, `flagdef` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Process Improvement: Database Design Ownership
|
||||||
|
|
||||||
|
### Current Challenge
|
||||||
|
|
||||||
|
The issues identified in this report share a common theme: **disconnect between database structure and API consumption patterns**. Many design decisions optimize for theoretical flexibility rather than practical developer workflow.
|
||||||
|
|
||||||
|
This is not a critique of intent — the design shows careful thought about multi-site configurability. However, when database schemas are designed in isolation from the developers who build APIs on top of them, friction inevitably occurs.
|
||||||
|
|
||||||
|
### Industry Best Practice
|
||||||
|
|
||||||
|
Modern software development teams typically follow this ownership model:
|
||||||
|
|
||||||
|
| Role | Responsibility |
|
||||||
|
|------|---------------|
|
||||||
|
| **Product/Business** | Define what data needs to exist (requirements) |
|
||||||
|
| **Backend Developers** | Design how data is structured (schema design) |
|
||||||
|
| **Backend Developers** | Implement APIs that consume the schema |
|
||||||
|
| **DBA (if applicable)** | Optimize performance, manage infrastructure |
|
||||||
|
|
||||||
|
The rationale is simple: **those who consume the schema daily are best positioned to design it**.
|
||||||
|
|
||||||
|
### Benefits of Developer-Owned Schema Design
|
||||||
|
|
||||||
|
| Benefit | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| **API-First Thinking** | Tables designed with JSON output in mind |
|
||||||
|
| **Faster Iterations** | Schema changes driven by real implementation needs |
|
||||||
|
| **Reduced Friction** | No translation layer between "what was designed" and "what we need" |
|
||||||
|
| **Better Documentation** | Developers document what they build |
|
||||||
|
| **Ownership & Accountability** | Single team owns the full stack |
|
||||||
|
|
||||||
|
### Recommendation
|
||||||
|
|
||||||
|
Consider transitioning database schema design ownership to the backend development team for future modules. This would involve:
|
||||||
|
|
||||||
|
1. **Requirements Gathering** — Business/product defines data needs
|
||||||
|
2. **Schema Proposal** — Backend team designs tables based on API requirements
|
||||||
|
3. **Review** — Technical review with stakeholders before implementation
|
||||||
|
4. **Implementation** — Backend team executes migrations and builds APIs
|
||||||
|
|
||||||
|
This approach aligns with how most modern development teams operate and would prevent the types of issues found in this review.
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> This recommendation is not about past decisions, but about optimizing future development velocity. The backend team's daily work with queries, JOINs, and API responses gives them unique insight into practical schema design.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
The test definition table structure is the most immediate blocker for development. Without a clear relationship between `testdef` and `testdefsite`, creating coherent API responses is not feasible. This should be prioritized in Sprint 1.
|
||||||
|
|
||||||
|
The broader issues (missing FKs, type mismatches) represent significant technical debt that will compound over time. Investment in database refactoring now prevents costly incidents later.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Report generated from migration file analysis in `app/Database/Migrations/`*
|
||||||
1305
src/blog/clqms-review-Sonnet.md
Normal file
1305
src/blog/clqms-review-Sonnet.md
Normal file
File diff suppressed because it is too large
Load Diff
21
src/blog/clqms-update-v1.md
Normal file
21
src/blog/clqms-update-v1.md
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
---
|
||||||
|
title: "CLQMS: v1.0 Architecture Finalized"
|
||||||
|
description: "The core architecture for the CLQMS system has been finalized, featuring a modular API design."
|
||||||
|
date: 2025-12-20
|
||||||
|
order: 1
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
- clqms
|
||||||
|
layout: clqms-post.njk
|
||||||
|
---
|
||||||
|
|
||||||
|
# Architecture Overview
|
||||||
|
|
||||||
|
We have finalized the v1.0 architecture for the Clinical Laboratory Quality Management System (CLQMS).
|
||||||
|
|
||||||
|
## Key Decisions
|
||||||
|
|
||||||
|
- **Event-Driven:** We are adopting an event-driven architecture for test result processing.
|
||||||
|
- **Micro-kernel:** The core system will remain small, with modules loaded as needed.
|
||||||
|
|
||||||
|
Next steps involve setting up the continuous integration pipeline.
|
||||||
157
src/blog/clqms-wst-concept.md
Normal file
157
src/blog/clqms-wst-concept.md
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
---
|
||||||
|
title: "Project Pandaria: Next-Gen LIS Architecture"
|
||||||
|
description: "An offline-first, event-driven architecture concept for the CLQMS."
|
||||||
|
date: 2025-12-19
|
||||||
|
order: 6
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
- clqms
|
||||||
|
layout: clqms-post.njk
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 💀 Pain vs. 🛡️ Solution
|
||||||
|
|
||||||
|
### 🚩 Problem 1: "The Server is Dead!"
|
||||||
|
> **The Pain:** When the internet cuts or the server crashes, the entire lab stops. Patients wait, doctors get angry.
|
||||||
|
|
||||||
|
**🛡️ The Solution: "Offline-First Mode"**
|
||||||
|
The workstation keeps working 100% offline. It has a local brain (database). Patients never know the internet is down.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🚩 Problem 2: "Data Vanished?"
|
||||||
|
> **The Pain:** We pushed data, the network blinked, and the sample disappeared. We have to re-scan manually.
|
||||||
|
|
||||||
|
**🛡️ The Solution: "The Outbox Guarantee"**
|
||||||
|
Data is treated like Registered Mail. It stays in a safe SQL "Outbox" until the workstation signs a receipt (ACK) confirming it is saved.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🚩 Problem 3: "Spaghetti Code"
|
||||||
|
> **The Pain:** Adding a new machine (like Mindray) means hacking the core LIS code with endless `if-else` statements.
|
||||||
|
|
||||||
|
**🛡️ The Solution: "Universal Adapters"**
|
||||||
|
Every machine gets a simple plugin (Driver). The Core System stays clean, modular, and untouched.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 🚩 Problem 4: "Inconsistent Results"
|
||||||
|
> **The Pain:** One machine says `WBC`, another says `Leukocytes`. The Database is a mess of different codes.
|
||||||
|
|
||||||
|
**🛡️ The Solution: "The Translator"**
|
||||||
|
A built-in dictionary auto-translates everything to Standard English (e.g., `WBC`) before it ever touches the database.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 🏗️ System Architecture: The "Edge" Concept
|
||||||
|
|
||||||
|
We are moving from a **Dependent** model (dumb terminal) to an **Empowered** model (Edge Computing).
|
||||||
|
|
||||||
|
### The "Core" (Central Server)
|
||||||
|
* **Role:** The "Hippocampus" (Long-term Memory).
|
||||||
|
* **Stack:** CodeIgniter 4 + MySQL.
|
||||||
|
* **Responsibilities:**
|
||||||
|
* Billing & Financials (Single Source of Truth).
|
||||||
|
* Permanent Patient History.
|
||||||
|
* API Gateway for external apps (Mobile, Website).
|
||||||
|
* Administrator Dashboard.
|
||||||
|
|
||||||
|
### The "Edge" (Smart Workstation)
|
||||||
|
* **Role:** The "Cortex" (Immediate Processing).
|
||||||
|
* **Stack:** Node.js (Electron) + SQLite.
|
||||||
|
* **Responsibilities:**
|
||||||
|
* **Hardware I/O:** Speaking directly to RS232/TCP ports.
|
||||||
|
* **Hot Caching:** Keeping the last 7 days of active orders locally.
|
||||||
|
* **Logic Engine:** Validating results against reference ranges *before* syncing.
|
||||||
|
|
||||||
|
> **Key Difference:** The Workstation no longer asks "Can I work?" It assumes it can work. It treats the server as a "Sync Partner," not a "Master." If the internet dies, the Edge keeps processing samples, printing labels, and validating results without a hiccup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 🔌 The "Universal Adapter" (Hardware Layer)
|
||||||
|
|
||||||
|
We use the **Adapter Design Pattern** to isolate hardware chaos from our clean business logic.
|
||||||
|
|
||||||
|
### The Problem: "The Tower of Babel"
|
||||||
|
Every manufacturer speaks a proprietary dialect.
|
||||||
|
* **Sysmex:** Uses ASTM protocols with checksums.
|
||||||
|
* **Roche:** Uses custom HL7 variants.
|
||||||
|
* **Mindray:** Often uses raw hex streams.
|
||||||
|
|
||||||
|
### The Fix: "Drivers as Plugins"
|
||||||
|
The Workstation loads a specific `.js` file (The Driver) for each connected machine. This driver has one job: **Normalization.**
|
||||||
|
|
||||||
|
#### Example: ASTM to JSON
|
||||||
|
**Raw Input (Alien Language):**
|
||||||
|
`P|1||12345||Smith^John||19800101|M|||||`
|
||||||
|
`R|1|^^^WBC|10.5|10^3/uL|4.0-11.0|N||F||`
|
||||||
|
|
||||||
|
**Normalized Output (clean JSON):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"test_code": "WBC",
|
||||||
|
"value": 10.5,
|
||||||
|
"unit": "10^3/uL",
|
||||||
|
"flag": "Normal",
|
||||||
|
"timestamp": "2025-12-19T10:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Benefit: "Hot-Swappable Labs"
|
||||||
|
Buying a new machine? You don't need to obscurely patch the `LISSender.exe`. You just drop in `driver-sysmex-xn1000.js` into the `plugins/` folder, and the Edge Workstation instantly learns how to speak Sysmex.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 🗣️ The "Translator" (Data Mapping)
|
||||||
|
|
||||||
|
Machines are stubborn. They send whatever test codes they want (`WBC`, `Leukocytes`, `W.B.C`, `White_Cells`). If we save these directly, our database becomes a swamp.
|
||||||
|
|
||||||
|
### The Solution: "Local Dictionary & Rules Engine"
|
||||||
|
Before data is saved to SQLite, it passes through the **Translator**.
|
||||||
|
|
||||||
|
1. **Alias Matching:**
|
||||||
|
* The dictionary knows that `W.B.C` coming from *Machine A* actually means `WBC_TOTAL`.
|
||||||
|
* It renames the key instantly.
|
||||||
|
|
||||||
|
2. **Unit Conversion (Math Layer):**
|
||||||
|
* *Machine A* sends Hemoglobin in `g/dL` (e.g., 14.5).
|
||||||
|
* *Our Standard* is `g/L` (e.g., 145).
|
||||||
|
* **The Rule:** `Apply: Value * 10`.
|
||||||
|
* The translator automatically mathematical normalized the result.
|
||||||
|
|
||||||
|
This ensures that our Analytics Dashboard sees **clean, comparable data** regardless of whether it came from a 10-year-old machine or a brand new one.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 📨 The "Registered Mail" Sync (Redis + Outbox)
|
||||||
|
|
||||||
|
We are banning the word "Polling" (checking every 5 seconds). It's inefficient and scary. We are switching to **Events** using **Redis**.
|
||||||
|
|
||||||
|
### 🤔 What is Redis?
|
||||||
|
Think of **MySQL** as a filing cabinet (safe, permanent, but slow to open).
|
||||||
|
Think of **Redis** as a **loudspeaker system** (instant, in-memory, very fast).
|
||||||
|
|
||||||
|
We use Redis specifically for its **Pub/Sub (Publish/Subscribe)** feature. It lets us "broadcast" a message to all connected workstations instantly without writing to a disk.
|
||||||
|
|
||||||
|
### 🔄 How the Flow Works:
|
||||||
|
|
||||||
|
1. **👨⚕️ Order Created:** The Doctor saves an order on the Server.
|
||||||
|
2. **📮 The Outbox:** The server puts a copy of the order in a special SQL table called `outbox_queue`.
|
||||||
|
3. **🔔 The Bell (Redis):** The server "shouts" into the Redis loudspeaker: *"New mail for Lab 1!"*.
|
||||||
|
4. **📥 Delivery:** The Workstation (listening to Redis) hears the shout instantly. It then goes to the SQL Outbox to download the actual heavy data.
|
||||||
|
5. **✍️ The Signature (ACK):** The Workstation sends a digital signature back: *"I have received and saved Order #123."*
|
||||||
|
6. **✅ Done:** Only *then* does the server delete the message from the Outbox.
|
||||||
|
|
||||||
|
**Safety Net & Self-Healing:**
|
||||||
|
* **Redis is just the doorbell:** If the workstation is offline and misses the shout, it doesn't matter.
|
||||||
|
* **SQL is the mailbox:** The message sits safely in the `outbox_queue` table indefinitely.
|
||||||
|
* **Recovery:** When the Workstation turns back on, it automatically asks: *"Did I miss anything?"* and downloads all pending items from the SQL Outbox. **Zero data loss, even if the notification is lost.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 🏆 Summary: Why We Win
|
||||||
|
|
||||||
|
* **Reliability:** 🛡️ 100% Uptime for the Lab.
|
||||||
|
* **Speed:** ⚡ Instant response times (Local Database is faster than Cloud).
|
||||||
|
* **Sanity:** 🧘 No more panic attacks when the internet provider fails.
|
||||||
|
* **Future Proof:** 🚀 Ready for any new machine connection in the future.
|
||||||
432
src/blog/clqms-wst-database.md
Normal file
432
src/blog/clqms-wst-database.md
Normal file
@ -0,0 +1,432 @@
|
|||||||
|
---
|
||||||
|
title: "Edge Workstation: SQLite Database Schema"
|
||||||
|
description: "Database design for the offline-first smart workstation."
|
||||||
|
date: 2025-12-19
|
||||||
|
order: 7
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
- clqms
|
||||||
|
- database
|
||||||
|
layout: clqms-post.njk
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the **SQLite database schema** for the Edge Workstation — the local "brain" that enables **100% offline operation** for lab technicians.
|
||||||
|
|
||||||
|
> **Stack:** Node.js (Electron) + SQLite
|
||||||
|
> **Role:** The "Cortex" — Immediate Processing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📊 Entity Relationship Diagram
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────┐ ┌──────────────┐
|
||||||
|
│ orders │────────<│ order_tests │
|
||||||
|
└─────────────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────┐ ┌──────────────┐
|
||||||
|
│ machines │────────<│ results │
|
||||||
|
└─────────────┘ └──────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ test_dictionary │ (The Translator)
|
||||||
|
└─────────────────┘
|
||||||
|
|
||||||
|
┌───────────────┐ ┌───────────────┐
|
||||||
|
│ outbox_queue │ │ inbox_queue │
|
||||||
|
└───────────────┘ └───────────────┘
|
||||||
|
(Push to Server) (Pull from Server)
|
||||||
|
|
||||||
|
┌───────────────┐ ┌───────────────┐
|
||||||
|
│ sync_log │ │ config │
|
||||||
|
└───────────────┘ └───────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗂️ Table Definitions
|
||||||
|
|
||||||
|
### 1. `orders` — Cached Patient Orders
|
||||||
|
|
||||||
|
Orders downloaded from the Core Server. Keeps the **last 7 days** for offline processing.
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `id` | INTEGER | Primary key (local) |
|
||||||
|
| `server_order_id` | TEXT | Original ID from Core Server |
|
||||||
|
| `patient_id` | TEXT | Patient identifier |
|
||||||
|
| `patient_name` | TEXT | Patient full name |
|
||||||
|
| `patient_dob` | DATE | Date of birth |
|
||||||
|
| `patient_gender` | TEXT | M, F, or O |
|
||||||
|
| `order_date` | DATETIME | When order was created |
|
||||||
|
| `priority` | TEXT | `stat`, `routine`, `urgent` |
|
||||||
|
| `status` | TEXT | `pending`, `in_progress`, `completed`, `cancelled` |
|
||||||
|
| `barcode` | TEXT | Sample barcode |
|
||||||
|
| `synced_at` | DATETIME | Last sync timestamp |
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE orders (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
server_order_id TEXT UNIQUE NOT NULL,
|
||||||
|
patient_id TEXT NOT NULL,
|
||||||
|
patient_name TEXT NOT NULL,
|
||||||
|
patient_dob DATE,
|
||||||
|
patient_gender TEXT CHECK(patient_gender IN ('M', 'F', 'O')),
|
||||||
|
order_date DATETIME NOT NULL,
|
||||||
|
priority TEXT DEFAULT 'routine' CHECK(priority IN ('stat', 'routine', 'urgent')),
|
||||||
|
status TEXT DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed', 'cancelled')),
|
||||||
|
barcode TEXT,
|
||||||
|
notes TEXT,
|
||||||
|
synced_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. `order_tests` — Requested Tests per Order
|
||||||
|
|
||||||
|
Each order can have multiple tests (CBC, Urinalysis, etc.)
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `id` | INTEGER | Primary key |
|
||||||
|
| `order_id` | INTEGER | FK to orders |
|
||||||
|
| `test_code` | TEXT | Standardized code (e.g., `WBC_TOTAL`) |
|
||||||
|
| `test_name` | TEXT | Display name |
|
||||||
|
| `status` | TEXT | `pending`, `processing`, `completed`, `failed` |
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE order_tests (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
order_id INTEGER NOT NULL,
|
||||||
|
test_code TEXT NOT NULL,
|
||||||
|
test_name TEXT NOT NULL,
|
||||||
|
status TEXT DEFAULT 'pending',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. `results` — Machine Output (Normalized)
|
||||||
|
|
||||||
|
Results from lab machines, **already translated** to standard format by The Translator.
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `id` | INTEGER | Primary key |
|
||||||
|
| `order_test_id` | INTEGER | FK to order_tests |
|
||||||
|
| `machine_id` | INTEGER | FK to machines |
|
||||||
|
| `test_code` | TEXT | Standardized test code |
|
||||||
|
| `value` | REAL | Numeric result |
|
||||||
|
| `unit` | TEXT | Standardized unit |
|
||||||
|
| `flag` | TEXT | `L`, `N`, `H`, `LL`, `HH`, `A` |
|
||||||
|
| `raw_value` | TEXT | Original value from machine |
|
||||||
|
| `raw_unit` | TEXT | Original unit from machine |
|
||||||
|
| `raw_test_code` | TEXT | Original code before translation |
|
||||||
|
| `validated` | BOOLEAN | Has been reviewed by tech? |
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE results (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
order_test_id INTEGER,
|
||||||
|
machine_id INTEGER,
|
||||||
|
test_code TEXT NOT NULL,
|
||||||
|
value REAL NOT NULL,
|
||||||
|
unit TEXT NOT NULL,
|
||||||
|
reference_low REAL,
|
||||||
|
reference_high REAL,
|
||||||
|
flag TEXT CHECK(flag IN ('L', 'N', 'H', 'LL', 'HH', 'A')),
|
||||||
|
raw_value TEXT,
|
||||||
|
raw_unit TEXT,
|
||||||
|
raw_test_code TEXT,
|
||||||
|
validated BOOLEAN DEFAULT 0,
|
||||||
|
validated_by TEXT,
|
||||||
|
validated_at DATETIME,
|
||||||
|
machine_timestamp DATETIME,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (order_test_id) REFERENCES order_tests(id),
|
||||||
|
FOREIGN KEY (machine_id) REFERENCES machines(id)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. `outbox_queue` — The Registered Mail 📮
|
||||||
|
|
||||||
|
Data waits here until the Core Server sends an **ACK (acknowledgment)**. This is the heart of our **zero data loss** guarantee.
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `id` | INTEGER | Primary key |
|
||||||
|
| `event_type` | TEXT | `result_created`, `result_validated`, etc. |
|
||||||
|
| `payload` | TEXT | JSON data to sync |
|
||||||
|
| `target_entity` | TEXT | `results`, `orders`, etc. |
|
||||||
|
| `priority` | INTEGER | 1 = highest, 10 = lowest |
|
||||||
|
| `retry_count` | INTEGER | Number of failed attempts |
|
||||||
|
| `status` | TEXT | `pending`, `processing`, `sent`, `acked`, `failed` |
|
||||||
|
| `acked_at` | DATETIME | When server confirmed receipt |
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE outbox_queue (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
event_type TEXT NOT NULL,
|
||||||
|
payload TEXT NOT NULL,
|
||||||
|
target_entity TEXT,
|
||||||
|
target_id INTEGER,
|
||||||
|
priority INTEGER DEFAULT 5,
|
||||||
|
retry_count INTEGER DEFAULT 0,
|
||||||
|
max_retries INTEGER DEFAULT 5,
|
||||||
|
last_error TEXT,
|
||||||
|
status TEXT DEFAULT 'pending',
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
sent_at DATETIME,
|
||||||
|
acked_at DATETIME
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Flow:** Data enters as `pending` → moves to `sent` when transmitted → becomes `acked` when server confirms → deleted after cleanup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 5. `inbox_queue` — Messages from Server 📥
|
||||||
|
|
||||||
|
Incoming orders/updates from Core Server waiting to be processed locally.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE inbox_queue (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
server_message_id TEXT UNIQUE NOT NULL,
|
||||||
|
event_type TEXT NOT NULL,
|
||||||
|
payload TEXT NOT NULL,
|
||||||
|
status TEXT DEFAULT 'pending',
|
||||||
|
error_message TEXT,
|
||||||
|
received_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
processed_at DATETIME
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 6. `machines` — Connected Lab Equipment 🔌
|
||||||
|
|
||||||
|
Registry of all connected analyzers.
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `id` | INTEGER | Primary key |
|
||||||
|
| `name` | TEXT | "Sysmex XN-1000" |
|
||||||
|
| `driver_file` | TEXT | "driver-sysmex-xn1000.js" |
|
||||||
|
| `connection_type` | TEXT | `RS232`, `TCP`, `USB`, `FILE` |
|
||||||
|
| `connection_config` | TEXT | JSON config |
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE machines (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
manufacturer TEXT,
|
||||||
|
model TEXT,
|
||||||
|
serial_number TEXT,
|
||||||
|
driver_file TEXT NOT NULL,
|
||||||
|
connection_type TEXT CHECK(connection_type IN ('RS232', 'TCP', 'USB', 'FILE')),
|
||||||
|
connection_config TEXT,
|
||||||
|
is_active BOOLEAN DEFAULT 1,
|
||||||
|
last_communication DATETIME,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example config:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"port": "COM3",
|
||||||
|
"baudRate": 9600,
|
||||||
|
"dataBits": 8,
|
||||||
|
"parity": "none"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 7. `test_dictionary` — The Translator 📖
|
||||||
|
|
||||||
|
This table solves the **"WBC vs Leukocytes"** problem. It maps machine-specific codes to our standard codes.
|
||||||
|
|
||||||
|
| Column | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `machine_id` | INTEGER | FK to machines (NULL = universal) |
|
||||||
|
| `raw_code` | TEXT | What machine sends: `W.B.C`, `Leukocytes` |
|
||||||
|
| `standard_code` | TEXT | Our standard: `WBC_TOTAL` |
|
||||||
|
| `unit_conversion_factor` | REAL | Math conversion (e.g., 10 for g/dL → g/L) |
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE test_dictionary (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
machine_id INTEGER,
|
||||||
|
raw_code TEXT NOT NULL,
|
||||||
|
standard_code TEXT NOT NULL,
|
||||||
|
standard_name TEXT NOT NULL,
|
||||||
|
unit_conversion_factor REAL DEFAULT 1.0,
|
||||||
|
raw_unit TEXT,
|
||||||
|
standard_unit TEXT,
|
||||||
|
reference_low REAL,
|
||||||
|
reference_high REAL,
|
||||||
|
is_active BOOLEAN DEFAULT 1,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
FOREIGN KEY (machine_id) REFERENCES machines(id),
|
||||||
|
UNIQUE(machine_id, raw_code)
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Translation Example:**
|
||||||
|
|
||||||
|
| Machine | Raw Code | Standard Code | Conversion |
|
||||||
|
|---------|----------|---------------|------------|
|
||||||
|
| Sysmex | `WBC` | `WBC_TOTAL` | × 1.0 |
|
||||||
|
| Mindray | `Leukocytes` | `WBC_TOTAL` | × 1.0 |
|
||||||
|
| Sysmex | `HGB` (g/dL) | `HGB` (g/L) | × 10 |
|
||||||
|
| Universal | `W.B.C` | `WBC_TOTAL` | × 1.0 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 8. `sync_log` — Audit Trail 📜
|
||||||
|
|
||||||
|
Track all sync activities for debugging and recovery.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE sync_log (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
direction TEXT CHECK(direction IN ('push', 'pull')),
|
||||||
|
event_type TEXT NOT NULL,
|
||||||
|
entity_type TEXT,
|
||||||
|
entity_id INTEGER,
|
||||||
|
server_response_code INTEGER,
|
||||||
|
success BOOLEAN,
|
||||||
|
duration_ms INTEGER,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 9. `config` — Local Settings ⚙️
|
||||||
|
|
||||||
|
Key-value store for workstation-specific settings.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
CREATE TABLE config (
|
||||||
|
key TEXT PRIMARY KEY,
|
||||||
|
value TEXT,
|
||||||
|
description TEXT,
|
||||||
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Default values:**
|
||||||
|
|
||||||
|
| Key | Value | Description |
|
||||||
|
|-----|-------|-------------|
|
||||||
|
| `workstation_id` | `LAB-WS-001` | Unique identifier |
|
||||||
|
| `server_url` | `https://api.clqms.com` | Core Server endpoint |
|
||||||
|
| `cache_days` | `7` | Days to keep cached orders |
|
||||||
|
| `auto_validate` | `false` | Auto-validate normal results |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 How the Sync Works
|
||||||
|
|
||||||
|
### Outbox Pattern (Push)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Lab Result │
|
||||||
|
│ Generated │
|
||||||
|
└────────┬────────┘
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Save to SQLite │
|
||||||
|
│ + Outbox │
|
||||||
|
└────────┬────────┘
|
||||||
|
▼
|
||||||
|
┌─────────────────┐ ┌─────────────────┐
|
||||||
|
│ Send to Server │────>│ Core Server │
|
||||||
|
└────────┬────────┘ └────────┬────────┘
|
||||||
|
│ │
|
||||||
|
│ ◄──── ACK ─────────┘
|
||||||
|
▼
|
||||||
|
┌─────────────────┐
|
||||||
|
│ Mark as 'acked' │
|
||||||
|
│ in Outbox │
|
||||||
|
└─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
### Self-Healing Recovery
|
||||||
|
|
||||||
|
If the workstation was offline and missed Redis notifications:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// On startup, ask: "Did I miss anything?"
|
||||||
|
async function recoverMissedMessages() {
|
||||||
|
const lastSync = await db.get("SELECT value FROM config WHERE key = 'last_sync'");
|
||||||
|
const missed = await api.get(`/outbox/pending?since=${lastSync}`);
|
||||||
|
|
||||||
|
for (const message of missed) {
|
||||||
|
await inbox.insert(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Sample Data
|
||||||
|
|
||||||
|
### Sample Machine Registration
|
||||||
|
|
||||||
|
```sql
|
||||||
|
INSERT INTO machines (name, manufacturer, driver_file, connection_type, connection_config)
|
||||||
|
VALUES ('Sysmex XN-1000', 'Sysmex', 'driver-sysmex-xn1000.js', 'RS232',
|
||||||
|
'{"port": "COM3", "baudRate": 9600}');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample Dictionary Entry
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Mindray calls WBC "Leukocytes" — we translate it!
|
||||||
|
INSERT INTO test_dictionary (machine_id, raw_code, standard_code, standard_name, raw_unit, standard_unit)
|
||||||
|
VALUES (2, 'Leukocytes', 'WBC_TOTAL', 'White Blood Cell Count', 'x10^9/L', '10^3/uL');
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sample Result with Translation
|
||||||
|
|
||||||
|
```sql
|
||||||
|
-- Machine sent: { code: "Leukocytes", value: 8.5, unit: "x10^9/L" }
|
||||||
|
-- After translation:
|
||||||
|
INSERT INTO results (test_code, value, unit, flag, raw_test_code, raw_value, raw_unit)
|
||||||
|
VALUES ('WBC_TOTAL', 8.5, '10^3/uL', 'N', 'Leukocytes', '8.5', 'x10^9/L');
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏆 Key Benefits
|
||||||
|
|
||||||
|
| Feature | Benefit |
|
||||||
|
|---------|---------|
|
||||||
|
| **Offline-First** | Lab never stops, even without internet |
|
||||||
|
| **Outbox Queue** | Zero data loss guarantee |
|
||||||
|
| **Test Dictionary** | Clean, standardized data from any machine |
|
||||||
|
| **Inbox Queue** | Never miss orders, even if offline |
|
||||||
|
| **Sync Log** | Full audit trail for debugging |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📁 Full SQL Migration
|
||||||
|
|
||||||
|
The complete SQL migration file is available at:
|
||||||
|
📄 [`docs/examples/edge_workstation.sql`](/docs/examples/edge_workstation.sql)
|
||||||
77
src/blog/clqms01.md
Normal file
77
src/blog/clqms01.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
---
|
||||||
|
title: CLQMS (Clinical Laboratory Quality Management System)
|
||||||
|
description: The core backend engine for modern clinical laboratory workflows.
|
||||||
|
date: 2025-12-19
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
- template
|
||||||
|
layout: clqms-post.njk
|
||||||
|
---
|
||||||
|
|
||||||
|
# CLQMS (Clinical Laboratory Quality Management System)
|
||||||
|
|
||||||
|
> **The core backend engine for modern clinical laboratory workflows.**
|
||||||
|
|
||||||
|
CLQMS is a robust, mission-critical API suite designed to streamline laboratory operations, ensure data integrity, and manage complex diagnostic workflows. Built on a foundation of precision and regulatory compliance, this system handles everything from patient registration to high-throughput test resulting.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏛️ Core Architecture & Design
|
||||||
|
|
||||||
|
The system is currently undergoing a strategic **Architectural Redesign** to consolidate legacy structures into a high-performance, maintainable schema. This design, spearheaded by leadership, focuses on reducing technical debt and improving data consistency across:
|
||||||
|
|
||||||
|
- **Unified Test Definitions:** Consolidating technical, calculated, and site-specific test data.
|
||||||
|
- **Reference Range Centralization:** A unified engine for numeric, threshold, text, and coded results.
|
||||||
|
- **Ordered Workflow Management:** Precise tracking of orders from collection to verification.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛡️ Strategic Pillars
|
||||||
|
|
||||||
|
- **Precision & Accuracy:** Strict validation for all laboratory parameters and reference ranges.
|
||||||
|
- **Scalability:** Optimized for high-volume diagnostic environments.
|
||||||
|
- **Compliance:** Built-in audit trails and status history for full traceability.
|
||||||
|
- **Interoperability:** Modular architecture designed for LIS, HIS, and analyzer integrations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Technical Stack
|
||||||
|
|
||||||
|
| Component | Specification |
|
||||||
|
| :------------- | :------------ |
|
||||||
|
| **Language** | PHP 8.1+ (PSR-compliant) |
|
||||||
|
| **Framework** | CodeIgniter 4 |
|
||||||
|
| **Security** | JWT (JSON Web Tokens) Authorization |
|
||||||
|
| **Database** | MySQL (Optimized Schema Migration in progress) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 📜 Usage Notice
|
||||||
|
This repository contains proprietary information intended for the 5Panda Team and authorized collaborators.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📈 Project Updates
|
||||||
|
|
||||||
|
<div class="not-prose grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4 mt-6">
|
||||||
|
{% for post in collections.clqms %}
|
||||||
|
<a href="{{ post.url }}" class="card bg-base-200 hover:bg-base-300 transition-colors border border-base-300 hover:border-primary/50 p-4 rounded-xl flex flex-col justify-between h-full gap-4 group">
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold text-lg group-hover:text-primary transition-colors mb-2">{{ post.data.title }}</h3>
|
||||||
|
<p class="text-base-content/70 text-sm line-clamp-3">{{ post.data.description }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-2 text-xs text-base-content/50 mt-auto">
|
||||||
|
<time datetime="{{ post.date | dateFormat('iso') }}">{{ post.date | dateFormat('short') }}</time>
|
||||||
|
<span>•</span>
|
||||||
|
<span>{{ post.content | readingTime }}</span>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
{% else %}
|
||||||
|
<div class="col-span-full">
|
||||||
|
<p class="text-base-content/60 italic">No updates available yet.</p>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
---
|
||||||
|
*© 2025 5Panda Team. Engineering Precision in Clinical Diagnostics.*
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
layout: base.njk
|
layout: base.njk
|
||||||
title: Proposals - 5Panda
|
title: Portfolio - 5Panda
|
||||||
description: Project proposals and innovative ideas
|
description: Our projects and technical showcase
|
||||||
---
|
---
|
||||||
|
|
||||||
<section class="py-16">
|
<section class="py-16">
|
||||||
@ -9,10 +9,10 @@ description: Project proposals and innovative ideas
|
|||||||
class="section-container">
|
class="section-container">
|
||||||
<!-- Header -->
|
<!-- Header -->
|
||||||
<div class="text-center mb-16">
|
<div class="text-center mb-16">
|
||||||
<span class="badge badge-primary badge-outline badge-lg mb-4">Proposals</span>
|
<span class="badge badge-primary badge-outline badge-lg mb-4">Portfolio</span>
|
||||||
<h1 class="text-4xl md:text-5xl font-bold mb-4">Project Proposals</h1>
|
<h1 class="text-4xl md:text-5xl font-bold mb-4">Our Projects</h1>
|
||||||
<p class="text-xl text-base-content/70 max-w-2xl mx-auto">
|
<p class="text-xl text-base-content/70 max-w-2xl mx-auto">
|
||||||
Innovative ideas and detailed project proposals.
|
Innovative solutions and technical deep dives.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<!-- Proposals List -->
|
<!-- Proposals List -->
|
||||||
@ -69,8 +69,8 @@ description: Project proposals and innovative ideas
|
|||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<h3 class="text-xl font-bold mb-2">No proposals yet</h3>
|
<h3 class="text-xl font-bold mb-2">No projects yet</h3>
|
||||||
<p class="text-base-content/60">Project proposals will appear here once they're added.</p>
|
<p class="text-base-content/60">Projects will appear here once they're added.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
---
|
|
||||||
title: Sample Project Proposal
|
|
||||||
description: A template for creating project proposals with clear structure and goals.
|
|
||||||
date: 2024-12-17
|
|
||||||
tags:
|
|
||||||
- posts
|
|
||||||
- template
|
|
||||||
layout: post.njk
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
Brief overview of what this project aims to achieve and why it matters.
|
|
||||||
|
|
||||||
## Problem Statement
|
|
||||||
|
|
||||||
What problem does this project solve? Who faces this problem?
|
|
||||||
|
|
||||||
- Pain point 1
|
|
||||||
- Pain point 2
|
|
||||||
- Pain point 3
|
|
||||||
|
|
||||||
## Proposed Solution
|
|
||||||
|
|
||||||
### Overview
|
|
||||||
|
|
||||||
Describe your proposed solution at a high level.
|
|
||||||
|
|
||||||
### Key Features
|
|
||||||
|
|
||||||
1. **Feature One** - Description of the first key feature
|
|
||||||
2. **Feature Two** - Description of the second key feature
|
|
||||||
3. **Feature Three** - Description of the third key feature
|
|
||||||
|
|
||||||
## Technical Approach
|
|
||||||
|
|
||||||
### Technology Stack
|
|
||||||
|
|
||||||
| Component | Technology |
|
|
||||||
|-----------|------------|
|
|
||||||
| Frontend | React / Vue / etc. |
|
|
||||||
| Backend | Node.js / Python / etc. |
|
|
||||||
| Database | PostgreSQL / MongoDB / etc. |
|
|
||||||
|
|
||||||
### Architecture
|
|
||||||
|
|
||||||
Describe the system architecture and how components interact.
|
|
||||||
|
|
||||||
```
|
|
||||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
||||||
│ Client │────▶│ Server │────▶│ Database │
|
|
||||||
└─────────────┘ └─────────────┘ └─────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Timeline
|
|
||||||
|
|
||||||
| Phase | Duration | Deliverables |
|
|
||||||
|-------|----------|--------------|
|
|
||||||
| Phase 1 | 2 weeks | Research & Design |
|
|
||||||
| Phase 2 | 4 weeks | Development |
|
|
||||||
| Phase 3 | 2 weeks | Testing & Launch |
|
|
||||||
|
|
||||||
## Success Metrics
|
|
||||||
|
|
||||||
How will we measure if this project is successful?
|
|
||||||
|
|
||||||
- Metric 1: Target value
|
|
||||||
- Metric 2: Target value
|
|
||||||
- Metric 3: Target value
|
|
||||||
|
|
||||||
## Budget & Resources
|
|
||||||
|
|
||||||
Outline the resources needed for this project.
|
|
||||||
|
|
||||||
## Risks & Mitigation
|
|
||||||
|
|
||||||
| Risk | Impact | Mitigation Strategy |
|
|
||||||
|------|--------|---------------------|
|
|
||||||
| Risk 1 | High | How to address it |
|
|
||||||
| Risk 2 | Medium | How to address it |
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Review and approve proposal
|
|
||||||
2. Assemble team
|
|
||||||
3. Kick off Phase 1
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*This is a sample proposal template. Replace this content with your actual project details.*
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
layout: base.njk
|
layout: base.njk
|
||||||
title: 5Panda - Project Proposals
|
title: 5Panda - Portfolio
|
||||||
description: Innovative project ideas and proposals
|
description: Innovative projects and ideas
|
||||||
---
|
---
|
||||||
|
|
||||||
<!-- Hero Section -->
|
<!-- Hero Section -->
|
||||||
@ -17,37 +17,39 @@ description: Innovative project ideas and proposals
|
|||||||
<div class="section-container relative z-10">
|
<div class="section-container relative z-10">
|
||||||
<div class="max-w-4xl mx-auto text-center">
|
<div class="max-w-4xl mx-auto text-center">
|
||||||
<div class="animate-slide-up">
|
<div class="animate-slide-up">
|
||||||
<span class="badge badge-primary badge-outline badge-lg mb-6">Project Proposals</span>
|
<span class="badge badge-primary badge-outline badge-lg mb-6">Development Team</span>
|
||||||
<h1 class="text-5xl md:text-7xl font-bold mb-6 leading-tight">
|
<h1 class="text-5xl md:text-7xl font-bold mb-6 leading-tight">
|
||||||
Ideas That<br>
|
Building the<br>
|
||||||
<span class="gradient-text">Shape Tomorrow.</span>
|
<span class="gradient-text">Future of Tech.</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p class="text-xl md:text-2xl text-base-content/70 mb-10 max-w-2xl mx-auto">
|
<p class="text-xl md:text-2xl text-base-content/70 mb-10 max-w-2xl mx-auto">
|
||||||
Innovative project proposals and concepts. Explore ideas that push boundaries and create impact.
|
We are a team of passionate developers, designers, and innovators crafting digital experiences that matter.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-wrap justify-center gap-4">
|
<div class="flex flex-wrap justify-center gap-4">
|
||||||
<a href="/blog/" class="btn btn-primary btn-lg gap-2 animate-glow">
|
<a href="/team/" class="btn btn-primary btn-lg gap-2 animate-glow">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path
|
<path
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7
|
||||||
|
20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014
|
||||||
|
0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"/>
|
||||||
</svg>
|
</svg>
|
||||||
View Proposals
|
Meet the Team
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<!-- Latest Proposals Section -->
|
<!-- Portfolio Section -->
|
||||||
<section class="py-20">
|
<section class="py-20">
|
||||||
<div class="section-container">
|
<div class="section-container">
|
||||||
<div class="text-center mb-12">
|
<div class="text-center mb-12">
|
||||||
<span class="badge badge-accent badge-outline mb-4">Latest</span>
|
<span class="badge badge-accent badge-outline mb-4">Portfolio</span>
|
||||||
<h2 class="text-3xl md:text-4xl font-bold mb-4">Recent Proposals</h2>
|
<h2 class="text-3xl md:text-4xl font-bold mb-4">Our Work</h2>
|
||||||
<p class="text-base-content/70 max-w-2xl mx-auto">Browse through the latest project ideas and proposals</p>
|
<p class="text-base-content/70 max-w-2xl mx-auto">Explore our latest projects and technical solutions</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 max-w-4xl mx-auto">
|
||||||
{% for post in collections.posts | head(4) %}
|
{% for post in collections.posts | head(4) %}
|
||||||
@ -77,15 +79,15 @@ description: Innovative project ideas and proposals
|
|||||||
stroke-width="1.5"
|
stroke-width="1.5"
|
||||||
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
|
||||||
</svg>
|
</svg>
|
||||||
<h3 class="text-xl font-bold mb-2">No proposals yet</h3>
|
<h3 class="text-xl font-bold mb-2">No projects yet</h3>
|
||||||
<p class="text-base-content/60">Project proposals will appear here once they're added.</p>
|
<p class="text-base-content/60">Projects will appear here once they're added.</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% if collections.posts.length > 4 %}
|
{% if collections.posts.length > 4 %}
|
||||||
<div class="text-center mt-10">
|
<div class="text-center mt-10">
|
||||||
<a href="/blog/" class="btn btn-outline btn-primary">View All Proposals</a>
|
<a href="/blog/" class="btn btn-outline btn-primary">View All Projects</a>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
81
src/team.njk
Normal file
81
src/team.njk
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
---
|
||||||
|
layout: base.njk
|
||||||
|
title: The 5Panda Team
|
||||||
|
description: Meet the innovative minds behind 5Panda.
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="hero-gradient min-h-[35vh] flex items-center justify-center relative overflow-hidden">
|
||||||
|
<div class="text-center z-10 px-4">
|
||||||
|
<h1 class="text-5xl md:text-7xl font-bold mb-4">Meet the
|
||||||
|
<span class="gradient-text">Squad</span>
|
||||||
|
</h1>
|
||||||
|
<p class="text-xl text-base-content/70">The pandas building the future.</p>
|
||||||
|
</div>
|
||||||
|
<!-- Background bubbles -->
|
||||||
|
<div class="absolute inset-0 overflow-hidden pointer-events-none">
|
||||||
|
<div class="absolute top-10 left-10 w-40 h-40 bg-primary/20 rounded-full blur-3xl animate-float"></div>
|
||||||
|
<div class="absolute bottom-10 right-10 w-40 h-40 bg-secondary/20 rounded-full blur-3xl animate-float animate-delay-200"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<section
|
||||||
|
class="py-16 section-container">
|
||||||
|
<!-- Team Photo -->
|
||||||
|
<div class="max-w-5xl mx-auto mb-20 animate-slide-up">
|
||||||
|
<div class="transform hover:scale-[1.01] transition-transform duration-500">
|
||||||
|
<img
|
||||||
|
src="/assets/images/team_v2.png"
|
||||||
|
alt="5Panda Team: Alam, Haris, Bambang, Irham, Zaka"
|
||||||
|
class="w-full h-auto rounded-3xl shadow-2xl">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Individual Bios (Text Fallback / Accessible Content) -->
|
||||||
|
<div
|
||||||
|
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 max-w-6xl mx-auto">
|
||||||
|
<!-- Alam -->
|
||||||
|
<div class="card bg-base-100/50 hover:bg-base-100 border border-base-200 hover:border-primary/50 transition-all duration-300
|
||||||
|
hover:-translate-y-2 group">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl group-hover:text-primary transition-colors">🤓 Alam</h2>
|
||||||
|
<div class="badge badge-primary badge-outline">UI/UX & Supervisor</div>
|
||||||
|
<p class="text-base-content/70 mt-2">The visionary with the glasses. Guiding the team and crafting perfect user
|
||||||
|
experiences.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Haris -->
|
||||||
|
<div class="card bg-base-100/50 hover:bg-base-100 border border-base-200 hover:border-secondary/50 transition-all duration-300
|
||||||
|
hover:-translate-y-2 group">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl group-hover:text-secondary transition-colors">🎧 Haris</h2>
|
||||||
|
<div class="badge badge-secondary badge-outline">The Vibecoder</div>
|
||||||
|
<p class="text-base-content/70 mt-2">Writing code that hits differently. Bringing the good vibes to the dev cycle.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Bambang -->
|
||||||
|
<div class="card bg-base-100/50 hover:bg-base-100 border border-base-200 hover:border-warning/50 transition-all duration-300
|
||||||
|
hover:-translate-y-2 group">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl group-hover:text-warning transition-colors">🔍 Bambang</h2>
|
||||||
|
<div class="badge badge-warning badge-outline">UI/UX & Tester</div>
|
||||||
|
<p class="text-base-content/70 mt-2">Quality assurance expert. Ensuring every pixel and interaction is flawless.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Irham -->
|
||||||
|
<div class="card bg-base-100/50 hover:bg-base-100 border border-base-200 hover:border-success/50 transition-all duration-300
|
||||||
|
hover:-translate-y-2 group">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl group-hover:text-success transition-colors">🕶️ Irham</h2>
|
||||||
|
<div class="badge badge-success badge-outline">Frontend Dev</div>
|
||||||
|
<p class="text-base-content/70 mt-2">Bringing designs to life with code. Always rocking the glasses.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Zaka -->
|
||||||
|
<div class="card bg-base-100/50 hover:bg-base-100 border border-base-200 hover:border-error/50 transition-all duration-300
|
||||||
|
hover:-translate-y-2 group">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title text-2xl group-hover:text-error transition-colors">👓 Zaka</h2>
|
||||||
|
<div class="badge badge-error badge-outline">Junior Backend Dev</div>
|
||||||
|
<p class="text-base-content/70 mt-2">Building the engine room. Young talent with a focus on server-side logic.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
Loading…
x
Reference in New Issue
Block a user