factcircuit
FactCircuit는 불변 출처와 단계별 검증, 제한된 검색, 재현 가능한 평가를 제공하는 감사 가능한 주장·증거 검증 루프입니다
아직 감지된 업데이트가 없습니다. GitHub 릴리스가 나오면 여기에 보입니다.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗A web-based Internal Quality Control (IQC) dashboard for clinical laboratory instruments. Import QC data from AU5800 and DxI analysers, compute summary statistics (Mean, SD, CV%, n), visualise trends with Levey-Jennings charts, and export audit-ready reports.
Admins see a Try beta switch in the header. Off is the standard app; on adds the features below. The choice is remembered per browser, and non-admin accounts never see the switch or the beta features. Exports (PDF/XLSX/CSV) are unchanged by the switch.
What's new panel — while beta is on, the front screen opens with a "New in the beta" panel listing each beta update and where to find it, plus step-by-step instructions for sigma metrics (where to enter TEa, the formula Sigma = (TEa % − |Bias %|) ÷ CV %, and how to read the result). It can be hidden (remembered per browser) and reopened from Account › What's new in the beta. Switching beta on after data is loaded shows the same list in a dialog.
Design changes in the beta:
The calculations live in public/beta-logic.js (no DOM, unit-tested directly by tests/beta-logic.test.js); the UI is public/beta.js. Both are static files, so the beta adds no Serverless Functions.
| Layer | Technology |
|---|---|
| Frontend | Vanilla JavaScript single-page app |
| Backend | Node.js serverless functions (Vercel) |
| Database | PostgreSQL (Neon serverless) |
| Auth | JWT (jose) + bcrypt password hashing |
| Charts | Chart.js 4.4 + chartjs-plugin-annotation |
| PDF Export | jsPDF + jspdf-autotable |
| Excel Export | xlsx-js-style |
| Hosting | Vercel |
iqc-summary-statistics/
├── public/
│ ├── index.html # Full SPA (HTML + CSS + JS)
│ ├── beta-logic.js # Beta QC calculations (Westgard, bias, sigma, lot comparison)
│ └── beta.js # Beta UI (admin "Try beta" toggle)
├── api/ # Every file here becomes a Serverless Function
│ ├── auth/
│ │ └── [action].js # All /api/auth/* routes (see lib/auth-routes/)
│ ├── admin/
│ │ ├── users.js # GET/PUT /api/admin/users
│ │ ├── reset-password.js # POST /api/admin/reset-password
│ │ └── activity.js # GET /api/admin/activity
│ ├── activity.js # POST /api/activity (client-side logging)
│ ├── csv-store.js # GET/POST /api/csv-store
│ ├── csv-store/
│ │ └── [id].js # GET/DELETE /api/csv-store/:id
│ ├── insights.js # POST /api/insights (AI summary, streamed)
│ ├── reports.js # GET/POST /api/reports
│ ├── reports/
│ │ └── [id].js # GET/DELETE/PATCH /api/reports/:id
│ └── setup.js # GET/POST /api/setup (init DB tables)
├── lib/ # Shared modules — NOT Serverless Functions
│ ├── auth.js # JWT verification middleware
│ ├── activity.js # Activity logging helper
│ └── auth-routes/ # Handlers dispatched by api/auth/[action].js
│ ├── login.js # POST /api/auth/login
│ ├── register.js # POST /api/auth/register
│ ├── logout.js # POST /api/auth/logout
│ ├── me.js # GET /api/auth/me
│ └── change-password.js # POST /api/auth/change-password
├── tests/
│ ├── frontend-logic.js # Extracted frontend functions for testing
│ ├── frontend.test.js # Frontend unit tests (parseCSV, computeStats, etc.)
│ └── api.test.js # API handler tests with mocked DB/auth
├── package.json
├── vercel.json # Vercel routing config
├── jest.config.js
└── babel.config.js
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/auth/register | Register new user. First user auto-becomes admin. |
| POST | /api/auth/login | Authenticate and receive HttpOnly JWT cookie. |
| POST | /api/auth/logout | Clear auth cookie. |
| GET | /api/auth/me | Get current user info (requires auth). |
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/reports | Any authenticated | List all saved reports. |
| POST | /api/reports | full_access | Save a new report. Supports compressed format (gzipped base64) or legacy uncompressed JSON. |
| GET | /api/reports/:id | Any authenticated | Fetch a specific report. Compressed data is sent to client for browser-side decompression. |
| PATCH | /api/reports/:id | full_access | Append data chunks to an existing report (legacy chunked upload support). |
| DELETE | /api/reports/:id | full_access | Delete a report (cascades to report_chunks). |
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| GET | /api/admin/users | admin | List all users. |
| PUT | /api/admin/users | admin | Update user status (approved/denied/pending) or permission (view_only/full_access). |
| GET | /api/admin/activity | admin | Fetch activity log (supports limit and offset query params). |
| Method | Endpoint | Permission | Description |
|---|---|---|---|
| POST | /api/activity | Any authenticated | Log a client-side action (data_process, export_pdf, export_xlsx, export_csv, data_clear, point_remove, point_restore). |
| Method | Endpoint | Description |
|---|---|---|
| GET/POST | /api/setup | Initialise database tables (reports, users, activity_log, report_chunks). Adds compressed_data column if missing. Cleans up empty/broken reports. |
Semicolon-delimited with 13 columns:
Protocol;Instrument;Parameter;Level;Date;Value;Target;SD;Status;Message;Comment;User;SampleId
Example:
TestProto;AU5800 1 L;Glucose;1;01/03/2025 08:00;5.5;5.0;0.3;Accepted;OK;;user1;SAMPLE1
AU5800 1 L -> AU/DxI-1)| Role | Status | Permission | Capabilities |
|---|---|---|---|
| admin | approved | full_access | All features + user management panel |
| user | approved | full_access | Import, export, save/load/delete reports |
| user | approved | view_only | Import, export, view reports (no save/delete) |
| user | pending | - | Cannot access dashboard until approved by admin |
| user | denied | - | Access denied |
The first registered user automatically becomes admin with full_access.
| Variable | Description |
|---|---|
DATABASE_URL | Neon PostgreSQL connection string |
JWT_SECRET | Secret key for signing JWT tokens |
# Install dependencies
npm install
# Set environment variables
export DATABASE_URL="postgresql://..."
export JWT_SECRET="your-secret-key"
# Initialise database tables
# Visit /api/setup or POST to it after deploying
# Deploy to Vercel
vercel
npm test
Tests run in Europe/London time (set in tests/global-setup.js) so date handling is exercised across BST.
Reports use a compressed storage approach to handle large datasets within Vercel's 4.5MB body size limit:
CompressionStream API, then base64-encodedcompressed_data TEXT columnDecompressionStreamfilters.removedPoints and listed in the PDF exportA typical 25MB dataset compresses to ~2-3MB for storage. The serverless function pre-warming strategy eliminates cold start delays (~45s reduced to ~1.5s).
The app is configured for Vercel with:
public/index.html served as the SPA for all non-API routesapi/ directory auto-deployed as serverless functionsmaxDuration: 60 in vercel.json)vercel.jsonThe Hobby plan allows a maximum of 12 Serverless Functions per deployment.
Exceeding it fails the deploy with exceeded_serverless_functions_per_deployment
after the build reports success, so a green build log is not proof of a
successful deploy — check the deployment state.
Every .js file under api/ counts as one function, including files that are
only ever imported by others. Shared code therefore lives in lib/ (outside
api/), and the five /api/auth/* routes are served by the single dispatcher
api/auth/[action].js.
Current count: 11 of 12. Before adding a file under api/, check the count:
find api -name "*.js" -type f | wc -l
To add a route without spending a slot, add a handler in lib/ and dispatch to
it from an existing route.