Hidden Cost Game
Hidden Cost Game is a web-based research prototype for studying how people interpret visible outcomes before and after learning that participants faced unequal hidden cost conditions.
The application is designed for local demonstration and controlled pilot deployment. It includes a participant flow, local JSON export, optional server submission, PostgreSQL storage, protected researcher export endpoints, and a minimal admin dashboard.
Hidden Cost Game examines judgment under incomplete information in a simplified decision environment. Participants first see outcome differences without knowing that players may have faced different underlying cost conditions; after the reveal, they reconsider their earlier interpretations.
The prototype is intended to support exploratory research questions about:
- Invisible inequality: how unobserved differences in cost burden can shape visible outcomes.
- Decision-making under incomplete information: how participants make judgments when important contextual information is missing.
- Attribution of outcomes: whether lower scores are attributed to decisions, risk acceptance, constraints, luck, or insufficient information.
- Perceived fairness: whether outcome differences are judged as fair before and after the hidden rule is disclosed.
- Legitimacy of objection/protest: whether objections from lower-scoring players are considered legitimate.
- Support for rule correction and redistribution: whether participants support adjusting rules or transferring points when hidden disadvantage is confirmed.
- Pre/post reveal design: how interpretations shift between the visible-results stage and the reveal-aware stage.
The language and measures are intentionally neutral. The prototype is not a diagnostic instrument, a persuasion tool, or a validated psychological scale.
The implemented participant route sequence is:
/— introduction: introduces the prototype, the general sequence, and local-first data handling./consent— consent and incomplete-information notice: explains voluntary participation, no direct identifiers, browser storage, and that some game information is intentionally withheld until the reveal./background— non-identifying background variables: collects broad participant context such as age group, gender, subjective economic status, medical cost pressure, healthcare coverage, and baseline orientation items./game— five healthcare-related decision rounds: assigns a visible profile, runs five simplified healthcare decision rounds, and records decisions, costs, scores, health, and timing./visible-results— score table before reveal: shows a fictional comparison table and the participant's visible outcome before the hidden cost rule is disclosed./pre-reveal-survey— initial interpretation: records initial judgments based only on the visible results table./hidden-rule-reveal— hidden cost rule disclosed: explains that the visible profile corresponded to unequal treatment-cost conditions./post-reveal-survey— revised interpretation: records parallel judgments after the hidden cost condition is known./individual-results— summary, metrics, optional server submission: summarizes the participant's game outcome, survey shifts, computed metrics, local JSON export, and optional server submission when enabled./export— JSON export and submission status: provides copy/download controls for the JSON export, reset controls, and optional server submission status./admin— protected researcher dashboard, not part of participant flow: lets researchers enter an export token to view server-submitted data summaries and download JSON/CSV.
There is also a legacy /results route that redirects in the browser to /individual-results for sessions with a completed post-reveal survey, or to /visible-results otherwise.
The current implementation uses:
- Next.js 14 App Router for pages and API routes.
- React 18 and TypeScript for the participant and admin interfaces.
- Tailwind CSS for styling.
- Browser
localStoragepersistence underhidden-cost-game-sessionfor participant progress and local exports. - Optional server submission through
POST /api/submissions;POST /api/research-submissionsis an alias. - PostgreSQL + Prisma for pilot-mode server storage.
- JSON and CSV export through protected admin endpoints.
- Simple protected admin dashboard at
/adminusing bearer-token-style access withADMIN_EXPORT_TOKEN. - Security headers configured in
next.config.mjs, including CSP, frame denial, content-type protection, referrer policy, and a restrictive permissions policy.
- Node.js 22 LTS. The repository includes
.nvmrc, so users ofnvmcan runnvm usefrom the project root. - npm. Use the committed
package-lock.jsonwithnpm cifor reproducible installs on fresh clones and CI. - PostgreSQL only when using server submission or admin exports. Local demo mode can run without a database while server submission is disabled.
git clone https://github.com/Spbd1/Hidden-Cost-Game.git
cd Hidden-Cost-Game
nvm use # optional, if you use nvmnpm ciUse npm install only when intentionally updating dependencies or regenerating package-lock.json.
cp .env.example .envEdit .env before enabling server features:
- Set
DATABASE_URLto your PostgreSQL connection string when using server submission/admin exports. - Keep
ENABLE_SERVER_SUBMISSION="false"andNEXT_PUBLIC_ENABLE_SERVER_SUBMISSION="false"for browser-only local demo mode. - Set a long random
ADMIN_EXPORT_TOKENbefore exposing admin routes. - Do not commit
.envor real secrets.
npm run db:generate
# equivalent: npx prisma generateFor local development databases:
npm run db:dev
# equivalent: npx prisma migrate devFor production or CI/CD deployment databases:
npm run db:migrate
# equivalent: npx prisma migrate deployThese scripts wrap npx prisma migrate dev and npx prisma migrate deploy, respectively.
npm run devOpen:
http://localhost:3000
Local demo mode works without a database when server submission is disabled. In that mode, participant progress remains in the browser and completed sessions can still be copied or downloaded as JSON.
npm run typecheck
npm run lint
npm run build
npm run startThen open:
http://localhost:3000
npm run dev starts the hot-reloading development server. npm run build compiles the production bundle and catches build-time TypeScript, Next.js, and bundling issues that may not appear during development.
Start from .env.example for local or server configuration.
| Variable | Required? | Example | Used by | Description |
|---|---|---|---|---|
DATABASE_URL |
Required for server submission and admin exports | postgresql://hcg:password@localhost:5432/hidden_cost_game?schema=public |
Prisma, submission API, admin API | PostgreSQL connection string. Local demo mode can run without it if server submission is disabled. |
APP_BASE_URL |
Recommended for deployment and export scripts | https://your-domain.com |
Export script, admin dashboard curl examples | Base URL for deployed app and command-line export helper. |
ENABLE_SERVER_SUBMISSION |
Required to store participant submissions | true |
Server API | Server-side gate for POST /api/submissions. If not true, server submission returns disabled. |
NEXT_PUBLIC_ENABLE_SERVER_SUBMISSION |
Required to show participant submission UI | true |
Browser UI | Build-time public flag. Controls whether participants see the optional server submission panel. Set it to true only when the server endpoint and database are ready. |
ADMIN_EXPORT_TOKEN |
Required for admin dashboard/API | replace-with-a-long-random-secret |
/admin, /api/admin/* |
Bearer token used to fetch stats and exports. Keep secret and rotate if exposed. |
SUBMISSION_RATE_LIMIT_WINDOW_MS |
Optional | 60000 |
Submission API | In-memory rate-limit window for submission attempts. Defaults to 60000 ms. |
SUBMISSION_RATE_LIMIT_MAX |
Optional | 20 |
Submission API | Maximum submissions per client key per window. Defaults to 20. |
MAX_SUBMISSION_BODY_BYTES |
Optional | 250000 |
Submission API | Maximum accepted submission body size. Defaults to 250000 bytes. |
EXPORT_SUBMISSIONS_LIMIT |
Optional | 500 |
Export script | Optional default page size for npm run export:submissions; command-line --limit takes precedence. |
GOOGLE_SHEETS_WEBHOOK_URL |
Optional | https://script.google.com/macros/s/.../exec |
Submission API | Optional Google Apps Script webhook URL. When set, successfully stored submissions are mirrored to Google Sheets after PostgreSQL save. |
GOOGLE_SHEETS_WEBHOOK_SECRET |
Recommended with Sheets mirror | replace-with-a-long-random-secret |
Submission API, Apps Script | Shared secret sent to the webhook for verification. Keep it private and do not append it to sheet rows. |
CONSENT_VERSION |
Optional server metadata fallback | pilot-consent-v1 |
Submission API | Stored as a fallback if a submitted payload lacks consentVersion. The client export currently uses the version constant in utils/researchMetrics.ts. |
SCHEMA_VERSION |
Present in .env.example; not currently read by app code |
research-export-v1 |
Deployment convention only | Reserved/configuration note for schema versioning. The client export currently uses the schema version constant in utils/researchMetrics.ts. |
For Docker Compose, .env.example also includes POSTGRES_USER, POSTGRES_PASSWORD, and POSTGRES_DB for the bundled PostgreSQL service. In NODE_ENV=production, npm run start refuses to run if ADMIN_EXPORT_TOKEN, POSTGRES_PASSWORD, or DATABASE_URL still contain the example secret values.
PostgreSQL remains the primary source of truth for completed research submissions. You can also mirror a flattened summary row to a Google Sheet by deploying the ready-to-copy Google Apps Script receiver in docs/google-sheets-apps-script.js and setting GOOGLE_SHEETS_WEBHOOK_URL. If the Sheets webhook is missing, slow, invalid, or returns an error, POST /api/submissions still returns success to the participant after the PostgreSQL write succeeds; the webhook result is only logged server-side.
Setup steps:
- Create a Google Sheet. The script will create a
Submissionstab if it does not already exist, and it will write the expected header row if row 1 is empty. - Open Extensions → Apps Script, paste the contents of
docs/google-sheets-apps-script.jsintoCode.gs, and save. - In Project Settings → Script properties, add
WEBHOOK_SECRETwith the same long random value you will set asGOOGLE_SHEETS_WEBHOOK_SECRETin the app. - Deploy the Apps Script as a web app and copy its
/execURL into.envasGOOGLE_SHEETS_WEBHOOK_URL; set the same secret inGOOGLE_SHEETS_WEBHOOK_SECRET. - Restart the app so the server process reads the new environment variables.
- Submit one complete test session and verify both the app logs and the new Google Sheet row.
The app sends the shared secret in the JSON body as secret because Google Apps Script web apps do not always expose request headers consistently. The provided script validates data.secret but deliberately excludes it from the HEADERS array so the secret is never appended to the sheet.
For a step-by-step Ubuntu VPS deployment walkthrough, see VPS Deployment Guide with Docker Compose.
The Docker Compose setup runs the Next.js app and a private PostgreSQL service. Postgres is not exposed with a public host port by default.
cp .env.example .env
# edit .env
# at minimum, change secrets and set APP_BASE_URL for deployment
docker compose up --build -d
docker compose exec app npm run db:migrate
curl http://localhost:3000/api/healthFor data collection, set both submission flags before building/restarting:
ENABLE_SERVER_SUBMISSION="true"
NEXT_PUBLIC_ENABLE_SERVER_SUBMISSION="true"Use a reverse proxy such as Caddy or Nginx in front of the app for public deployments, and enable HTTPS before collecting pilot data.
Requirements:
- Node.js 22 LTS
- npm
- PostgreSQL
- A configured
DATABASE_URL - A private
.envwith deployment secrets
Typical setup:
npm ci
npm run db:generate
npm run db:migrate # runs npx prisma migrate deploy
npm run build
npm run startA PM2 example:
npm install -g pm2
pm2 start npm --name hidden-cost-game -- start
pm2 saveIn production, place the Node process behind HTTPS with a reverse proxy and keep PostgreSQL private to the server or private network.
Data collection is local-first and only reaches the researcher after an explicit optional submission step:
Participant browser
→ localStorage
→ optional POST /api/submissions
→ PostgreSQL
→ /admin dashboard or admin CSV/JSON endpoints
Important operational details:
- Participant progress is saved in browser
localStorageas the session proceeds. - Data does not reach the researcher unless server submission is enabled and the participant clicks submit.
- Server submission requires both the server flag (
ENABLE_SERVER_SUBMISSION=true) and the browser UI flag (NEXT_PUBLIC_ENABLE_SERVER_SUBMISSION=true). - No direct identifiers are requested in the participant flow.
- Participants are instructed not to enter names, contact details, or other identifying information in open text responses.
- The research payload does not store IP addresses.
- The server stores a hashed user-agent value as operational metadata, not in the research payload.
- Researchers can download collected rows as JSON or CSV from
/adminor from the protected admin API endpoints.
The dashboard is available at:
/admin
It:
- Requires
ADMIN_EXPORT_TOKEN; the researcher enters the token in the browser. - Shows total submissions, completed submissions, first/last submission timestamps, and high/low coverage counts.
- Shows average computed metrics for submitted rows.
- Shows recent submissions sorted newest first.
- Downloads JSON and CSV from the browser.
- Stores the entered token in
sessionStorageby default; the optional “remember” checkbox stores it in browserlocalStorage.
The dashboard is intentionally not a full authentication system. Use it only over HTTPS. For stronger protection, put /admin and /api/admin/* behind reverse proxy basic auth, an allowlist, institutional SSO, or a VPN.
- Open
/adminon the deployed site. - Enter
ADMIN_EXPORT_TOKEN. - Click Download CSV or Download JSON.
JSON:
curl -H "Authorization: Bearer YOUR_ADMIN_EXPORT_TOKEN" \
"https://your-domain.com/api/admin/submissions?limit=100" \
-o submissions.jsonCSV:
curl -H "Authorization: Bearer YOUR_ADMIN_EXPORT_TOKEN" \
"https://your-domain.com/api/admin/submissions.csv" \
-o submissions.csvJSON:
curl -H "Authorization: Bearer YOUR_ADMIN_EXPORT_TOKEN" \
"http://localhost:3000/api/admin/submissions?limit=100" \
-o submissions.jsonCSV:
curl -H "Authorization: Bearer YOUR_ADMIN_EXPORT_TOKEN" \
"http://localhost:3000/api/admin/submissions.csv" \
-o submissions.csvThe export:submissions script saves a JSON file under exports/ using APP_BASE_URL and ADMIN_EXPORT_TOKEN:
APP_BASE_URL="https://your-domain.com" ADMIN_EXPORT_TOKEN="YOUR_ADMIN_EXPORT_TOKEN" npm run export:submissionsThe JSON endpoint is paginated and supports limit up to 500 plus cursor for additional pages. The CSV endpoint exports all rows available to the admin token.
The database stores each submitted session in the ResearchSubmission table. The full research export is stored as a JSON payload, while selected relational columns are stored for indexing, filtering, and export metadata.
Stored relational fields include:
idsessionIdschemaVersionexportVersionconsentVersionassignedDisplayedProfileassignedHiddenProfilecompletedGameRoundssubmittedAtpayloadappVersionuserAgentHashcreatedAt
The full JSON research export includes:
participantProfileassignedProfilegameSummarygameRoundspreRevealSurveypostRevealSurveycomputedMetricscompletenessconsentVersionschemaVersion- session and timestamp fields
- optional server submission status metadata
CSV export flattens core fields for analysis, including assignment condition, final scores, treatment-choice counts, computed metrics, survey responses, open-text responses, and participant background variables.
All main Likert items use 1–7 response scales. Open text fields require 10–500 characters. These are prototype measures, not validated scales.
Implemented measures include:
- Primary attribution: the participant's initial explanation for lower scores based only on visible results.
- Individual responsibility: perceived responsibility of lower-scoring players.
- Constraint suspicion: pre-reveal expectation that players may have faced different constraints.
- Protest/objecting legitimacy: perceived legitimacy of objections by lower-scoring players.
- Rule correction support: support for adjusting rules or scoring when hidden constraints are present.
- Redistribution support: support for transferring points from higher-scoring to lower-scoring players under confirmed hidden disadvantage.
- Confidence: confidence in the initial interpretation.
- Information sufficiency: perceived adequacy of available information before the reveal.
- Structural impact: post-reveal perceived effect of the hidden cost difference.
- Perspective change: post-reveal reported change in views of lower-scoring players.
- Remembered initial judgment: post-reveal memory of the participant's pre-reveal attribution, responsibility rating, constraint-suspicion rating, and confidence in that memory.
Computed metrics are derived from game and survey responses for prototype analysis:
responsibilityShift: post-reveal individual responsibility minus pre-reveal individual responsibility.constraintRecognitionShift: post-reveal structural impact minus pre-reveal constraint suspicion.protestLegitimacyShift: post-reveal protest legitimacy minus pre-reveal protest legitimacy.ruleCorrectionSupportShift: post-reveal rule-correction support minus pre-reveal rule-correction support.redistributionSupportShift: post-reveal redistribution support minus pre-reveal redistribution support.certaintyCorrection: pre-reveal confidence minus post-reveal rating of initial judgment accuracy.informationCaution:8 - informationSufficiency; higher values indicate less felt information before the reveal.perspectiveChange: post-reveal rating of how much the reveal changed views of lower-scoring players.burden: total treatment cost paid divided by total available income.careAvoidance: skipped treatments plus half of partial treatments.attributionCategoryShift: pre-reveal primary attribution compared with post-reveal revised primary attribution.rememberedResponsibilityError: remembered pre-reveal responsibility minus the original pre-reveal responsibility rating.rememberedConstraintSuspicionError: remembered pre-reveal constraint suspicion minus the original pre-reveal constraint-suspicion rating.rememberedPrimaryAttributionMatchesOriginal: whether remembered and original pre-reveal primary attribution match.memoryConfidence: confidence in memory of the initial interpretation.memoryDistortionMagnitude: absolute remembered responsibility error plus absolute remembered constraint-suspicion error.
This project is maintained by Dr. Mohammad Moradi.
- Email: dr.moradi@gmail.com
- LinkedIn: https://www.linkedin.com/in/mohammad-moradik/
Thoughtful feedback, methodological suggestions, replication ideas, and collaboration inquiries are very welcome. If you are interested in the project or have comments on the study design, please feel free to get in touch.
This project uses incomplete information and should be reviewed carefully before use with real participants.
Key points:
- No direct identifiers are requested.
- Participants are told not to enter identifying information in open text responses.
- Data remains local-only until optional server submission is enabled and the participant chooses to submit.
- Incomplete information is used during the game and interpretation stage, then revealed before optional server submission.
- Participants can stop before submission; in that case, no data reaches the researcher through the app.
- The prototype is exploratory and not a validated instrument.
- Formal research use may require ethics review, IRB review, consent materials, withdrawal instructions, accessibility review, and data-management planning.
- Browser
localStorageis not secure research storage and can be cleared or accessed by someone using the same browser profile. - Server deployment must use HTTPS before collecting pilot data.
- Researchers must protect
ADMIN_EXPORT_TOKENand database credentials. - Deletion is not self-service unless a study-specific deletion workflow is implemented. Manual deletion is possible for known records through database operations.
For pilot deployment:
- Use HTTPS for all participant and admin traffic.
- Keep
.envprivate and out of version control. - Use a long, random
ADMIN_EXPORT_TOKEN. - Rotate
ADMIN_EXPORT_TOKENimmediately if it may have leaked. - Do not expose PostgreSQL publicly.
- Back up the database before updates, migrations, or deletion.
- Put
/adminand/api/admin/*behind stronger access controls for real deployments. - Security headers are configured in
next.config.mjs. - Submission rate limiting is in-memory and prototype-level; it is not enterprise-grade abuse protection and will reset when the app process restarts.
A production deployment should use the same reproducible setup as a fresh clone or CI runner:
npm ci
npm run db:generate
npm run db:migrate # runs npx prisma migrate deploy
npm run build
npm run startRequired deployment environment variables for server collection are:
DATABASE_URLfor PostgreSQL storage and Prisma migrations.ENABLE_SERVER_SUBMISSION=truesoPOST /api/submissionsaccepts submissions.NEXT_PUBLIC_ENABLE_SERVER_SUBMISSION=truebeforenpm run buildso the participant UI exposes server submission.ADMIN_EXPORT_TOKENfor/adminand/api/admin/*exports.APP_BASE_URLfor export helpers and deployment-specific examples.
Production startup validation refuses the example ADMIN_EXPORT_TOKEN and PostgreSQL password values, and also refuses ENABLE_SERVER_SUBMISSION=true without DATABASE_URL. This makes placeholder secrets fail closed instead of being silently accepted.
Optional operational controls are SUBMISSION_RATE_LIMIT_WINDOW_MS, SUBMISSION_RATE_LIMIT_MAX, and MAX_SUBMISSION_BODY_BYTES. They default to safe prototype values when unset, but production deployments should review them for the expected participant volume and hosting topology.
Before collecting pilot data:
- Set production environment variables in
.envor hosting configuration. - Set
APP_BASE_URLto the deployed HTTPS origin. - Set
ENABLE_SERVER_SUBMISSION=trueon the server. - Set
NEXT_PUBLIC_ENABLE_SERVER_SUBMISSION=truebefore build so participants see the submission UI. - Use a strong
ADMIN_EXPORT_TOKEN. - Replace the example
POSTGRES_PASSWORDbefore starting production containers. - Install exactly from the lockfile:
npm ci. - Generate Prisma client:
npm run db:generate. - Run database migration:
npm run db:migrate(npx prisma migrate deploy). - Run build checks:
npm run typecheck,npm run lint, andnpm run build. - Verify health:
curl http://localhost:3000/api/healthor the equivalent deployed URL. - Complete one test participant session.
- Submit the test session.
- Verify
/adminstats show the test submission. - Export JSON.
- Export CSV.
- Delete test submissions if needed and permitted by the study plan.
- Configure database backups.
- Configure a reverse proxy.
- Enable HTTPS.
- Confirm PostgreSQL is not publicly reachable.
- Confirm Node.js 22 LTS is active:
node --version. - Fresh clones and CI should use
npm ci, notnpm install. - If build works on one machine but not another, remove local install artifacts and reinstall from the lockfile:
rm -rf node_modules && npm ci. - Use
npm installonly when intentionally changing dependencies and committing the resultingpackage-lock.json.
Run the app on another port:
PORT=3001 npm run devor stop the process currently using port 3000.
- Local demo mode can run without
DATABASE_URLif server submission is disabled. - Server submission API will not work without
DATABASE_URL. - Server submission and admin exports require
DATABASE_URL. - For Docker Compose, the app service builds
DATABASE_URLfromPOSTGRES_USER,POSTGRES_PASSWORD, andPOSTGRES_DB.
Run:
npx prisma generateor the equivalent package script:
npm run db:generateThen restart the app process. This is required on fresh clones before TypeScript can use the generated Prisma client types reliably.
- Confirm PostgreSQL is running and reachable.
- Confirm
DATABASE_URLpoints to the intended database. - For Docker, verify the database is healthy:
docker compose ps. - Re-run:
docker compose exec app npm run db:migrate. - Back up before manual repair or production migration changes.
- Confirm the request includes
Authorization: Bearer YOUR_ADMIN_EXPORT_TOKEN. - Confirm the token exactly matches the server's
ADMIN_EXPORT_TOKEN. - Restart the app if the environment variable changed.
- Re-enter
ADMIN_EXPORT_TOKEN; the implemented dashboard does not use a separate dashboard password. - Clear remembered admin token from the dashboard or browser storage if an old token was saved.
- Confirm
/api/admin/statsworks with curl and the same token.
- Set
NEXT_PUBLIC_ENABLE_SERVER_SUBMISSION=truebefore building the app. - Rebuild and restart; public
NEXT_PUBLIC_*values are embedded into the client bundle. - Also set
ENABLE_SERVER_SUBMISSION=trueso the server accepts submissions.
- Confirm the submission response showed success.
- Confirm
ENABLE_SERVER_SUBMISSION=trueandDATABASE_URLare set on the running server. - Check
/api/healthforserverSubmissionEnabledanddatabaseConfigured. - Confirm migrations were run.
- Confirm the admin dashboard is pointed at the same deployment/database that received the submission.
- Start both services:
docker compose up --build -d. - Check health and logs:
docker compose psanddocker compose logs postgres app. - Use the Compose service host (
postgres) inside Docker, notlocalhost, for app-to-database connections. - Ensure
POSTGRES_USER,POSTGRES_PASSWORD, andPOSTGRES_DBmatch the composedDATABASE_URL.
- Use Reset study session in the export panel, or clear site data in the browser.
- For manual browser-console cleanup, remove
hidden-cost-game-sessionfromlocalStorage.
Scripts currently defined in package.json:
| Script | Purpose |
|---|---|
npm run dev |
Starts the Next.js development server. |
npm run build |
Builds the production Next.js app. |
npm run start |
Starts the production server after a successful build. |
npm run lint |
Runs the Next.js ESLint check. |
npm run typecheck |
Runs TypeScript with tsc --noEmit. |
npm run db:generate |
Generates the Prisma client. |
npm run db:dev |
Runs Prisma migrate in development mode. |
npm run db:migrate |
Applies existing Prisma migrations in deployment mode. |
npm run db:studio |
Opens Prisma Studio for database inspection. |
npm run validate:sample |
Validates the sample research export JSON. |
npm run export:submissions |
Downloads admin JSON submissions from APP_BASE_URL using ADMIN_EXPORT_TOKEN and saves them under exports/. |
- The decision game is simplified and does not model real healthcare systems, insurance contracts, illness severity, household budgets, or clinical outcomes.
- The survey measures are prototypes and are not validated scales.
- The visible score table is fictional and intended to support the experimental flow.
- The hidden-rule reveal may create demand effects or cue participants toward particular interpretations.
- Online samples may have attention, comprehension, device, and representativeness limitations.
- Results should be interpreted as exploratory prototype data only.
- The app does not currently include a self-service deletion workflow.
- The admin dashboard is intentionally minimal and is not a full authentication or research data-management system.
License: MIT. See LICENSE.
Project status: Research prototype. Suitable for controlled pilot testing after deployment review.