Organization: The Education University of Hong Kong
Project: KeySteps@JC Assessment Data Processing System
Version: 2025/26 Academic Year
The 4Set System is a comprehensive web-based assessment data processing pipeline that automates the collection, validation, processing, and monitoring of educational survey data for the KeySteps@JC research project. It replaces legacy desktop workflows with an unsupervised, cloud-integrated solution capable of handling PDF form uploads, performing rigorous data validation, enriching submission data, and automatically submitting to JotForm while providing real-time monitoring dashboards for quality assurance.
β
Automated PDF Processing - File watcher with debounce logic for OneDrive uploads
β
Two-Phase Validation - Filename format + cross-field consistency checks
β
Data Enrichment - Automatic field calculation and metadata injection
β
Termination Rules - Intelligent assessment exit criteria based on threshold logic
β
JotForm Integration - Idempotent upsert workflow with retry mechanisms
β
Qualtrics Integration - Complete data extraction from Qualtrics surveys (all tasks)
β
Grade Detection - Automatic K1/K2/K3 classification based on assessment dates (Aug-Jul school year)
β
Grade-Aware Data Merging - JotForm + Qualtrics data merged per (coreId, grade) pair to prevent cross-grade contamination
β
Unique Student Filtering - Deduplicated Core ID display in filter dropdowns
β
Quality Monitoring - Multi-level dashboards for data completeness verification
β
Security First - AES-256-GCM encryption for all sensitive assets
β
Production Ready - Windows Service + Synology Docker deployment support
OneDrive Cloud Storage
β
Processor Agent (Windows/Synology)
β’ File Watcher & Validation
β’ PDF Parsing & Enrichment
β’ JotForm Upload with Retry
β’ Filing Protocol
β
JotForm Database
β
Monitoring Dashboard (GitHub Pages)
β’ Upload Status & Queue Health
β’ Checking System (5-Level Drilldown)
β’ Error Reporting & Analytics
π New Users: For a simplified step-by-step guide to get started quickly, see our Quick Start Guide - perfect for new users who want to start using the system right away without diving into technical details.
- PowerShell 7+ (required for AES-GCM decryption)
- Python 3.7+ with
pypdforPyPDF2librarypip install pypdf # or pip install PyPDF2 - Windows Credential Manager entry with master key (Windows deployment)
- OneDrive for Business sync client or Synology Cloud Sync
- Encrypted credential bundles in
assets/directory
-
Clone Repository
git clone https://github.com/herman925/4Set-Server.git cd 4Set-Server -
Configure Agent Settings Edit
config/agent.jsonto set OneDrive path and processing options:{ "oneDrive": { "autoDetect": true, "relativePath": "\\YourOrg\\YourPath\\4Set-Server" } } -
Set Up Credentials
- Windows: Store system password in Credential Manager
cmdkey /generic:4set-processor-master /user:system /pass:YourPassword
- Synology: Configure Docker secret
processor_master_key
- Windows: Store system password in Credential Manager
-
Start Processor Agent
# Interactive mode (for testing) pwsh -File .\processor_agent.ps1 # Single-run mode (process current files and exit) pwsh -File .\processor_agent.ps1 -SingleRun # Custom config path pwsh -File .\processor_agent.ps1 -ConfigPath "C:\custom\config.json"
-
Access Web Dashboards
- Main Entry: Open
index.htmlin browser - Upload Interface:
upload.htmlfor PDF submissions - Checking System:
checking_system_home.htmlfor data validation
- Main Entry: Open
When running the Checking System locally, you'll need a CORS proxy to access JotForm API. The proxy is not needed for GitHub Pages or production deployment.
One-Click Startup:
# Windows - Double-click or run:
start_dev.bat
# Linux/Mac:
./start_dev.shThis will:
- Install Flask dependencies (if needed)
- Start the CORS proxy server on
http://127.0.0.1:3000 - Automatically open your browser to the main entry page (
index.html)
Manual Startup:
# Install dependencies
pip install -r requirements.txt
# Start proxy server
python proxy_server.py --port 3000 --host 127.0.0.1
# Access at: http://127.0.0.1:3000/index.htmlWhy Needed:
- Browsers block cross-origin API requests (CORS policy)
- JotForm API doesn't allow direct calls from
localhost - The Flask proxy routes requests through Python (no CORS restrictions)
- Production (GitHub Pages): No proxy needed - works directly
| File | Purpose |
|---|---|
processor_agent.ps1 |
Main processor agent (PowerShell 7 service) |
worker.ps1 |
Worker thread manager for parallel processing |
proxy_server.py |
Flask CORS proxy for local development |
start_dev.bat |
Windows one-click startup (proxy + browser) |
start_dev.sh |
Linux/Mac one-click startup (proxy + browser) |
upload.py |
Python upload utility (legacy/backup) |
index.html |
System entry page with navigation |
upload.html |
Drag-and-drop PDF upload interface |
queue_manifest.json |
Persistent queue state for restart recovery |
requirements.txt |
Python dependencies (pypdf, Flask, requests) |
README.md |
This file - comprehensive documentation |
AGENTS.md |
Development roadmap and strategic planning |
Configuration files for system behavior:
agent.jsonβ OneDrive paths, polling intervals, worker settingsjotform_config.jsonβ Rate limits, batch sizes, retry scheduleshost_identity.json.exampleβ Computer number override templatechecking_system_config.jsonβ Dashboard display options
Encrypted data assets and static resources:
credentials.encβ API keys, system password (AES-256-GCM encrypted)coreid.encβ Student ID mappings (Core ID β School, Class, Name)schoolid.encβ School metadata (ID β Name, District, Group)classid.encβ Class mappings (Class ID β Actual Class Name)jotformquestions.jsonβ Field name to Question ID (QID) mappingscss/,js/,logos/β Dashboard styling and assets
PDF extraction engine:
parse_pdf_cli.pyβ Command-line interface for PDF parsingpdf_tools.pyβ Core extraction using pypdf/PyPDF2 libraries
Comprehensive product requirement documents:
calculation_bible.mdβ Complete calculation and validation referenceoverview_prd.mdβ System architecture and component overviewprocessor_agent_prd.mdβ Agent specification and requirementsprocessor_agent_runbook_prd.mdβ Operational procedureschecking_system_prd.mdβ Quality assurance validation rulesdata_security_prd.mdβ Encryption and credential managementtermination-rules.mdβ Assessment termination logicupload_monitoring_prd.mdβ Upload failure detection
Utility scripts for testing and development:
test_jotform_filter.ps1β JotForm API filter validationtest_chunked_update.ps1β Batch update testing
Development test files and utilities:
test-pipeline-core-id.htmlβ JotForm + Qualtrics merge pipeline test toolREADME_PIPELINE_TEST.mdβ Documentation for pipeline test tooltest-jotform-filter.htmlβ Browser-based JotForm filter testingtest_qualtrics_syd1.htmlβ Qualtrics API connection tester- Other temporary development and verification files
Archived processed files (organized by school ID):
S###/β Successfully processed PDFs and JSON by schoolUnsorted/β Failed validations or upload errors
Multi-level monitoring dashboards:
checking_system_home.htmlβ System overview and navigationchecking_system_1_district.htmlβ District-level aggregationchecking_system_1_group.htmlβ Project group viewchecking_system_2_school.htmlβ School-level completionchecking_system_3_class.htmlβ Class drilldown with heatmapschecking_system_4_student.htmlβ Student detail validation
Export Features:
- All exports include calculated Status Light column (π’ Complete, π΄ Incomplete, π‘ Post-Term, βͺ Not Started)
- Validates exported data matches displayed status for quality assurance
- See
export-utils.jsfor implementation details
Validation Logic:
- Radio-Text Questions (Theory of Mind, Math Pattern): Automatically marks as incorrect if radio empty but text field filled
- Prevents partial submissions and ensures data completeness
- See
task-validator.jslines 354-389 for implementation
Purpose: Autonomous Windows/Synology service for PDF ingestion and processing
Core Functionality:
- Monitors OneDrive sync folder for new PDF uploads
- Applies two-phase validation (filename format + cross-field consistency)
- Extracts form fields and generates enriched JSON
- Calculates termination rules based on threshold logic
- Data Overwrite Protection (optional, configurable): Prevents accidental corruption of existing assessment data
- Can be enabled/disabled via
config/agent.jsonβdataProtection.enableDataOverwriteProtection - When enabled (default): Validates that existing non-empty assessment answers won't be overwritten
- When disabled: Allows full data overwrites, relying on human due diligence
- Exception fields (student-id, child-name, school-id, etc.) are always allowed to be updated
- See
user_guide_conflicts.htmlfor detailed conflict resolution procedures
- Can be enabled/disabled via
- Uploads to JotForm with idempotent upsert pattern
- Files successfully processed PDFs by school ID
- Moves failed uploads to
Unsorted/for manual review - Exposes telemetry API for dashboard integration
Configuration Options (config/agent.json):
{
"oneDrive": {
"autoDetect": true,
"relativePath": "\\Organization\\Path\\4Set-Server",
"fallbackRoot": "C:\\Users\\Username"
},
"watchFolders": ["incoming"],
"pollingIntervalSeconds": 5,
"maxConcurrentWorkers": 2,
"logRetentionDays": 30,
"dataProtection": {
"enableDataOverwriteProtection": true
}
}OneDrive Detection Strategy:
$env:OneDriveCommercial(Business account - highest priority)$env:OneDrive(Personal account fallback)- Registry:
HKCU\Software\Microsoft\OneDrive\Commercial - Registry:
HKLM\Software\Microsoft\OneDrive(system-wide) fallbackRootfrom config (manual override)
Running the Agent:
# Interactive mode (for testing and development)
pwsh -File .\processor_agent.ps1
# Single-run mode (process current files once and exit)
pwsh -File .\processor_agent.ps1 -SingleRun
# Custom configuration path
pwsh -File .\processor_agent.ps1 -ConfigPath "C:\custom\agent.json"
# Windows Service installation (production)
# See PRDs/processor_agent_runbook_prd.md for NSSM setupUsage Example:
- Start the agent in a PowerShell window
- Drop PDF files into the configured watch folder (e.g.,
incoming/) - Agent automatically:
- Moves file to
processing/staging area - Validates filename format (xxxxx_YYYYMMDD_HH_MM.pdf)
- Extracts PDF form fields to JSON
- Cross-validates against encrypted mappings
- Enriches with computed fields (sessionkey, computerno, class-id)
- Calculates termination rules
- Uploads to JotForm (upsert by sessionkey)
- Files both PDF + JSON to
filed/{schoolId}/orfiled/Unsorted/
- Moves file to
- Review processing logs in
logs/YYYYMMDD_processing_agent.csv - Check queue state in
queue_manifest.jsonfor restart recovery
Stopping the Agent:
- Press
Ctrl+Cin PowerShell session (graceful shutdown) - Service stop command:
Stop-Service -Name "4SetProcessor"(if installed as service)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 1: File Detection β
β incoming/ folder β
β (OneDrive synced, watched by agent) β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 2: File Staging β
β processing/ folder β
β β’ Move from incoming/ to prevent re-processing β
β β’ Apply debounce logic (wait for file stability) β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 3: Phase 1 Validation β
β Filename Format Check β
β β’ Pattern: xxxxx_YYYYMMDD_HH_MM.pdf β
β β’ Extract student ID, date, time from filename β
β β’ Reject if format invalid β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 4: PDF Parsing β
β parse_pdf_cli.py (Python Engine) β
β β’ Extract all form fields from PDF β
β β’ Generate raw answer fields (ERV_Q1, CM_Q1, etc.) β
β β’ Extract score helpers (ERV_Q1_Sc, etc.) β
β β’ Create initial JSON structure β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 5: Phase 2 Validation β
β Cross-Field Consistency Check β
β β’ Decrypt coreid.enc, schoolid.enc mappings β
β β’ Lookup Core ID (C#####) in student database β
β β’ Verify school ID matches student record β
β β’ Reject if: coreid_missing_in_mapping β
β β’ Reject if: coreid_schoolid_mismatch β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 6: Data Enrichment β
β Add Computed & Lookup Fields β
β β’ sessionkey: Filename stem (unique ID) β
β β’ computerno: Extract from computer name β
β β’ child-name: Lookup from coreid.enc β
β β’ class-id: Lookup from coreid.enc (25/26 only) β
β β’ class-name: Lookup from classid.enc β
β β’ Gender: Fallback if missing in PDF β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 7: Termination Calculation β
β Apply Threshold Rules to Score Data β
β β’ ERV_Ter1/2/3: <5 correct in 12-question blocks β
β β’ CM_Ter1/2/3/4: <4 correct in 5-7 question blocks β
β β’ CWR_10Incorrect: 10 consecutive incorrect β
β β’ FM_Ter: All FM_squ scores = 0 β
β β’ Use "Absolute Certainty Principle" β
β β’ Remove all _Sc helper fields after calculation β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 8: Write JSON β
β Save Enriched Data to Disk β
β β’ Write complete JSON with all enriched fields β
β β’ Set jotformsubmissionid = "" (placeholder) β
β β’ Ensure data integrity before upload β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β STAGE 9: JotForm Upload β
β Idempotent Upsert Workflow β
β β’ Search for existing submission by sessionkey β
β β’ IF FOUND: Update (exclude sessionkey field) β
β β’ IF NOT FOUND: Create new submission β
β β’ Retry with exponential backoff (3 attempts) β
β β’ Write back jotformsubmissionid to JSON β
βββββββββββββββββββββββββββββ¬ββββββββββββββββββββββββββββββββββ
β
βββββββββ΄ββββββββ
β β
Upload Success Upload Failed
β β
β β
ββββββββββββββββββ ββββββββββββββββ
β filed/S###/ β βfiled/Unsorted/β
β β’ PDF file β β β’ PDF file β
β β’ JSON file β β β’ JSON file β
β (with subID) β β (no subID) β
ββββββββββββββββββ ββββββββββββββββ
- File system watcher monitors
incoming/folder - Triggers on file creation/modification events
- Debounce logic ensures file is fully uploaded (stable size)
- OneDrive sync status verification (if available)
- Move file from
incoming/toprocessing/immediately - Prevents duplicate processing if watcher triggers again
- Clears watch folder for next batch
- Records file path in queue manifest
- Filename Pattern:
xxxxx_YYYYMMDD_HH_MM.pdfxxxxx: 5-digit student ID (can have "C" prefix internally)YYYYMMDD: Date (e.g., 20250904)HH_MM: Time (24-hour format, e.g., 14_07)
- Validation Checks:
- Correct number of underscore separators
- Valid date format (year, month, day ranges)
- Valid time format (hour 00-23, minute 00-59)
- Rejection: Move to
Unsorted/with reason code if invalid
- Parser:
parser/parse_pdf_cli.py(Python engine) - Library: pypdf or PyPDF2
- Extraction:
- All form fields (text boxes, checkboxes, radio buttons)
- Answer fields (ERV_Q1, CM_Q1_TEXT, etc.)
- Score helper fields (ERV_Q1_Sc, CM_Q1_TEXT_Sc, etc.)
- Field Mapping: Uses
assets/jotformquestions.jsonfor friendly names - Output: Initial JSON structure with raw PDF data
- Cross-Field Consistency Check:
- Decrypt
assets/coreid.encusing system password - Extract Core ID from JSON (e.g., "C13268")
- Look up Core ID in decrypted student database
- Retrieve expected School ID from student record
- Compare with School ID extracted from PDF
- Verify student name matches (optional secondary check)
- Decrypt
- Rejection Codes:
pdf_extraction_failed- Parser error or corrupted PDFcoreid_missing_in_mapping- Student not found in databasecoreid_schoolid_mismatch- School IDs don't match expectations
- Security: Encrypted mappings decrypted in-memory only, buffers cleared after use
- Computed Fields:
sessionkey: Derived from filename stem (e.g., "13268_20250904_14_07")computerno: Extracted from $env:COMPUTERNAME (e.g., "KS095" β "095")jotformsubmissionid: Initially empty, populated after upload
- Lookup Fields (from
coreid.enc):child-name: Student's full nameclass-id: Class identifier for 25/26 academic year onlyGender: Fallback if missing in PDF
- Lookup Fields (from
classid.enc):class-name: Human-readable class name
- Override Support:
config/host_identity.jsonfor container deployments
-
Termination Rules (see
PRDs/termination-rules.md):Rule Questions Threshold Output Field ERV_Ter1 ERV_Q1βQ12 <5 correct term_ERV_Ter1 ERV_Ter2 ERV_Q13βQ24 <5 correct term_ERV_Ter2 ERV_Ter3 ERV_Q25βQ36 <5 correct term_ERV_Ter3 CM_Ter1 CM_Q1βQ7 <4 correct term_CM_Ter1 CM_Ter2 CM_Q8βQ12 <4 correct term_CM_Ter2 CM_Ter3 CM_Q13βQ17 <4 correct term_CM_Ter3 CM_Ter4 CM_Q18βQ22 <4 correct term_CM_Ter4 CWR_10Incorrect CWR_Q1βQ60 10 consecutive term_CWR_10Incorrect FM_Ter FM_squ_1β3 All = 0 term_FM_Ter -
Absolute Certainty Principle:
- Set
"0"(continued) if:correct β₯ threshold(already passed) - Set
"1"(terminated) if:correct + unanswered < threshold(impossible to pass) - Otherwise: Don't set - still possible to pass with remaining questions
- Example: ERV_Ter1 needs β₯5 correct out of 12
- 3 correct, 1 unanswered: Max = 4 < 5 β Set "1" β
- 3 correct, 3 unanswered: Max = 6 β₯ 5 β Don't set βΈοΈ
- Set
-
Score Field Cleanup: Remove all
_Schelper fields after calculation (not uploaded)
- Write enriched JSON to disk at same location as PDF
- Filename: Same stem as PDF with
.jsonextension - Include all enriched fields, computed terminations
- Set
jotformsubmissionid = ""as placeholder - Critical: Data persisted BEFORE upload attempt (never lost)
- Upsert Workflow:
- Search JotForm for existing submission by
sessionkey - If found: Update submission (exclude immutable
sessionkeyfield) - If not found: Create new submission (include
sessionkey) - Write back
jotformsubmissionidto JSON file
- Search JotForm for existing submission by
- Retry Logic:
- 3 attempts with exponential backoff (default)
- Delays: 10s, 30s, 90s (with Β±20% jitter)
- Retry on: 429 (rate limit), 5xx (server errors), timeouts
- Fail immediately on: 4xx errors (except 429)
- Rate Limiting (
config/jotform_config.json):maxConcurrent: Parallel upload workers (default: 2)perMinuteQuota: Max requests per minute (default: 120)batchSize: Records per batch request (default: 25)burstCooldownSeconds: Delay after rate limit hit (default: 60)
- Permanent Failure Handling:
- After exhausting retries, log error details
- File to
filed/Unsorted/regardless of school ID - JSON retains empty
jotformsubmissionid(detection flag) - Logs contain full error stack for debugging
- Success Path:
filed/{schoolId}/- Both PDF and JSON moved to school-specific folder
- JSON contains valid
jotformsubmissionid - Files archived for audit trail
- Failure Path:
filed/Unsorted/- Validation failures OR permanent upload failures
- JSON has empty/missing
jotformsubmissionid - Requires manual review and potential re-processing
- Collision Handling: If file exists, append timestamp suffix
- PowerShell 7.0+ (required for AES-GCM decryption via System.Security.Cryptography)
- Download: https://github.com/PowerShell/PowerShell/releases
- Verify:
pwsh --version
- Python 3.7+ with PDF parsing library
# Option 1: pypdf (recommended) pip install pypdf # Option 2: PyPDF2 (legacy support) pip install PyPDF2
- OneDrive for Business sync client OR Synology Cloud Sync
- Web Browser (Chrome, Firefox, Edge) for dashboard access
- Windows Credential Manager (Windows deployment credential storage)
- NSSM (Non-Sucking Service Manager) for Windows Service installation
- Docker (Synology NAS deployment)
- CPU: Dual-core 2.0 GHz
- RAM: 4 GB
- Disk: 10 GB free space (for logs and filed PDFs)
- Network: Broadband internet (for OneDrive sync and JotForm API)
- CPU: Quad-core 2.5 GHz or better (Intel i5/i7, AMD Ryzen)
- RAM: 8 GB or more
- Disk: 50 GB free space (SSD preferred for faster I/O)
- Network: Dedicated line with >10 Mbps upload
- Windows 10/11 Enterprise - Primary production platform
- Synology DS923+ - NAS deployment (Ryzen R1600, 4+ GB RAM)
- Windows Server 2019/2022 - Enterprise server deployment
- Encrypted Assets:
credentials.enc,coreid.enc,schoolid.enc,classid.enc - System Password: Stored in Windows Credential Manager or DSM Docker Secrets
- HTTPS Access: All external API calls over TLS 1.2+
- File Permissions: Restricted access to
assets/andconfig/directories
After validation, the agent enriches the JSON with computed and lookup fields:
| Field | Source | Type | Description | Example |
|---|---|---|---|---|
sessionkey |
Filename stem | Computed | Unique identifier for submission | "13268_20250904_14_07" |
computerno |
$env:COMPUTERNAME |
Computed | Computer number from hostname | "KS095" β "095" |
jotformsubmissionid |
JotForm API response | Returned | Submission ID after upload | "5584719287206845678" |
child-name |
coreid.enc lookup |
Lookup | Student's full name | "εΌ΅δΈ" |
class-id |
coreid.enc lookup |
Lookup | Class identifier (25/26 year only) | "C-023-03" |
class-name |
classid.enc lookup |
Lookup | Human-readable class name | "ε°δΈη²η" |
Gender |
coreid.enc fallback |
Conditional | Gender if missing in PDF | "M" or "F" |
Important: The PDF contains score helper fields (e.g., ERV_Q1_Sc, CM_Q1_TEXT_Sc) used internally for termination calculation. These fields are removed after enrichment and will not be uploaded to JotForm. Only raw answer values are preserved in the final submission.
Processing Flow:
- Extract score helpers from PDF (
_Scfields) - Use helpers to calculate termination outcomes
- Remove all
_Scfields from JSON - Upload only raw answers and computed terminations
The system calculates termination outcomes based on survey logic defined in PRDs/termination-rules.md:
| Rule | Question Range | Total Qs | Threshold | Output Field | Logic |
|---|---|---|---|---|---|
| ERV_Ter1 | ERV_Q1βQ12 | 12 | β₯5 correct | term_ERV_Ter1 |
"1" if <5, "0" if β₯5 |
| ERV_Ter2 | ERV_Q13βQ24 | 12 | β₯5 correct | term_ERV_Ter2 |
"1" if <5, "0" if β₯5 |
| ERV_Ter3 | ERV_Q25βQ36 | 12 | β₯5 correct | term_ERV_Ter3 |
"1" if <5, "0" if β₯5 |
| CM_Ter1 | CM_Q1βQ7 | 7 | β₯4 correct | term_CM_Ter1 |
"1" if <4, "0" if β₯4 |
| CM_Ter2 | CM_Q8βQ12 | 5 | β₯4 correct | term_CM_Ter2 |
"1" if <4, "0" if β₯4 |
| CM_Ter3 | CM_Q13βQ17 | 5 | β₯4 correct | term_CM_Ter3 |
"1" if <4, "0" if β₯4 |
| CM_Ter4 | CM_Q18βQ22 | 5 | β₯4 correct | term_CM_Ter4 |
"1" if <4, "0" if β₯4 |
| CWR_10Incorrect | CWR_Q1βQ60 | 60 | <10 consecutive | term_CWR_10Incorrect |
"1" if β₯10 seq., "0" otherwise |
| FM_Ter | FM_squ_1β3 | 3 | Any score >0 | term_FM_Ter |
"1" if all=0, "0" if any>0 |
- "0" = Continued (threshold met, assessment proceeds)
- "1" = Terminated (threshold not met, assessment ended early)
- Empty/Unset = Indeterminate (not enough data to decide)
Termination values are calculated only when mathematically certain:
-
Set "0" (continued) if:
correct_count β₯ threshold- Student has already met the passing criteria
- Assessment definitely continues to next section
- Example: 6 correct out of 12, need β₯5 β Set "0" β
-
Set "1" (terminated) if:
correct_count + unanswered_count < threshold- Even if student answers all remaining questions correctly, cannot reach threshold
- Assessment definitely terminates
- Example: 3 correct, 1 unanswered, need β₯5 (out of 12)
- Max possible = 3 + 1 = 4 < 5 β Set "1" β
-
Don't set (leave empty) otherwise
- Student might still reach threshold with remaining questions
- Need more data to make definitive determination
- Example: 3 correct, 3 unanswered, need β₯5 (out of 12)
- Max possible = 3 + 3 = 6 β₯ 5 β Don't set βΈοΈ
- Empty/unanswered questions are treated as missing data, not failures
- If PDF already contains termination value (filled during survey), it is preserved
- System calculations are used to validate administrator's recorded terminations
- Mismatches between recorded and calculated values trigger warnings in Checking System
Example 1: ERV_Ter1 (English Vocab Block 1)
- Questions: ERV_Q1 through ERV_Q12 (12 total)
- Threshold: β₯5 correct to continue
- Scenario A: 6 answered, 6 correct
- Result: Set
term_ERV_Ter1 = "0"(passed threshold) β
- Result: Set
- Scenario B: 8 answered, 3 correct
- Max possible = 3 + 4 = 7 β₯ 5
- Result: Don't set (could still pass) βΈοΈ
- Scenario C: 11 answered, 3 correct
- Max possible = 3 + 1 = 4 < 5
- Result: Set
term_ERV_Ter1 = "1"(cannot pass) β
Example 2: CWR_10Incorrect (Chinese Word Reading)
- Questions: CWR_Q1 through CWR_Q60 (60 total)
- Threshold: <10 consecutive incorrect to continue
- Scenario A: Questions 10-19 all incorrect (10 in sequence)
- Result: Set
term_CWR_10Incorrect = "1"(terminated) β
- Result: Set
- Scenario B: 9 consecutive incorrect, then 1 correct
- Result: Set
term_CWR_10Incorrect = "0"(passed) β
- Result: Set
- Scenario C: 5 consecutive incorrect, rest unanswered
- Result: Don't set (need more data) βΈοΈ
The Checking System is a comprehensive data validation suite that provides multi-level drilldown views for monitoring assessment completion and data quality. It serves two critical verification functions:
Purpose: Show what test administrators recorded and uploaded to JotForm
- Display all student answers exactly as submitted
- Reflect administrator's manual decisions during assessment
- Provide complete visibility into database contents
- Serve as the "source of truth" view for uploaded data
Purpose: Calculate what SHOULD be true and flag discrepancies
- Recalculate termination rules based on actual responses
- Compare administrator's recorded decisions vs. system calculations
- Identify data quality issues and missing data
- Alert administrators to inconsistencies requiring verification
The system provides 5 levels of drill-down navigation:
Level 1: District View (checking_system_1_district.html)
β Geographic aggregation
Level 1B: Group View (checking_system_1_group.html)
β Project group organization
Level 2: School View (checking_system_2_school.html)
β Individual school performance
Level 3: Class View (checking_system_3_class.html)
β Class-level completion heatmaps
Level 4: Student View (checking_system_4_student.html)
β Individual student detail validation
The Checking System automatically saves user interface preferences to prevent view resets when navigating between pages:
Saved Preferences:
- Section Expansion/Collapse: All
<details>element states (open/closed) are preserved per page - View Mode (Class Page): Choice between "by Set" and "by Task" views is remembered per class
- Grade Selection (Student Page): Selected grade (K1/K2/K3) is remembered per student
- Task Filter (Student Page): Filter dropdown selection is remembered per student
Implementation:
- Uses browser
localStoragefor persistence - Preferences are scoped to specific entities (classId, coreId, schoolId, etc.)
- Graceful fallback if localStorage is unavailable
- Managed by
assets/js/checking-system-preferences.jsutility
Usage: No action required from users - preferences are saved automatically as you interact with the interface. Your view settings will be restored when you return to a page.
- Is there any missing data?
- How many questions are unanswered?
- Which specific questions are missing?
- Are there gaps in required fields?
- Visual Indicators: Grey status circles, "Missing only" filter, answered/total counts
- Did the administrator mark termination rules correctly?
- What did the administrator record? (0=passed, 1=terminated)
- What should they have recorded based on actual answers?
- Do the two values match?
- Visual Indicators:
- β Green checkmark = Verified - Record matches calculation
β οΈ Orange warning = Mismatch detected - Please verify- π΄ Red highlight = Triggered termination
- Side-by-side comparison views
| Icon | Meaning | Description |
|---|---|---|
| β | Complete & Verified | All tasks submitted, calculations match |
| Incomplete / Mismatch | Missing data or calculation discrepancy | |
| π΄ | Terminated | Assessment ended early per rules |
| βͺ | Not Started | No submission data available |
| π΅ | In Progress | Partial completion |
New Feature (October 2025): The Checking System now displays text answer fields for Theory of Mind (ToM) questions with smart status indicators.
Applicable Fields:
- ToM_Q3_TEXT, ToM_Q3a_TEXT, ToM_Q4a_TEXT, ToM_Q6a_TEXT, ToM_Q7a_TEXT, ToM_Q7b_TEXT
Note: ToM_Q3_TEXT and ToM_Q4a_ins1_TEXT are instruction questions (asking students to guess what's in a container before revealing) and are excluded from display in the checking system, though they remain in the source data files.
Status Display Rules:
| Scenario | Radio Answer | Text Content | Display | Meaning |
|---|---|---|---|---|
| Correct selected | β Correct | Any | π΅ N/A | Text not needed |
| Wrong selected | β Incorrect | Has text | π΅ Answered | Text provided |
| Wrong selected | β Incorrect | Empty | π΄ Not answered | Expected but missing |
| No answer | (null) | Any | βͺ β | No display needed |
Key Rules:
- "Not answered" status ONLY appears when the radio answer is incorrect
- When radio is not answered, _TEXT field shows "β" (no display needed)
- _TEXT fields are NEVER counted in completion percentage calculations
- Only scored questions (radio_text questions) count toward task completion
Example: In ToM_Q3a, if student selects "ηδ»" (correct answer), the ToM_Q3a_TEXT field shows "N/A" even if text is accidentally entered. If student selects "ε Άδ»" (other), the text field should contain the actual answer and shows "Answered" status.
Documentation: Complete validation logic and implementation details available in PRDs/calculation_bible.md (Lines 158-220).
:matches operator on sessionkey field (QID 3).
After extensive testing in October 2025, we discovered that JotForm's standard filter operators (:eq, :contains) DO NOT WORK for student ID field (QID 20). They return the full dataset (545+ submissions) regardless of filter values.
β CORRECT Implementation:
// Use :matches operator on sessionkey field (QID 3)
const studentIdNumeric = coreId.replace(/^C/, ''); // Remove "C" prefix
const filter = { "q3:matches": studentIdNumeric };
const url = `https://api.jotform.com/form/${formId}/submissions?` +
`filter=${encodeURIComponent(JSON.stringify(filter))}`;This reduces data transfer from ~30 MB to ~110 KB and improves performance from 3-5 seconds to <500ms.
-
From Main Entry Page (
index.html):- Click "Checking System" button
- Navigate to
checking_system_home.html
-
Direct Access:
- Open
checking_system_home.htmlin browser - Enter system password when prompted
- Select desired view level (District/Group/School/Class/Student)
- Open
-
Navigation Flow:
- Start at District or Group overview
- Click on region to drill down to schools
- Click on school to view classes
- Click on class to see student list
- Click on student to view detailed validation
The agent uses a Python-based PDF parser that extracts all form fields and generates enriched JSON output.
Components:
-
parser/parse_pdf_cli.py- Command-line interface- Receives PDF path as argument
- Returns JSON to stdout
- Handles errors gracefully
-
parser/pdf_tools.py- Core extraction engine- Uses
pypdforPyPDF2library - Extracts text boxes, checkboxes, radio buttons
- Handles multi-page forms
- Preserves field types and values
- Uses
Field Mapping: Uses assets/jotformquestions.json to map PDF field names (e.g., "Student ID") to standardized friendly names (e.g., "student-id") that match JotForm question IDs for seamless upload.
Usage Example:
# Command-line invocation
python parser/parse_pdf_cli.py "/path/to/13268_20250904_14_07.pdf"
# Output (JSON to stdout)
{
"student-id": "C13268",
"school-id": "S023",
"ERV_Q1": "2",
"ERV_Q1_Sc": "1",
...
}Error Handling:
- Returns error object with descriptive message if parsing fails
- Logs detailed stack trace to agent logs
- Moves problematic PDF to
Unsorted/with reason code
After enrichment, the agent automatically uploads data to JotForm using an idempotent upsert workflow:
-
Search for existing submission by
sessionkey(unique identifier)- Use JotForm API:
GET /form/{formId}/submissions?filter={"q3:matches":"13268"} - QID 3 contains the sessionkey field
- Use JotForm API:
-
Decision Branch:
- If found: Update existing submission
- Use
POST /submission/{submissionId} - Exclude immutable
sessionkeyfield from payload - Preserve existing
jotformsubmissionid
- Use
- If not found: Create new submission
- Use
POST /form/{formId}/submissions - Include
sessionkeyin payload - Generate new
jotformsubmissionid
- Use
- If found: Update existing submission
-
Write Back:
- Extract
jotformsubmissionidfrom API response - Update local JSON file with submission ID
- Commit changes to disk before filing
- Extract
- Enriched JSON is written BEFORE upload attempt
- Ensures data is never lost even if upload fails
- JSON file serves as local backup and audit trail
- Can be manually re-uploaded or debugged if needed
- Re-running same PDF updates existing submission (no duplicates)
sessionkeyserves as unique constraint- Multiple uploads of same file converge to single JotForm record
- Safe to retry failed uploads without data duplication
- Attempts: 3 by default (configurable via
config/jotform_config.json) - Delay Schedule: 10s, 30s, 90s (with Β±20% jitter to avoid thundering herd)
- Retryable Errors:
- 429 (Rate Limit Exceeded) - Respect cooldown period
- 5xx (Server Errors) - Transient failures, retry helps
- Timeouts - Network issues may resolve
- Non-Retryable Errors:
- 4xx (Client Errors, except 429) - Bad request, won't succeed on retry
- Authentication failures - Credential issue needs manual fix
- Malformed data - Validation error won't change
After exhausting all retry attempts:
- Log error with full details (level: ERROR)
- Error message, HTTP status code
- Request payload (sanitized)
- Timestamp and attempt count
- File to
Unsorted/folder regardless of valid School ID- Both PDF and JSON moved together
- JSON retains empty
jotformsubmissionid(detection flag) - Data preserved for manual review
- Queue telemetry updated for dashboard visibility
Encrypted bundle containing:
jotformApiKey- API key for authenticationjotformFormId- Target form ID for submissionssystemPassword- Master password for decryption
Maps friendly field names to JotForm Question IDs (QIDs):
{
"sessionkey": "3",
"student-id": "20",
"school-id": "21",
"computerno": "647",
"ERV_Q1": "100",
...
}{
"maxConcurrent": 2, // Parallel upload workers
"batchSize": 25, // Records per batch request
"perMinuteQuota": 120, // Max requests per minute
"retryScheduleSeconds": [10, 30, 90], // Retry delays
"burstCooldownSeconds": 60 // Delay after rate limit hit
}# View all JotForm-related log entries
Select-String -Path logs/YYYYMMDD_processing_agent.csv -Pattern "Jotform"
# Filter for specific sessionkey
Select-String -Path logs/*.csv -Pattern "13268_20250904_14_07" |
Where-Object { $_.Line -match "Jotform" }Successful Upload:
2025-10-16 14:07:23,INFO,13268_20250904_14_07.pdf,Jotform upload attempt 1 of 3
2025-10-16 14:07:24,INFO,13268_20250904_14_07.pdf,Found existing Jotform submission: 5584719287206845678
2025-10-16 14:07:25,INFO,13268_20250904_14_07.pdf,Jotform upload successful, wrote back submissionID: 5584719287206845678 (took 1 attempt(s))
Failed Upload (with retries):
2025-10-16 14:10:15,INFO,13269_20250904_15_30.pdf,Jotform upload attempt 1 of 3
2025-10-16 14:10:16,WARN,13269_20250904_15_30.pdf,Jotform upload failed (429 Rate Limit), retrying after 10s
2025-10-16 14:10:27,INFO,13269_20250904_15_30.pdf,Jotform upload attempt 2 of 3
2025-10-16 14:10:28,INFO,13269_20250904_15_30.pdf,Creating new Jotform submission
2025-10-16 14:10:29,INFO,13269_20250904_15_30.pdf,Jotform upload successful (took 2 attempt(s))
Permanent Failure:
2025-10-16 14:15:45,INFO,13270_20250904_16_00.pdf,Jotform upload attempt 3 of 3
2025-10-16 14:16:15,ERROR,13270_20250904_16_00.pdf,Jotform upload PERMANENTLY FAILED after 3 attempts: API Error 500 Internal Server Error
2025-10-16 14:16:15,INFO,13270_20250904_16_00.pdf,Filing to Unsorted due to upload failure
Failed uploads are automatically filed to filed/Unsorted/ folder, making them easy to identify and retry:
# PowerShell script to check Unsorted folder for failed uploads
Get-ChildItem filed/Unsorted -Filter "*.json" | ForEach-Object {
$json = Get-Content $_.FullName | ConvertFrom-Json
$sessionkey = $json.data.sessionkey
# Check if jotformsubmissionid is empty/missing
if ([string]::IsNullOrWhiteSpace($json.data.jotformsubmissionid)) {
Write-Host "Failed upload: $($_.Name) (sessionkey: $sessionkey)" -ForegroundColor Yellow
# Find error details in logs
$logPattern = $sessionkey -replace '_', '\\_'
Select-String -Path "logs/*_processing_agent.csv" -Pattern $logPattern |
Where-Object { $_.Line -match "PERMANENTLY FAILED|ERROR" } |
ForEach-Object {
Write-Host " Error: $($_.Line)" -ForegroundColor Red
}
}
}Logs are the single source of truth for failure reasons. Search logs by sessionkey:
# Get all log entries for specific file (errors and warnings)
$sessionkey = "13268_20250904_14_07"
Select-String -Path "logs/*_processing_agent.csv" -Pattern $sessionkey |
Where-Object { $_.Line -match "ERROR|WARN" }
# Get only permanent failures
Select-String -Path "logs/*_processing_agent.csv" -Pattern "PERMANENTLY FAILED"
# Get rate limit warnings
Select-String -Path "logs/*_processing_agent.csv" -Pattern "429.*Rate Limit"- Identify failed file in
filed/Unsorted/folder - Review error in logs to understand root cause
- Fix underlying issue:
- Rate limit: Wait for cooldown period (60+ seconds)
- Network error: Check internet connectivity
- API error: Verify JotForm service status
- Data error: Manually correct JSON fields
- Move file back to
incoming/folder for re-processingMove-Item "filed/Unsorted/13268_20250904_14_07.pdf" "incoming/" Move-Item "filed/Unsorted/13268_20250904_14_07.json" "incoming/"
- Monitor logs for successful re-upload
The processor agent exposes upload status via telemetry API:
Endpoint: GET http://localhost:48500/api/upload-status
Response:
{
"totalFiles": 150,
"successCount": 145,
"failedCount": 5,
"pendingRetries": 2,
"lastUpdate": "2025-10-16T14:30:00Z",
"failures": [
{
"sessionkey": "13270_20250904_16_00",
"filename": "13270_20250904_16_00.pdf",
"attempts": 3,
"lastError": "API Error 500 Internal Server Error",
"timestamp": "2025-10-16T14:16:15Z"
}
]
}Alternative for GitHub Pages: Write status/upload_status.json every 60 seconds with same structure for static hosting.
The system integrates Qualtrics survey data with JotForm submissions to provide a comprehensive view of student assessments. All Qualtrics task data (TGMD, ERV, SYM, TOM, etc.) is automatically extracted and merged with JotForm data.
- Parallel Fetch - JotForm and Qualtrics data are fetched simultaneously for optimal performance
- Export & Poll - Creates survey export and polls for completion
- Download - Retrieves completed export file
- Transform - Maps 632 Qualtrics QIDs to field names using
assets/qualtrics-mapping.json - Merge - Combines with JotForm data by Core ID
Performance Optimization: The system uses parallel API calls to fetch JotForm and Qualtrics data simultaneously, reducing total sync time by 30-40%. This means faster cache builds and improved user experience.
All tasks from qualtrics-mapping.json are extracted:
- TGMD - Test of Gross Motor Development (observational assessment)
- ERV - Expressive Vocabulary
- SYM - Symbolic Understanding
- TOM - Theory of Mind
- CM - Counting & Magnitude
- CWR - Chinese Word Reading
- HTKS - Head-Toes-Knees-Shoulders
- TEC - Test of Emotional Comprehension
- And more...
Implementation Status: β
All merge requirements from issues #149 and #151 are fully implemented. See PRDs/jotform_qualtrics_integration_prd.md section "Implementation Verification: Issues #149 and #151" for detailed verification.
- "Earliest non-empty wins" - Timestamp-based merge strategy for JotForm vs Qualtrics data
- When both sources have a value: Use the value from the earlier timestamp
- When only one source has a value: Use that value regardless of source
- Conflict detection logs all overwrites for audit trail
Critical: Within-source merge happens BEFORE cross-source merge to ensure data integrity:
- Within-Source Merge (Qualtrics): Sort Qualtrics records by date (earliest first), merge multiple responses per student
- Within-Source Merge (JotForm): Sort JotForm submissions by created_at (earliest first), merge multiple submissions per student
- Cross-Source Merge: Merge grouped Qualtrics data with JotForm base record by (coreId, grade) pairs
- Add metadata:
_sources,_qualtricsConflicts,_orphaned
Grade Grouping: Final cache contains separate records for each (coreId, grade) combination. Never merges data across different grades (K1/K2/K3).
// Example merged record
{
"coreId": "10261",
"sessionkey": "10261_20240915_10_30",
"grade": "K2", // Auto-detected from assessment date
"ERV_Q1": "answer1", // From earliest source (JotForm or Qualtrics)
"TGMD_111_Hop_t1": 1, // From earliest source with non-empty value
"_sources": ["jotform", "qualtrics"],
"_qualtricsConflicts": [...] // List of conflicts resolved by timestamp
}Students are automatically classified into K1/K2/K3 based on assessment dates using August-July school year boundaries:
- K1 (2023/24): August 2023 - July 2024
- K2 (2024/25): August 2024 - July 2025
- K3 (2025/26): August 2025 - July 2026
- Try
recordedDatefrom Qualtrics (ISO 8601 format) - Fallback to
sessionkeyfrom JotForm (format:{coreId}_{YYYYMMDD}_{HH}_{MM}) - Calculate school year:
month >= 8 ? year : year - 1 - Map to grade:
2023 β K1, 2024 β K2, 2025 β K3
Implemented in assets/js/grade-detector.js and integrated into data merger.
Filter dropdowns deduplicate students by Core ID to prevent showing multiple entries for students with records in different grades (K1, K2, K3). Each student appears once regardless of how many grade-level assessments they have completed.
Implemented in assets/js/checking-system-filters.js using Map-based deduplication.
Three-Layer Cache System (IndexedDB-based):
-
Merged Submissions Cache (
merged_jotform_qualtrics_cache)- Final merged dataset containing JotForm-only, Qualtrics-only, and merged records
- Each record has QID-indexed answers structure for uniform processing
- Tagged with
gradefield (K1/K2/K3) to prevent cross-grade contamination - Access:
JotFormCache.loadFromCache()orJotFormCache.getStudentSubmissions(coreId, grade)
-
Validation Cache (
student_task_validation_cache)- Pre-computed task validation results (answered/total per task, set progress)
- Map<coreId, validationData> structure
- Invalidated on cache rebuild or data sync
- Eliminates redundant validation processing
-
Raw Qualtrics Cache (
qualtrics_raw_responses)- Stores transformed Qualtrics responses for quick re-sync
- Enables "Refresh with Qualtrics" feature without full rebuild
- Significantly faster than re-fetching from Qualtrics API
Key Design Decisions:
- QID-Indexed Answers: All submissions (JotForm and Qualtrics-only) store answers with JotForm QID as key, ensuring uniform processing through
validateStudent()which converts to fieldName-indexed format - Grade Separation: Each (coreId, grade) pair creates a separate cache record, never mixing K1/K2/K3 data
- Parallel Fetching: JotForm and Qualtrics data fetched simultaneously using
Promise.all()for 40% performance improvement - Within-Then-Cross Merge: Data merged within each source first (multiple Qualtrics responses, multiple JotForm submissions), then merged across sources
Data Flow: API Fetch β Transform β Within-Source Merge β Cross-Source Merge β Convert to Submission Format β Cache β Validate β Display
See PRDs/jotform_qualtrics_integration_prd.md section "Comprehensive Cache Design Documentation" for complete technical details.
Complete configuration example with explanations:
{
"oneDrive": {
"autoDetect": true,
"relativePath": "\\The Education University of Hong Kong\\o365grp_KeySteps@JC - General\\98 - IT Support\\04 - Homemade Apps\\4Set-Server",
"fallbackRoot": "C:\\Users\\YourUsername"
},
"watchFolders": ["incoming"],
"pollingIntervalSeconds": 5,
"maxConcurrentWorkers": 2,
"logRetentionDays": 30,
"telemetryPort": 48500,
"queueManifestPath": "./queue_manifest.json",
"validation": {
"requireComputerNumber": false
},
"dataProtection": {
"enableDataOverwriteProtection": true
}
}Field Descriptions:
oneDrive.autoDetect- Enable automatic OneDrive path detectiononeDrive.relativePath- Path appended to detected OneDrive rootoneDrive.fallbackRoot- Manual override if auto-detection failswatchFolders- Array of folders to monitor (relative to OneDrive path)pollingIntervalSeconds- File system check frequencymaxConcurrentWorkers- Parallel PDF processing threadslogRetentionDays- Auto-delete logs older than N daystelemetryPort- Local HTTP API port for dashboard queriesqueueManifestPath- Persistent queue state file locationvalidation.requireComputerNumber- When false, allows uploads without computer number metadatadataProtection.enableDataOverwriteProtection- When true (default), prevents overwriting existing non-empty assessment data; when false, allows full data overwrites
Rate limiting and retry settings:
{
"maxConcurrent": 2,
"batchSize": 25,
"perMinuteQuota": 120,
"retryScheduleSeconds": [10, 30, 90],
"burstCooldownSeconds": 60,
"timeoutSeconds": 30
}Field Descriptions:
maxConcurrent- Maximum parallel upload workers (1-5 recommended)batchSize- Records per batch API request (1-100)perMinuteQuota- Maximum API calls per minute (respect JotForm limits)retryScheduleSeconds- Delay array for successive retriesburstCooldownSeconds- Additional delay after rate limit hittimeoutSeconds- HTTP request timeout duration
Performance Tuning:
- High throughput: Increase
maxConcurrentto 4-5, ensure adequate CPU/RAM - Rate limit avoidance: Decrease
perMinuteQuotato 80-100 - Large forms: Decrease
batchSizeto 10-15, increasetimeoutSecondsto 60
Optional configuration for Synology/container deployments:
{
"computerno": "095",
"hostName": "KS095"
}When to Use:
- Synology NAS where
$env:COMPUTERNAMEis unavailable - Docker containers with generic hostnames
- Testing environments with non-standard naming
If omitted: Agent extracts computer number from $env:COMPUTERNAME automatically
Dashboard display and behavior settings:
{
"districts": ["Shatin", "Sham Shui Po", "Kowloon City", "Tuen Mun", "Yuen Long", "Others"],
"groups": [1, 2, 3, 4, 5],
"refreshIntervalSeconds": 60,
"defaultPageSize": 50,
"enableDemoMode": false
}Symptoms:
- Files accumulate in
incoming/folder - No log entries being generated
- Service appears running but inactive
Diagnosis:
# Check service status (if installed as service)
Get-Service -Name "4SetProcessor"
# Check if process is running
Get-Process -Name "pwsh" | Where-Object { $_.CommandLine -match "processor_agent" }
# Verify OneDrive sync status
Get-ItemProperty -Path "HKCU:\Software\Microsoft\OneDrive" -Name "OneDriveCommercial"
# Review recent logs
Get-Content logs/*_processing_agent.csv -Tail 50Solutions:
- Service hung: Restart service
Restart-Service -Name "4SetProcessor"
- OneDrive not synced: Check OneDrive system tray icon for errors
- Path misconfigured: Verify
config/agent.jsonpaths exist - Permission denied: Run agent with appropriate file system permissions
Symptoms:
- Files moved to
filed/Unsorted/folder - JSON files have empty
jotformsubmissionid - Error messages in logs about API failures
Diagnosis:
# Check for permanent failures
Select-String -Path logs/*.csv -Pattern "PERMANENTLY FAILED"
# Check for rate limiting
Select-String -Path logs/*.csv -Pattern "429.*Rate Limit"
# Verify credentials decrypt correctly
# (test decrypt manually with debug script)Solutions:
-
Rate limit exceeded:
- Wait 60+ seconds for cooldown
- Decrease
perMinuteQuotainconfig/jotform_config.json - Reduce
maxConcurrentto 1-2
-
Network connectivity:
- Verify internet connection
- Test JotForm API:
curl https://api.jotform.com/user - Check firewall/proxy settings
-
Invalid credentials:
- Verify
credentials.encdecrypts correctly - Check API key validity in JotForm account
- Rotate credentials if compromised
- Verify
-
Malformed data:
- Review JSON structure in
filed/Unsorted/ - Check for required fields in
jotformquestions.json - Validate field mappings are correct
- Review JSON structure in
Symptoms:
- Files moved to
filed/Unsorted/immediately - Log shows rejection reason codes
- No JotForm upload attempt made
Rejection Codes & Solutions:
| Code | Meaning | Solution |
|---|---|---|
pdf_extraction_failed |
Parser error | Re-scan PDF with correct settings, check file integrity |
coreid_missing_in_mapping |
Student not in database | Add student to coreid.enc, re-encrypt, restart agent |
coreid_schoolid_mismatch |
School IDs don't match | Verify correct school ID in PDF form, check mapping accuracy |
invalid_filename_format |
Filename pattern wrong | Rename file to xxxxx_YYYYMMDD_HH_MM.pdf format |
Diagnosis:
# Find rejection reasons
Select-String -Path logs/*.csv -Pattern "rejected|Reject"
# Check specific file rejection
Select-String -Path logs/*.csv -Pattern "13268_20250904_14_07" |
Where-Object { $_.Line -match "reject" }Symptoms:
- Orange warning icons (
β οΈ ) in dashboard - "Recorded" vs "Calculated" values differ
- Termination flags don't match system computation
Investigation Steps:
- Review actual question responses in student detail view
- Verify calculation logic against
PRDs/termination-rules.md - Check for missing/unanswered questions that affect thresholds
- Compare administrator's decision with automatic calculation
Common Causes:
- Incomplete data: Unanswered questions make calculation indeterminate
- Manual override: Administrator intentionally marked differently
- Data entry error: Wrong answer recorded during assessment
- Logic edge case: Unusual response pattern not covered by rules
Resolution:
- If calculation is correct: Re-upload PDF with corrected termination value
- If data is missing: Complete assessment and re-submit
- If edge case: Document scenario and escalate to development team
- If intentional override: Add comment in system for audit trail
Symptoms:
- Agent logs "OneDrive path not found"
- Files not being picked up from watch folder
- Auto-detection fails in all strategies
Diagnosis:
# Check environment variables
$env:OneDriveCommercial
$env:OneDrive
# Check registry keys
Get-ItemProperty -Path "HKCU:\Software\Microsoft\OneDrive\Commercial"
Get-ItemProperty -Path "HKLM:\Software\Microsoft\OneDrive"
# Verify OneDrive is signed in
Get-Process -Name "OneDrive"Solutions:
- Set fallback path in
config/agent.json:{ "oneDrive": { "autoDetect": false, "fallbackRoot": "C:\\Users\\YourUsername\\OneDrive - YourOrg", "relativePath": "\\Path\\To\\4Set-Server" } } - Sign in to OneDrive on the machine
- Verify sync folder in OneDrive settings
- Use UNC path for network shares if applicable
Symptoms:
- Processing takes >10 seconds per file
- CPU usage constantly at 100%
- Queue backlog growing faster than processing
Diagnosis:
# Check current queue size
$manifest = Get-Content queue_manifest.json | ConvertFrom-Json
$manifest.queue.Count
# Monitor CPU and memory
Get-Process -Name "pwsh" | Select-Object CPU, WS
# Check for disk I/O bottleneck
Get-Counter '\PhysicalDisk(*)\% Disk Time'Solutions:
- Reduce concurrent workers:
{ "maxConcurrentWorkers": 1 } - Increase polling interval:
{ "pollingIntervalSeconds": 10 } - Upgrade hardware:
- Add RAM (8 GB+ recommended)
- Use SSD for filed/ directory
- Upgrade to quad-core CPU
- Optimize Python parser:
- Use pypdf instead of PyPDF2 (faster)
- Pre-compile Python scripts
- Consider caching parsed results
- Never commit
credentials.jsonor decrypted files to version control - Rotate API keys quarterly or after suspected compromise
- Use Windows Credential Manager for system password storage
- Restrict file permissions on
assets/directory (Admin/Service account only) - Audit access logs monthly for unauthorized access attempts
- Algorithm: AES-256-GCM (authenticated encryption)
- Key Derivation: PBKDF2-SHA256, 100,000 iterations
- Salt: 16-byte random per encryption
- IV: 12-byte random per encryption
- Never reuse salts or IVs across encryption operations
- Use HTTPS for all JotForm API calls (enforced by client)
- Bind telemetry API to localhost only (prevent remote access)
- Firewall rules: Allow outbound HTTPS (443) only
- No inbound connections required for agent operation
- Service account: Run agent as limited-privilege service account
- File permissions:
filed/directory readable by agent only - Dashboard authentication: System password required for web access
- Audit logging: All actions logged with timestamp and user
- Verify agent service is running
- Check OneDrive sync status (green checkmark)
- Review dashboard for overnight uploads
- Clear/investigate files in
filed/Unsorted/ - Monitor queue size in
queue_manifest.json
- Review and archive old log files (>7 days)
- Check disk space on
filed/directories - Validate encrypted asset integrity (SHA-256 hashes)
- Test credential rotation procedures (dry run)
- Review error patterns in logs
- Performance benchmarking and trend analysis
- Security audit of access logs
- Update documentation for process changes
- Stakeholder reporting on completion rates
- Backup critical configuration files
- Rotate API keys and system passwords
- Full system security review
- Update dependencies (PowerShell, Python libraries)
- Disaster recovery drill
- Capacity planning and hardware assessment
- PowerShell 7.4+ for script development
- Python 3.9+ with pypdf library
- Git for version control
- Code editor (VS Code recommended with PowerShell extension)
- Clone repository and create feature branch
- Make changes following code standards (see below)
- Test locally with
-SingleRunmode - Update documentation (PRDs, README, Agent)
- Commit with conventional commit messages
- Create pull request with detailed description
- PowerShell: Follow PSScriptAnalyzer recommendations
- Python: PEP 8 compliant, type hints where applicable
- JavaScript: ES6+ with strict mode enabled
- HTML/CSS: Semantic markup, Tailwind CSS utilities
- Unit tests for data transformation functions
- Integration tests for end-to-end pipeline
- Security tests for encryption/decryption
- Performance tests for throughput benchmarks
type(scope): description
[optional body]
[optional footer]
Types: feat, fix, docs, style, refactor, test, chore
Example: feat(parser): add support for multi-page PDFs
- Quick Start Guide:
quick_start_guide.html- π NEW! Simple step-by-step guide for new users - Main Documentation:
/PRDs/folder - AGENTS.md: Development roadmap and priorities
- Security Architecture:
PRDs/data_security_prd.md - Termination Rules:
PRDs/termination-rules.md - JotForm API Integration:
PRDs/jotform_qualtrics_integration_prd.md- NEW: Complete API call process documentation (see "Complete API Call Process Documentation" section)
- Includes upload workflow, create submission process, troubleshooting, and best practices
- Web User Guides: Interactive HTML guides accessible from system home page
quick_start_guide.html- Quick start guide for new users (comprehensive overview)user_guide_checking_system.html- Checking system usage guideuser_guide_uploader.html- Upload interface guideuser_guide_qualtrics.html- Qualtrics integration guideuser_guide_conflicts.html- Data conflict resolution guide- Guideline System: Advanced interactive learning system with spotlight effects, modals, and tooltips
- Technical specification:
PRDs/guideline_prd.md - Implementation:
assets/js/spotlight-system.jsandassets/css/spotlight-system.css
- Technical specification:
- PRD User Guides: Markdown versions in
/PRDs/folderPRDs/guideline_prd.md- User guide system design specification (spotlight, modals, tooltips)PRDs/checking_system_user_guide_prd.md- Checking system usage guidePRDs/assessment_uploader_user_guide_prd.md- Upload interface guidePRDs/qualtrics_tgmd_user_guide_prd.md- Qualtrics integration guidePRDs/data_conflicts_user_guide_prd.md- Data conflict resolution guide
Note: All documentation has been consolidated into the /PRDs/ directory. Legacy references to TEMP/ subdirectories should use the following paths:
TEMP/tasks/termination-rules.mdβPRDs/termination-rules.mdTEMP/integrations/jotform-integration.mdβPRDs/jotform_qualtrics_integration_prd.mdTEMP/architecture/security-architecture.mdβPRDs/data_security_prd.mdTEMP/data-tool/*βPRDs/data-pipeline.mdUSER_GUIDE_*.mdβPRDs/*_user_guide_prd.md(consolidated October 2025)
- JotForm API: https://api.jotform.com/docs/
- OneDrive API: https://docs.microsoft.com/graph/onedrive-concept-overview
- PowerShell 7: https://github.com/PowerShell/PowerShell
- pypdf: https://pypdf.readthedocs.io/
- Tailwind CSS: https://tailwindcss.com/docs
- Issues: GitHub Issues tracker
- Discussions: GitHub Discussions
- Email: project-maintainers@eduhk.hk
This project is developed for The Education University of Hong Kong's KeySteps@JC research initiative. All rights reserved.
Project Team:
- KeySteps@JC Research Team
- The Education University of Hong Kong IT Support
- 4Set System Development Contributors
Special Thanks:
- JotForm for API access and support
- Microsoft for OneDrive integration capabilities
- Open-source contributors of pypdf and PowerShell
Last Updated: October 2025
Version: 2025/26 Academic Year
Status: Production Ready