Documentation
Tutorials
Workflows
Kebab actions
Daily Work
Dashboard & Reports
Quality Control
ISO 17025
Logs
Contacts
Billing
Analysis Setup
Settings
Initial Setup
Help & Reference
Architecture decisions
Architecture decisions
0:000:00

Why every sample carries two timestamps

The concrete problem in the field

The sampler is standing in a vineyard near Nemea.

It's 07:42, the fog hasn't lifted,

and the phone shows "no signal."

She works the core auger,

takes a sample from three points along the row,

snaps a photo, captures the GPS coordinates,

and taps "Save" inside the /collect route.

The sample has been collected — but the LIMS server doesn't know about it yet.

It will find out at 09:15, when the truck crests the ridge and reconnects to 4G.

That gap — anywhere from a few minutes to several hours, occasionally days —

is the trust window every offline-first system has to confront.

An ESYD assessor will ask: "When exactly did the sampling happen?"

— and the answer cannot be "whenever our server received it."

The decision: two distinct timestamps

Every Sample row carries two separate timestamps in the database:

  • collectedAt (datetime(6) on the samples table) — the moment of observation,

stamped from the sampler's device clock

when they tapped "Save" in the field.

This is the field that ISO 17025 §7.3.3(b) explicitly requires

("date AND time of sampling").

  • createdAt (@CreateDateColumn inherited from Common) —

the moment the record reached the LIMS server

and the mutation succeeded. Stamped by the server's clock.

One timestamp is not enough.

If we kept only createdAt, we would lose the real observation moment

— the day's weather, the field conditions,

the simultaneity with other events become unrecoverable.

If we kept only collectedAt, we lose the audit trail of when the record actually entered the LIMS,

and an assessor could not tell

whether a record landed late or was edited after the fact.

What §7.11.3 actually demands

ISO/IEC 17025:2017 §7.11.3 requires "protection and integrity" of the LIMS

— including a defensible record

of when each event actually occurred.

The (collectedAt, createdAt) pair satisfies three points of the clause at once:

  • the observation event is immutable and clearly separated from sync mechanics,
  • the gap between the two is measurable

— an assessor can see exactly how long the record took to reach the LIMS,

  • when the lab marks "received at the lab,"

the third stamp receivedDate (the COLLECTED → RECEIVED transition

via markSampleReceivedAtLab) closes the custody chain.

Where the pair surfaces in the UI

In the sample-detail dialog, a "Sample Timestamp" block

above the status-history timeline shows both stamps side-by-side,

plus a "Sync delay" label

when the gap exceeds 5 minutes

(otherwise suppressed so walk-in samples stay clean).

The sync-status chip in the navbar shows in real time

how many records are still waiting to reach the server.

The non-obvious consequence

Any audit query that correlates samples with "what was happening that day"

— meteorological data, personnel shifts, consumable lots,

instrument calibration reports —

must join on collectedAt, not createdAt.

Otherwise a sample collected on Tuesday but synced on Thursday

will appear falsely correlated with Thursday's events.

This subtlety lives in the traceability queries — not the screens —

which is why it deserves to be written down explicitly here.