Commit Graph

20 Commits

Author SHA1 Message Date
Experiments DB Dev b0c6217cb4 fix(backend): widen plot_config size cap to fit a full hiddenSubjects list
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:30:06 -04:00
Experiments DB Dev e9511bdac3 feat(backend): persist per-experiment plot_config via PUT /:id/plot-config
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 10:14:50 -04:00
Experiments DB Dev fe4e89f497 feat(backend): PATCH /animals/:id/csv-config endpoint
Implement TDD for new route that accepts CSV configuration with validated buckets.
Route uses auditMiddleware to log config changes and delegates validation to
validateCsvConfig helper from Task 2.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-01 07:49:00 -04:00
Experiments DB Dev 1ae55591f4 feat(backend): add csv_analysis_config validator 2026-05-01 07:47:17 -04:00
Experiments DB Dev 7b023fb8a3 feat(db): add csv_analysis_config JSON column to animals 2026-05-01 07:43:58 -04:00
Experiments DB Dev ee2cf19b74 test: add analysisOnly regression tests; fix cache isolation in tests
New file tests/experimentDailyStatuses.test.js — 9 tests covering
GET /api/experiments/:id/daily-statuses:
  - 404 on unknown experiment
  - all statuses returned when no filter
  - analysisOnly=true keeps only non-null analysis_summary records
  - analysisOnly=true returns [] when none have analysis_summary
  - the exact null/object mix that caused the production 500 now passes
  - analysisOnly=false (explicit) returns all records
  - ?date= param is forwarded to Prisma correctly
  - empty-array response on zero statuses
  - returned records include expected fields

Fixed tests/calendar.test.js: existing tests were silently broken by the
cache added in the perf commit. Both test files now mock lib/cache to
always miss, so each test exercises the real route logic in isolation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 15:11:21 -04:00
Experiments DB Dev d125d2e365 fix: filter analysis_summary in JS instead of Prisma WHERE clause
Prisma 5 rejects both null and Prisma.DbNull as WHERE filter values for
Json? fields (no clean IS NOT NULL operator). Filter the result array in
JS after the query — the output is immediately cached so this only runs
once per cache period.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 14:54:08 -04:00
Experiments DB Dev c0d0b8e6ba fix: use Prisma.DbNull to filter non-null Json? field in analysisOnly
Prisma 5 rejects JS null as a where-clause value on Json? fields.
Use Prisma.DbNull (SQL NULL sentinel) so the NOT filter compiles correctly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 14:47:50 -04:00
Experiments DB Dev d2c8d69718 perf: server-side caching + remove bulk status fetch from experiment page
- Add backend/src/lib/cache.js — in-process TTL Map cache with
  prefix-based invalidation; no external dependencies

- Cache /experiments/:id/calendar?field=X per (experimentId, field).
  Invalidated immediately on any DailyStatus write.

- Cache /experiments/:id/daily-statuses per (experimentId, date, analysisOnly).
  Invalidated immediately on any DailyStatus write (create/update/delete
  and analysis-summary saves all clear the relevant prefix).

- Add ?analysisOnly=true to /daily-statuses: filters rows where
  analysis_summary IS NOT NULL. Charts are the only consumer and already
  discard rows without analysis data; pre-filtering server-side avoids
  sending the full status payload (71 rows → 1 for this experiment).

- ExperimentCalendar: remove allStatuses prop, fetch calendar data
  directly via API. The component now issues one small GET per field
  selection change instead of the parent loading thousands of rows upfront.

- ExperimentDetail: pass analysisOnly:true to getDailyStatuses;
  drop allStatuses prop from ExperimentCalendar. Cold experiment page
  load no longer fetches every daily status record.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 14:03:59 -04:00
Experiments DB Dev 52d5fca5d1 feat: store matching metadata on session file registration
Each saved file now records three additional fields:
  matched_identifier    — the literal animal_id_string or animal_name
                          substring that matched in the filename
  animal_id_string_snap — snapshot of animal.animal_id_string at
                          registration time
  animal_name_snap      — snapshot of animal.animal_name at
                          registration time

This lets future consumers (scripts, exports, re-matching) identify
the subject from the file record alone, even if animal data changes.
The matched identifier is also shown in the drop preview UI (↳ 2852).

Tests: 25 total (+2 for new payload fields).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:41:36 -04:00
Experiments DB Dev daf36908ef feat: session file registry with drag-and-drop on day view
Adds a FileDropZone at the bottom of the day view where users can
drop all session files at once. Filenames are regex-matched to
subjects using their animal_id_string (word-boundary aware, so
"2852" won't match "28520"). Only metadata is recorded — files
are never uploaded.

Backend: new session_files table (id, daily_status_id, filename,
file_size BIGINT, file_type, last_modified, notes). Routes:
  POST /api/daily-statuses/:id/files  (batch create)
  GET  /api/daily-statuses/:id/files
  DELETE /api/session-files/:id

Frontend: FileDropZone shows per-subject match groups, unmatched
files, and a warning for subjects with no match. After saving,
registered files are displayed with delete capability.

test-files/: generate_test_files.sh creates 4 dummy files per
subject (2×bin, 2×csv) with correct naming for drag-and-drop
testing. Pre-generated for 2026-04-23 with IDs 2852/3076/3077/3078.

Tests: 19 tests covering drag state, regex matching, save payload,
partial-overlap prevention, multi-subject batching, and delete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 13:12:45 -04:00
Experiments DB Dev c8f0c5f298 feat: replace calendar day modal with dedicated day-view page
Calendar day clicks now navigate to /experiments/:id/day/:date
(with ?field= param). ExperimentDayView shows a sticky-column table
of all subjects with data that day, inline edit via modal, and a
session-metrics bar chart from analysis_summary. Subject name links
to /daily-statuses/:id. Backend adds ?date= filtering to
GET /experiments/:id/daily-statuses.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 12:46:35 -04:00
Experiments DB Dev 9535f86970 feat: add experiment calendar view with per-day subject counts
New ExperimentCalendar component added as a third view mode (List / Group / Calendar)
on the Experiment Detail page.

Backend:
- GET /api/experiments/:id/calendar?field=<fieldIdOrBuiltinKey>
  Returns { field, days: { "YYYY-MM-DD": count } } where count = number of
  subjects with a non-null/non-empty value for the chosen field on that date.
  Supports both builtin fields (vitals, treatment, notes, experiment_description)
  and custom fields addressed by fieldId UUID.

Frontend:
- ExperimentCalendar component: navigable monthly grid, field selector
  (defaults to first field with "weight" in label, else first active field),
  blue badges showing subject count per day, click to open modal with all
  subjects' data for that day.
- Integrated into ExperimentDetail as displayMode "calendar", persisted
  in localStorage alongside the existing list/group modes.
- experimentsApi.getCalendar() added to API client.

Tests:
- backend/tests/calendar.test.js: 8 unit tests (404, empty days, builtin
  count, custom field count, whitespace ignored, multi-month, field echo,
  missing param).
- frontend/tests/ExperimentCalendar.test.jsx: 13 RTL tests covering
  rendering, default field selection, count badges, month navigation,
  field-change re-fetch, day click modal, and multi-subject display.

All 47 backend + 13 calendar frontend tests passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 09:26:35 -04:00
Experiments DB Dev b7374777d3 feat(template): stable fieldId per field — custom_fields keyed by UUID, not label slug 2026-04-15 14:01:14 -04:00
Experiments DB Dev 86a56427b7 feat(template): per-experiment daily record template — rename/add/hide fields, custom_fields JSONB 2026-04-15 13:56:17 -04:00
Experiments DB Dev 80fb1c6e27 fix(docker): openssl in Alpine, binaryTargets for Prisma, IPv4 healthcheck, start_period for migration delay 2026-04-15 13:43:20 -04:00
Experiments DB Dev 9b3c883bf0 fix(security): rate limiting, CORS restriction, tableName allowlist, non-root Docker, migration entrypoint, aria-live 2026-04-15 13:25:18 -04:00
Experiments DB Dev 5874ed8374 feat(tests): Jest backend (38 tests), Jest RTL frontend (28 tests), Playwright E2E spec 2026-04-15 13:23:48 -04:00
Experiments DB Dev 04d1eee8f8 feat(backend): Prisma schema, Express routes, audit middleware, error handling 2026-04-15 10:40:39 -04:00
Experiments DB Dev 44dfe3fdc4 chore: initialize project structure, Docker setup, docker-compose on port 52867 2026-04-15 10:38:41 -04:00