# The .well format
> An open JSON format for the complete record of a water well, from construction and lithology to tests, operation, permits and water quality.
A `.well` file is the record of one water well, written as a single JSON document. It holds what a drilling report, a well log and the well's later history hold today, in a form that software can read, check and draw: the borehole and its casing, the lithological column, pumping tests, water levels, permits, meters and water analyses.
The format is open (Apache 2.0) and defined by a written [specification](/format/spec). Any tool can read and write it. [welldot.org](https://welldot.org) is one editor among them, and the [`@welldot/*` packages](/core) are one implementation.
::well-preview{example="estrela-doeste-sp-bauru-serra-geral"}
::
## Where to start
::card-group
:::card{title="What is .well?" icon="i-lucide-file-braces" to="/format/getting-started/what-is-well"}
What a file holds, why it exists and how it relates to the spec and the packages.
:::
:::card{title="For geologists" icon="i-lucide-mountain" to="/format/getting-started/for-geologists"}
How a drilling report maps onto the file: lithology, construction, tests, operation, permits and water quality.
:::
:::card{title="For developers" icon="i-lucide-code" to="/format/getting-started/for-developers"}
Read, validate and write `.well` files from code, and what the schema guarantees.
:::
:::card{title="Your first .well file" icon="i-lucide-file-plus" to="/format/getting-started/first-well-file"}
Write a small, valid file by hand and see it drawn.
:::
::
## How this section is organized
- **Getting started** introduces the format for each kind of reader.
- **Concepts** explains the ideas the whole format rests on: depths, units, vocabularies, profiles, versions and extensions.
- **Guides** walk through tasks: writing a file, converting a report, validating, extending the format and building software on it.
- **Reference** lists every object and field (generated from the schema), links the JSON Schema and shows the example wells.
- **Specification** is the normative text, rendered as it is written in the repository. When a guide and the spec seem to differ, the spec is right.
- **Changelog** lists the revisions of the format.
::note
These pages explain the format and link to the rules. The rules themselves live only in the [specification](/format/spec), so there is a single source to cite.
::
# What is .well?
> What a .well file holds, the problem it solves, and how the specification, the JSON Schema and the welldot packages fit together.
`.well` is an open file format for water well data. One file describes one well: how it was built, what rock it crosses, what was measured in it and what happened throughout its life. The file is plain JSON, so it opens in any text editor and any programming language can read it.
## The problem
Most well records live in scanned drilling reports, PDF profiles, spreadsheets and the closed formats of individual programs. A person can read them; software cannot. Comparing a hundred wells means retyping a hundred reports, and a profile drawn in one program cannot be edited in another. Registries, consultants and researchers each rebuild the same data by hand.
A `.well` file records the same facts once, as structured data that can be validated, searched, compared, drawn and archived.
## Key principles
The [design principles](/format/spec/v2/overview#design-principles) shape every field. The ones a new reader meets first:
- **Self-describing.** Field names are written out in full (`well_screen`, `static_level`), so a file can be read without the spec at hand.
- **One unit per quantity.** Depths are meters, diameters millimeters, flow rates m³/h. The file never declares units. See [Units and precision](/format/concepts/units-and-precision).
- **Ground level is zero.** Depths are measured down from the ground surface. See [Depths and intervals](/format/concepts/depths-and-intervals).
- **Graceful incompleteness.** A record with only a static level is still a valid record. Unknown values are left out, not invented.
- **Open to extension.** Fields and blocks prefixed with `x-` carry data the spec does not define, and every reader keeps them. See [Extensibility](/format/concepts/extensibility).
## What a file holds
| Part | What it records | Main blocks |
| ------------- | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Identity | Name, construction method, purpose, driller, location, registry identifiers | `name`, `well_type`, `well_purpose`, `location`, `well_id` |
| Construction | Borehole, casing, screens, reductions, gravel pack and seals, centralizers, wellhead pad | `bore_hole`, `well_case`, `well_screen`, `reduction`, `surface_case`, `hole_fill`, `centralizers`, `cement_pad` |
| Geology | Lithological column, fractures, cavities | `lithology`, `fractures`, `caves` |
| Hydraulics | Water levels, pumping tests, interpreted aquifer parameters | `hydrodynamic_events`, `aquifer_analysis` |
| Operation | History log, pumps, permits, meters, production, operating regime, documents | `history_logs`, `pump_installations`, `permits`, `meters`, `production`, `operating_regime`, `attachments` |
| Water quality | Samples and their field and laboratory results | `water_samples` |
The full list, in order, is the [top-level structure](/format/spec/v2/format-reference#top-level-structure) in the specification. Every object has a [reference page](/format/reference/objects) generated from the schema.
## What it is for
The specification names three uses ([Purpose](/format/spec/v2/overview#purpose)):
- **Visualization.** Enough construction and geological detail to draw an accurate well profile at true depth scale.
- **Registration.** A complete static record that can be submitted to a regulator, stored in a registry and archived.
- **Research.** Structured geology, tests and analyses that support comparison across many wells.
## The pieces
```mermaid
flowchart LR
S["Specification (spec/, plain Markdown)"] --> J["JSON Schema (generated)"]
S --> C["@welldot/core (types, validation)"]
C --> J
C --> U["@welldot/utils (analysis)"]
C --> R["@welldot/render (profile drawing)"]
C --> A["Your tool"]
J --> A
```
- The **specification** is the standard. It is versioned Markdown in the [`spec/` folder](https://github.com/welldothq/welldot/tree/main/spec) of the repository, and this site renders it under [Specification](/format/spec).
- The **JSON Schema** is generated from the reference implementation and published at [`https://welldot.org/schema/v2/well.schema.json`](https://welldot.org/schema/v2/well.schema.json). See [JSON Schema](/format/reference/json-schema).
- **[`@welldot/core`](/core)** is the TypeScript reference implementation: types, validators and serialization. [`@welldot/utils`](/utils) analyzes a well, and [`@welldot/render`](/render) draws it.
- **[welldot.org](https://welldot.org)** is a free web editor built on these packages. It is not required: any program that follows the spec can read and write `.well` files.
## Next
- [For geologists](/format/getting-started/for-geologists): how the parts of a drilling report map onto the file.
- [For developers](/format/getting-started/for-developers): reading and validating files from code.
- [Your first .well file](/format/getting-started/first-well-file): a small file written by hand.
# For geologists
> How the parts of a drilling report and a well's later history map onto a .well file, explained in field terms.
This page is for geologists, hydrogeologists and drillers who know what a well report contains and want to know where each item goes in a `.well` file. No programming is needed. A `.well` file is text, and the [welldot.org](https://welldot.org) editor writes it for you if you prefer forms to JSON.
The well below is one of the [example files](/format/reference/examples): a 26 m well in the Dunas-Barreiras aquifer with two screens, a gravel pack and a cemented annulus. The profile is drawn from the file itself.
::well-preview{example="extremoz-rn-barreiras-raso"}
::
## Read a file as a report
A file is a list of named blocks. Most blocks are lists of **intervals**: a `from` depth and a `to` depth in meters, measured down from the ground surface. A casing from 0 to 8 m, a sandstone layer from 1.5 to 6 m and a screen from 8 to 11 m are all written the same way. The [depth convention](/format/concepts/depths-and-intervals) is the one idea to learn before anything else.
Measurements use one unit each, always the same: depths in meters, diameters in millimeters, flow rates in m³/h. A casing sold as 6 inches is written with its real outer diameter in millimeters. See [Units and precision](/format/concepts/units-and-precision).
## Identity and location
| In the report | In the file |
| ------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------- |
| Well name or code | `name` |
| Type of well (tubular, hand-dug, gallery) | `well_type`, the construction method only ([values](/format/spec/v2/format-reference#well_type--recommended-values)) |
| Use (supply, monitoring, piezometer) | `well_purpose`, a list ([values](/format/spec/v2/format-reference#well_purpose--recommended-values-since-v21)) |
| Driller, completion date | `well_driller`, `construction_date` (`YYYY-MM-DD`) |
| Registry numbers (SIAGAS, CNARH, a state agency) | `well_id`, one entry per authority ([rules](/format/spec/v2/format-reference#well_id)) |
| Coordinates and ground elevation | `location`, in decimal degrees on WGS84 ([rules](/format/spec/v2/format-reference#location)) |
::tip
An artesian well is not a type of construction. Write `well_type: "tubular"` and record the flowing condition as a water level above ground, which is a negative depth ([level sign convention](/format/spec/v2/format-reference#level-sign-convention)).
::
## Lithology, fractures and cavities
Each layer of the lithological description is one entry of `lithology`:
```json
{
"from": 1.5,
"to": 6,
"description": "Arenito médio a grosso, pouco argiloso, creme-amarelado",
"color": "#e2cf9a",
"texture": { "code": 607, "vocabulary": "fgdc" },
"geologic_unit": "Grupo Barreiras",
"aquifer_unit": "Sistema Aquífero Dunas-Barreiras"
}
```
- `description` is the driller's or geologist's description, kept in the language of the report.
- `texture` is the fill pattern drawn on the profile. By default it is a code from the FGDC geologic map symbolization standard; the 600 series covers sedimentary rocks and the 700 series metamorphic and igneous rocks. The [FGDC texture reference](/format/spec/reference/fgdc-textures) lists all 282 codes and marks the ones that have a drawn pattern.
- `color` is the color used for the layer, as a hex code.
- `geologic_unit` and `aquifer_unit` name the stratigraphic unit and the aquifer it belongs to.
Discrete fractures go in `fractures` with their depth, dip, azimuth and whether they yield water. Cavities, common in karst, go in `caves` as intervals. See the [Lithology](/format/spec/v2/object-schemas#lithology--lithology), [Fracture](/format/spec/v2/object-schemas#fracture--fractures) and [Cave](/format/spec/v2/object-schemas#cave--caves) schemas.
## Construction
Construction is described as the well was built (as-built), one block per component:
| Component | Block | Notes |
| --------------------------- | -------------- | ---------------------------------------------------------------------- |
| Drilled hole | `bore_hole` | One entry per diameter; a telescoping hole has several. |
| Blank casing | `well_case` | Material in `type`, outer diameter in mm. |
| Screen (filter) | `well_screen` | Slot opening in mm (`screen_slot`), screen design in `slot_type`. |
| Diameter reduction | `reduction` | Diameter at the top and at the bottom. |
| Surface (protective) casing | `surface_case` | The outer casing near the surface. |
| Gravel pack and seals | `hole_fill` | `type` is `gravel_pack` or `seal`; the material goes in `description`. |
| Centralizers | `centralizers` | An interval and a spacing, as reports give them. |
| Wellhead pad | `cement_pad` | Dimensions in meters. |
A casing that sticks up above the ground starts at a negative depth, for example `"from": -0.5`. The schemas are in [Object schemas](/format/spec/v2/object-schemas#object-schemas), and the recommended names for materials are in [Construction materials](/format/spec/v2/format-reference#construction-materials--recommended-values-since-v23).
## Water levels and pumping tests
Every measurement of the water level and every pumping test is an **event** in `hydrodynamic_events`, kept in time order. Each event has a type:
| Field work | Event `type` |
| -------------------------------------------- | ------------------ |
| Static level read during a visit | `spot_measurement` |
| Test at one constant flow rate | `constant_rate` |
| Step-drawdown test | `step_drawdown` |
| Air-lift development with an estimated yield | `airlift` |
| Recovery readings without the drawdown phase | `recovery_only` |
| Data logger series | `logger_series` |
A test holds its static level, its pumping steps (rate in m³/h, duration in minutes, readings of elapsed time and depth) and an optional recovery phase. Drawdown and specific capacity are **not** stored: software computes them from the readings, so they can never disagree with the data. See [`hydrodynamic_events`](/format/spec/v2/object-schemas#hydrodynamic_events).
The interpretation of a test is a separate entry in `aquifer_analysis`: transmissivity, storativity, Jacob coefficients, recommended flow rate, the method and the analyst, and the ids of the events it used. Several interpretations of the same test can coexist. See [`aquifer_analysis`](/format/spec/v2/object-schemas#aquifer_analysis).
## History, permits and operation
- `history_logs` is the well's logbook: maintenance, inspections, incidents and changes of status (active, inactive, decommissioned, abandoned).
- `permits` records abstraction permits (outorgas), their validity, granted flow and conditions (condicionantes), and when each condition was met.
- `pump_installations` and `meters` record each pump and totalizer for the period it was installed.
- `production` holds meter readings, from which volumes are calculated, and `operating_regime` the declared pumping hours and flow.
These blocks are optional. A drilling report alone fills the construction and geology and leaves them out.
## Water quality
Each sample is one entry of `water_samples`, with its date, sampling point, laboratory and a list of results. A result names its parameter by a code (`nitrate_as_n`, `ph`, `total_coliforms`) and gives a value, a presence or absence, or a text. Results below the detection limit keep their qualifier (`<`). The unit comes from the parameter code, so it is never written next to the value.
Drinking water limits are **not** written in the file. Software compares the results with the limit set the reader chooses (WHO, the EU directive, the Brazilian ordinance). See [Water quality](/format/spec/v2/water-quality).
## What to do with missing data
Leave it out. The format is built for [graceful incompleteness](/format/spec/v2/overview#design-principles): a field without a value is omitted, never filled with a guess, zero or `null` ([absent values](/format/spec/v2/format-reference#absent-values-since-v24)). A file with only construction and lithology is complete enough to be drawn and registered, and more can be added later.
::note
The schema requires the construction and geology blocks even when they are empty: write `"fractures": []` when the report mentions none. The [Well object reference](/format/reference/objects/well) lists which blocks are required, and [Your first .well file](/format/getting-started/first-well-file) shows the minimum.
::
## Next
- [Your first .well file](/format/getting-started/first-well-file)
- [Converting a report](/format/guides/converting-a-report), by hand or with an AI assistant
- [Depths and intervals](/format/concepts/depths-and-intervals)
# For developers
> Read, validate and write .well files from code, what the schema guarantees, and which parts of the spec matter most when you build on the format.
A `.well` file is a JSON object with a fixed set of top-level blocks. You can read it with any JSON parser and validate it with the published [JSON Schema](/format/reference/json-schema) in any language. In TypeScript or JavaScript, [`@welldot/core`](/core) gives you types, validation that matches the spec, and the v1 upgrade.
## Read and validate in TypeScript
::code-group
```bash [pnpm]
pnpm add @welldot/core
```
```bash [npm]
npm install @welldot/core
```
```bash [yarn]
yarn add @welldot/core
```
::
```ts [read.ts]
import { readFile } from 'node:fs/promises';
import { parseWell, type Well } from '@welldot/core';
const text = await readFile('PT-01.well', 'utf8');
// Throws a ZodError (with a path for each problem) when the file is invalid.
// A version 1 file is upgraded to v2 on the way in.
const well: Well = parseWell(text);
console.log(well.name, well.bore_hole.length, well.lithology.length);
```
[Parsing and validating](/core/guides/parsing-and-validating) covers the error shape, `safeParse` and legacy files. [Serializing and sharing](/core/guides/serializing-and-sharing) covers writing a file back.
## Validate in other languages
The JSON Schema (draft 2020-12) is published at a stable URL:
```text
https://welldot.org/schema/v2/well.schema.json
```
Any JSON Schema validator can use it, for example `ajv` in JavaScript, `jsonschema` in Python or `santhosh-tekuri/jsonschema` in Go. The schema is generated from the Zod validators of `@welldot/core`, so the two accept the same documents. A few rules can only be checked in code; see [Validating](/format/guides/validating).
## What the shape guarantees
- **Required fields.** `version`, `well_type` ([core identity](/format/spec/v2/format-reference#core-identity)) and the construction and geology arrays (`bore_hole`, `well_case`, `reduction`, `well_screen`, `surface_case`, `hole_fill`, `lithology`, `fractures`, `caves`, possibly empty) are required. Everything else is optional. See the [Well object](/format/reference/objects/well). Note that `parseWell` and the generated JSON Schema currently accept a file without `well_type`; write it anyway.
- **Fixed units.** Every number has one canonical unit set by the spec, never by the file ([Units](/format/spec/v2/format-reference#units)). Convert on display, not in storage.
- **Absent means unknown.** Optional fields without a value are omitted, never `null` ([Absent values](/format/spec/v2/format-reference#absent-values-since-v24)).
- **Unknown members survive.** Readers must preserve fields and blocks they do not recognize, prefixed with `x-` or not ([Extensibility](/format/spec/v2/format-reference#extensibility)). Do not strip them on save.
- **Open vocabularies.** Most string fields recommend values but accept any string. Show unknown values as they are ([Recommended vocabularies](/format/spec/v2/format-reference#recommended-vocabularies)).
- **String ids, local references.** Records point to each other by string `id` within the same file ([Cross-reference rules](/format/spec/v2/format-reference#cross-reference-and-uniqueness-rules)). A dangling reference is a warning, not an error.
::note
`parseWell` in `@welldot/core` currently accepts a file without `version`. The spec says such a file SHOULD be rejected ([parser version handling](/format/spec/v2/format-reference#parser-version-handling)), so always write it.
::
## Parts of the spec to read first
| If you are building | Read |
| ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| A reader or viewer | [Top-level structure](/format/spec/v2/format-reference#top-level-structure), [Versioning](/format/spec/v2/format-reference#versioning), [Units](/format/spec/v2/format-reference#units) |
| An editor | the above, plus [Block kinds](/format/spec/v2/format-reference#block-kinds-since-v23), [Datetime conventions](/format/spec/v2/format-reference#datetime-conventions), [Duplicate ids](/format/spec/v2/format-reference#duplicate-ids-within-a-scope) |
| A database or registry | [Cross-reference rules](/format/spec/v2/format-reference#cross-reference-and-uniqueness-rules), [`well_id`](/format/spec/v2/format-reference#well_id), [Interoperability](/format/spec/v2/interoperability) |
| A converter from another format | [Units](/format/spec/v2/format-reference#units), [Precision](/format/spec/v2/format-reference#precision-and-uncertainty), [Changes from v1](/format/spec/v2/overview#changes-from-v1) |
[Building on .well](/format/guides/building-on-well) collects the decisions that matter for editors, databases and pipelines.
## Draw and analyze
- [`@welldot/render`](/render) draws a profile into an SVG element with D3.
- [`@welldot/utils`](/utils) computes volumes, drawdown, permit status, production totals and water quality indicators.
```ts
import { parseWell } from '@welldot/core';
import { calculateHoleFillVolume, getLatestStaticLevel } from '@welldot/utils';
const well = parseWell(text);
calculateHoleFillVolume('gravel_pack', well); // m³ of gravel pack
getLatestStaticLevel(well); // m below ground, negative if flowing
```
## Test data
The 19 [example wells](/format/reference/examples) are complete, valid files covering crystalline, karst, sedimentary, flowing, hand-dug and monitoring wells, plus incomplete and inconsistent records. They ship with `@welldot/core` as `@welldot/core/examples/` and are served at `https://welldot.org/examples/`.
## Working with an AI assistant
Point your assistant at this site's [MCP server](/ai/mcp) to let it search these docs and validate the `.well` files it writes, or give it [`/llms.txt`](/ai/llms).
# Your first .well file
> Write a small, valid .well file by hand, step by step, and see the profile it draws.
This page builds a small well from an empty file: a 60 m tubular well in sandstone with one casing, one screen, a gravel pack and three lithology layers. You need a text editor. Save the result as `demo.well`.
::steps
### Start with the version and the identity
Every file starts with `version`. Files written today use `"2.4"`, as a string ([Versioning](/format/spec/v2/format-reference#versioning)). `well_type` is the construction method, here a drilled `tubular` well. The spec requires it ([core identity](/format/spec/v2/format-reference#core-identity)), although `parseWell` currently accepts a file without it.
```json
{
"version": "2.4",
"name": "Demo well, sandstone aquifer",
"well_type": "tubular",
"location": { "lat": -22.2139, "lng": -49.9458, "elevation": 512 }
}
```
`location` is optional. Coordinates are decimal degrees on WGS84 and the elevation is in meters ([location](/format/spec/v2/format-reference#location)).
### Describe the hole
The borehole is a list of intervals, one per diameter. Depths are meters below ground and diameters are millimeters ([Units](/format/spec/v2/format-reference#units)). This hole is drilled at 254 mm (10 in) to 60 m.
```json
"bore_hole": [{ "from": 0, "to": 60, "diameter": 254, "drilling_method": "rotary" }]
```
### Add the casing, the screen and the annulus
A blank casing from 0.5 m above ground to 36 m, then a screen to 54 m. The casing starts at `-0.5` because it sticks up above the ground. `hole_fill` describes the annulus: a cement seal at the top and a gravel pack around the screen.
```json
"well_case": [{ "from": -0.5, "to": 36, "type": "pvc", "diameter": 168.3 }],
"well_screen": [
{ "from": 36, "to": 54, "type": "pvc", "diameter": 168.3, "screen_slot": 0.75 }
],
"hole_fill": [
{ "from": 0, "to": 30, "type": "seal", "diameter": 254, "description": "Cement grout" },
{ "from": 30, "to": 60, "type": "gravel_pack", "diameter": 254, "description": "Gravel, 2–4 mm" }
]
```
Diameters are the real outer diameter (as-built), not the nominal size: a 6 in PVC casing is 168.3 mm ([as-built convention](/format/spec/v2/format-reference#diameter-and-the-as-built-convention)).
### Add the lithology
Each layer has a depth interval, a description, a color, a texture code for the pattern and the geologic and aquifer units. Code 607 is "massive sand or sandstone" in the [FGDC texture reference](/format/spec/reference/fgdc-textures).
```json
"lithology": [
{ "from": 0, "to": 8, "description": "Red clayey soil", "color": "#9c5a3c", "texture": { "code": 620, "vocabulary": "fgdc" }, "geologic_unit": "Cover", "aquifer_unit": "Unsaturated zone" },
{ "from": 8, "to": 30, "description": "Silty sandstone", "color": "#c9a27a", "texture": { "code": 612, "vocabulary": "fgdc" }, "geologic_unit": "Botucatu Formation", "aquifer_unit": "Guarani" },
{ "from": 30, "to": 60, "description": "Medium-grained sandstone", "color": "#e0c48f", "texture": { "code": 607, "vocabulary": "fgdc" }, "geologic_unit": "Botucatu Formation", "aquifer_unit": "Guarani" }
]
```
### Close the required lists
The construction and geology lists are required even when empty. This well has no reduction, surface casing, fractures or caves, so they are empty lists.
```json
"reduction": [],
"surface_case": [],
"fractures": [],
"caves": []
```
::
## The complete file
Put the pieces together inside one object. The preview validates the file with `@welldot/core` and draws it with `@welldot/render`.
::well-preview
```well
{
"version": "2.4",
"name": "Demo well, sandstone aquifer",
"well_type": "tubular",
"location": { "lat": -22.2139, "lng": -49.9458, "elevation": 512 },
"bore_hole": [
{ "from": 0, "to": 60, "diameter": 254, "drilling_method": "rotary" }
],
"well_case": [{ "from": -0.5, "to": 36, "type": "pvc", "diameter": 168.3 }],
"reduction": [],
"well_screen": [
{ "from": 36, "to": 54, "type": "pvc", "diameter": 168.3, "screen_slot": 0.75 }
],
"surface_case": [],
"hole_fill": [
{ "from": 0, "to": 30, "type": "seal", "diameter": 254, "description": "Cement grout" },
{ "from": 30, "to": 60, "type": "gravel_pack", "diameter": 254, "description": "Gravel, 2–4 mm" }
],
"lithology": [
{ "from": 0, "to": 8, "description": "Red clayey soil", "color": "#9c5a3c", "texture": { "code": 620, "vocabulary": "fgdc" }, "geologic_unit": "Cover", "aquifer_unit": "Unsaturated zone" },
{ "from": 8, "to": 30, "description": "Silty sandstone", "color": "#c9a27a", "texture": { "code": 612, "vocabulary": "fgdc" }, "geologic_unit": "Botucatu Formation", "aquifer_unit": "Guarani" },
{ "from": 30, "to": 60, "description": "Medium-grained sandstone", "color": "#e0c48f", "texture": { "code": 607, "vocabulary": "fgdc" }, "geologic_unit": "Botucatu Formation", "aquifer_unit": "Guarani" }
],
"fractures": [],
"caves": []
}
```
::
## Check it
- Open [welldot.org](https://welldot.org) and drop `demo.well` on the editor. It shows the same profile and lets you keep editing with forms.
- Ask an AI assistant connected to the [docs MCP server](/ai/mcp) to validate it.
- From code, call `parseWell` from [`@welldot/core`](/core/getting-started/first-use).
If something is wrong, the validator names the field: `well_screen[0].screen_slot: Required` means the first screen has no slot size. [Validating](/format/guides/validating) explains the messages.
## Where to go next
- Add a water level: a `spot_measurement` event in `hydrodynamic_events` ([schema](/format/spec/v2/object-schemas#spot_measurement)).
- Read [Writing a .well by hand](/format/guides/writing-by-hand) for the habits that keep a file valid as it grows.
- Browse the [example wells](/format/reference/examples) for complete records.
# Depths and intervals
> How .well measures depth from the ground surface, writes components as from/to intervals, and handles stick-up, water levels above ground and the well's usable depth.
Every position along a well is a depth in meters, measured from the ground surface. Ground level is zero and depth increases downward. This single reference is used by the construction, the geology, the water levels and the sampling points, so they can be drawn and compared on one scale.
The rule is a [design principle](/format/spec/v2/overview#design-principles) ("ground level as zero") and is stated for levels in the [level sign convention](/format/spec/v2/format-reference#level-sign-convention).
## Intervals
Most components occupy a stretch of the hole and are written as an interval: `from` is the top and `to` is the bottom.
```json
{ "from": 36, "to": 54, "type": "pvc", "diameter": 168.3, "screen_slot": 0.75 }
```
A screen from 36 to 54 m, a sandstone layer from 8 to 30 m and a gravel pack from 30 to 60 m are written the same way. `from` and `to` are reserved for depth intervals in meters; time periods use `period_start` and `period_end` ([naming rules](/format/spec/v2/format-reference#naming-rules-since-v23)).
A few things are points rather than intervals: a fracture has a single `depth`, and a water level is a single depth reading.
```mermaid
flowchart TB
A["−0.5 m · top of casing (stick-up)"] --> B["0 m · ground surface"]
B --> C["3.9 m · static water level"]
C --> D["36 m · top of screen"]
D --> E["54 m · base of screen"]
E --> F["60 m · bottom of hole"]
```
## Above the ground: negative depths
Anything above the ground surface has a negative depth.
- **Stick-up.** A casing that rises 0.5 m above the ground starts at `"from": -0.5`. Renderers draw it above the wellhead pad.
- **Flowing wells.** A water level 3.98 m above the ground is written `"static_level": -3.98`. This is how the format records an artesian (flowing) condition, instead of a special well type ([`well_type`](/format/spec/v2/format-reference#well_type--recommended-values)).
The exception is the pump intake depth (`pump_installations[].intake_depth`), an equipment position that is always positive.
::do-dont
#do
Record a flowing well as a tubular well with a negative static level.
```json
{ "well_type": "tubular" }
{ "type": "spot_measurement", "static_level": -3.98 }
```
#dont
Use the deprecated `artesian` type, or a positive level with a note.
```json
{ "well_type": "artesian" }
{ "type": "spot_measurement", "static_level": 3.98, "notes": "above ground" }
```
::
## Total depth and usable depth
The well's drilled depth is the deepest `to` of its construction. It does not need its own field: software reads it from the data (`calculatedWellDepth` in [`@welldot/core`](/core/api/functions/calculatedWellDepth)).
`well_depth` is something else: the depth that is usable now, as measured or reported. It can be shallower than the drilled depth when the well has silted up or was partly backfilled ([core identity fields](/format/spec/v2/format-reference#core-identity)). Leave it out unless someone measured it.
## Overlaps and gaps
The format does not require intervals of different blocks to line up. A screen sits inside a borehole interval, a gravel pack around several screens, a lithology layer across a casing joint. Within one block, real reports sometimes contain gaps or overlaps; the [inconsistent data example](/format/reference/examples) keeps them on purpose. Validation checks types and required fields, not geometry, so such a file is still valid. Editors and analysis tools may warn about it.
## Elevation is separate
The ground elevation of the wellhead is stored once in `location.elevation`, in meters above the WGS84 ellipsoid or the datum named in `location.properties.elevation_datum` ([location](/format/spec/v2/format-reference#location)). To get the elevation of a point in the well, subtract its depth from that elevation.
## Related
- [Units and precision](/format/concepts/units-and-precision)
- The construction schemas: [`BoreHole`](/format/spec/v2/object-schemas#borehole--bore_hole), [`WellCase`](/format/spec/v2/object-schemas#wellcase--well_case), [`WellScreen`](/format/spec/v2/object-schemas#wellscreen--well_screen), [`HoleFill`](/format/spec/v2/object-schemas#holefill--hole_fill)
# Units and precision
> Why every number in a .well file has one fixed SI unit, which units apply to which fields, how to convert non-SI reports, and how to record measurement precision.
A `.well` file never says which units it uses. Each kind of quantity has exactly one unit, fixed by the specification: depths in meters, diameters in millimeters, flow rates in cubic meters per hour, and so on. Two files that describe the same well therefore contain the same numbers.
## The units
The main ones, from the spec's [canonical units](/format/spec/v2/format-reference#canonical-units) table:
| Quantity | Unit |
| ---------------------------------------- | -------------- |
| Depth, length, elevation | m |
| Diameter (hole, casing, screen, annulus) | mm |
| Screen slot opening | mm |
| Flow rate | m³/h |
| Elapsed time, test duration | min |
| Volume | m³ |
| Transmissivity | m²/s |
| Hydraulic conductivity | m/s |
| Power | kW |
| Substance concentration | mg/L |
| Electrical conductivity | µS/cm at 25 °C |
| Angles (azimuth, dip) | degrees |
The spec binds every numeric field to its unit in the [field-to-unit binding](/format/spec/v2/format-reference#field-to-unit-binding-normative) list. Water quality results take their unit from the parameter code; see [water quality units](/format/spec/v2/water-quality#units).
::note
The wellhead pad (`cement_pad`) is in meters like every other length, so a 1 × 1 m pad is written `1.0`, not `1000`.
::
## Why one unit
Configurable units would let the same well be written in many ways, make comparison harder and allow files whose declared units do not match their numbers. The spec's [rationale](/format/spec/v2/format-reference#rationale) follows GeoJSON (always WGS84) and RFC 3339 (always an offset): one well, one encoding. Display and input units belong to the applications that read and write the files.
## Converting reports
Reports often use inches, feet, gallons per minute or slot numbers. Convert them before writing the file ([rationale](/format/spec/v2/format-reference#rationale)):
| In the report | In the file |
| --------------------- | ---------------------------------------------------------------- |
| Hole drilled at 10 in | `"diameter": 254` |
| Casing 6 in (nominal) | the real outer diameter, for example `168.3` for 6 in steel pipe |
| Screen slot 20 | `"screen_slot": 0.508` (20 thousandths of an inch) |
| Depth 150 ft | `45.72` |
| Yield 50 US gpm | `11.36` m³/h |
| Pump 5 hp | `3.73` kW |
Two details matter:
- **As-built diameters.** Casing, screen and annulus diameters are the real outer diameter in millimeters, not the nominal trade size. A 6 in casing with an outer diameter of 168.3 mm is written `168.3`, not `152.4` ([as-built convention](/format/spec/v2/format-reference#diameter-and-the-as-built-convention)).
- **Slot numbers.** A US slot number is thousandths of an inch: slot 20 is 0.508 mm ([screen slot](/format/spec/v2/format-reference#screen-slot)).
`@welldot/core` ships the common conversions (`inchesToMm`, `feetToMeters`, `slotNumberToMm`, `usGallonsPerMinuteToCubicMeterPerHour`, `hpToKilowatts` and more). See [Unit conversion](/core/guides/unit-conversion).
## Dates and times
Dates and times have fixed forms too ([datetime conventions](/format/spec/v2/format-reference#datetime-conventions)):
- **Calendar dates** (`YYYY-MM-DD`) for the construction date and legal documents such as permits.
- **Instants** (RFC 3339 with a UTC offset, such as `2024-09-03T08:00:00-03:00`) for every measurement, test, log entry and sample. An instant without an offset is invalid.
A laboratory date known only to the day is written at local midnight with a `_resolution: "day"` marker ([instants known only by date](/format/spec/v2/format-reference#instants-known-only-by-date-since-v23)).
## Precision
Some measurements can carry their uncertainty in a sibling field named with `_precision`: `static_level_precision`, `depth_precision`, `lat_precision`, a result's `value_precision`. The value is one standard deviation, in the unit of the measurement it qualifies.
```json
{ "static_level": 3.94, "static_level_precision": 0.01 }
```
Laboratories usually report an expanded uncertainty U with a coverage factor k (often k = 2). Store U / k, not U. The full list of precision fields is in [Precision and uncertainty](/format/spec/v2/format-reference#precision-and-uncertainty).
::tip
Leave a precision field out when the report does not state one. A missing precision means "not documented", which is honest; an invented one is not.
::
## Coordinates
Coordinates are decimal degrees in a geographic reference system, WGS84 by default. Projected coordinates such as UTM must be converted to latitude and longitude before writing; the original system can be kept in `location.properties.original_crs` ([CRS scope](/format/spec/v2/format-reference#crs-scope)).
# Vocabularies
> Open and closed vocabularies in .well, how recommended values and x- values work, and where the lithology textures and water quality parameters come from.
Many fields in a `.well` file take a word from a list: the construction method, the casing material, the type of a pumping test, the category of a log entry. The format distinguishes two kinds of lists.
## Open vocabularies
Most lists are **open**. The spec recommends a set of values so that files from different sources agree, but any string is valid. A value outside the recommended set should start with `x-`, and every tool must keep it and show it as written ([recommended vocabularies](/format/spec/v2/format-reference#recommended-vocabularies)).
```json
{ "from": 0, "to": 12, "diameter": 200, "drilling_method": "x-jetting" }
```
Here `x-jetting` says plainly that jetting is not one of the recommended drilling methods (rotary, percussion, cable tool, auger, air hammer), while keeping the information.
Values are stored as their key (`stainless_steel`), never as a translated label (`Aço inox`). Labels are the job of the application; `@welldot/core` publishes English and Portuguese labels for every recommended value ([Vocabularies and textures](/core/guides/vocabularies-and-textures)).
Some open vocabularies, with their spec tables:
| Field | Recommended values |
| ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `well_type` | [construction methods](/format/spec/v2/format-reference#well_type--recommended-values) |
| `well_purpose` | [uses](/format/spec/v2/format-reference#well_purpose--recommended-values-since-v21) |
| `bore_hole[].drilling_method` | [drilling methods](/format/spec/v2/format-reference#bore_holedrilling_method--recommended-values-since-v23) |
| `well_case[].type`, `well_screen[].type`, `reduction[].type` | [construction materials](/format/spec/v2/format-reference#construction-materials--recommended-values-since-v23) |
| `well_screen[].slot_type` | [screen designs](/format/spec/v2/format-reference#well_screenslot_type--recommended-values-since-v23) |
| `hydrodynamic_events[].type` | [event types](/format/spec/v2/object-schemas#event-types) |
| `aquifer_analysis[].method` | [analysis methods](/format/spec/v2/format-reference#aquifer_analysismethod--recommended-values) |
| `history_logs[].category` | [log categories](/format/spec/v2/format-reference#history_logscategory--recommended-values) |
The full list is under [Recommended vocabularies](/format/spec/v2/format-reference#recommended-vocabularies) in the format reference.
::do-dont
#do
Use the recommended value when the report states it, and an `x-` value when it states something the list does not have.
```json
{ "type": "galvanized_steel" }
{ "type": "x-concrete" }
{ "drilling_method": "x-jetting" }
```
#dont
Force a value into the nearest recommended word, or write a custom value without the prefix.
```json
{ "type": "carbon_steel" }
{ "type": "Tubo de concreto" }
{ "drilling_method": "rotary" }
```
::
An unprefixed custom value, such as `"Tubo de concreto"`, is still valid: readers must accept and keep it. It is not recommended, because the spec says non-canonical values SHOULD use the `x-` prefix ([recommended vocabularies](/format/spec/v2/format-reference#recommended-vocabularies), [custom vocabulary values](/format/spec/v2/format-reference#custom-vocabulary-values--x--prefix)). The report's exact wording belongs in a free-text field such as `description`, `notes` or `obs`.
## Closed vocabularies
A few fields accept only the listed values, and anything else makes the file invalid. These are fields that software must be able to act on without guessing:
- `hole_fill[].type`: `gravel_pack` or `seal` ([HoleFill](/format/spec/v2/object-schemas#holefill--hole_fill)).
- `history_logs[].status`: `active`, `maintenance`, `inactive`, `decommissioned` or `abandoned` ([allowed values](/format/spec/v2/format-reference#history_logsstatus--allowed-values-since-v23)).
- `permits[].status`, result qualifiers, validation statuses and a few others, each marked in the spec.
## Lithology textures
A lithology layer's `texture` names a fill pattern with a `code` and a `vocabulary` ([Texture](/format/spec/v2/object-schemas#texture)). The default vocabulary is `fgdc`, the lithology patterns of the US Federal Geographic Data Committee's geologic map symbolization standard (FGDC-STD-013-2006). The 600 series (sedimentary) and 700 series (metamorphic and igneous) are the most useful for well logs. All 282 codes, and which of them have a drawn pattern, are in the [FGDC texture reference](/format/spec/reference/fgdc-textures).
```json
{ "texture": { "code": 627, "vocabulary": "fgdc" } }
```
Other vocabularies are allowed: `cgi` (the CGI Simple Lithology terms), or the HTTPS address of a vocabulary published by a survey or regulator ([vocabulary values](/format/spec/v2/object-schemas#vocabulary-values)).
## Water quality parameters
A result's parameter is also a code in a vocabulary: `{ "code": "nitrate_as_n", "vocabulary": "welldot" }`. The `welldot` vocabulary has 98 parameters, each with a fixed unit; CAS registry numbers (`"vocabulary": "cas"`) are accepted for substances, and `x-` vocabularies for anything else ([parameter vocabulary](/format/spec/v2/water-quality#parameter-vocabulary)).
## How values become official
When an `x-` value proves common, a later revision of the spec can promote it. The `x-` form then stays equivalent until the next major version ([vocabulary value promotion](/format/spec/v2/format-reference#vocabulary-value-promotion)). To propose a value, open an issue; see [Extending the format](/format/guides/extending).
# Profiles
> How a .well file declares that it follows extra rules, such as a regulator's required fields, using JSON Schema profiles.
The core format is deliberately neutral: it does not know any country's rules. A **profile** is how a regulator, a utility or a research network adds its own requirements on top. A profile is a JSON Schema published at an HTTPS address. A file lists the profiles it claims to follow in `profiles`:
```json
"profiles": [
"https://welldot.org/profiles/brazil-ana/v1/schema.json"
]
```
The rules for profiles are in [`profiles`](/format/spec/v2/format-reference#profiles) in the format reference.
## What a profile can do
A profile can make an optional field required, restrict an open vocabulary to certain values, demand a particular `x-` extension, or require certain water quality parameters for a monitoring program ([profile schema requirements](/format/spec/v2/format-reference#profile-schema-requirements)). It usually extends the canonical schema with `allOf` and adds its own constraints.
For example, a registry could require `well_id`, `location` and `construction_date`, and limit `well_purpose` to the uses it recognizes. A drinking water program could require nitrate, coliforms and turbidity in every sample ([regulatory profiles](/format/spec/v2/water-quality#regulatory-profiles)).
## Conformance is a claim
Listing a profile is a statement by the author, not a guarantee. A profile-aware reader validates the file against each profile it can reach and reports failures as **warnings**. A profile that cannot be downloaded is ignored, and a file is never rejected because of one ([profile validation](/format/spec/v2/format-reference#profile-validation)). A file that is valid `.well` stays valid everywhere.
```mermaid
flowchart LR
F[".well file"] --> C{"Core schema (parseWell)"}
C -->|invalid| E["errors"]
C -->|valid| P{"Each listed profile (if reachable)"}
P -->|fails| W["warnings"]
P -->|passes| OK["conforms"]
```
## Profiles and jurisdiction
National drinking water limits are not written into files either. They are data that software picks at display time (`@welldot/core` ships WHO, EU and Brazilian limit sets). Local obligations, such as which parameters must be sampled, enter through profiles. See [jurisdiction neutrality](/format/spec/v2/overview#design-principles) in the design principles and [limit sets](/format/spec/v2/water-quality#limit-sets-are-not-part-of-the-file).
## Status
The `profiles` field and its rules are part of v2. No official profile is published yet; the `spec/profiles/` folder is reserved for them. If you need one for your agency or program, open an issue on [GitHub](https://github.com/welldothq/welldot/issues). The [Relationship to GWML2](/format/spec/v2/interoperability#profiles-for-regulatory-adoption) section describes how profiles support regulatory adoption.
# Versions and deprecation
> How .well versions are written and compared, what readers do with older and newer files, and how fields are deprecated without breaking existing files.
Every `.well` file states the version of the format it follows in `version`. Readers use it to decide how to read the file, and to reject a file they cannot read rather than misread it.
## How versions are written
| `version` | Meaning |
| --------- | ------------------------------------------------------------------------------- |
| `1` | The first version: construction and geology only. |
| `2` | Version 2, revisions 2.0 to 2.3 (the revision is not stated). |
| `"2.4"` | Version 2, revision 2.4. Since 2.4 the revision is written as a string `"M.m"`. |
Revisions are compared as two integers, never as text: `"2.10"` comes after `"2.9"`. Patch levels such as `"2.4.1"` are not allowed. See [Versioning](/format/spec/v2/format-reference#versioning).
::warning
`@welldot/core` 0.4.0 and earlier reject `"2.4"` files. This is the one intentional break in version 2. Upgrade the software that reads your files before writing 2.4 files.
::
## What a reader does
The full rules are in [parser version handling](/format/spec/v2/format-reference#parser-version-handling). In short:
- A v2 reader accepts `1`, `2` and any `"2.x"`. A v1 file is upgraded on read: flat coordinates become `location`, `fgdc_texture` becomes `texture`, `screen_slot_mm` becomes `screen_slot` ([changes from v1](/format/spec/v2/overview#changes-from-v1)).
- A newer minor revision (say a future `"2.9"`) is still read as v2. Fields the reader does not know are kept, not dropped.
- A version that is malformed (`2.4` as a number, `"v2.4"`) or from an unknown major version (`3`, `"3.0"`) is rejected with an error that names it.
## Minor revisions are additive
Within version 2, each revision only adds: new optional fields, new blocks, new recommended values and clarifications. It never removes a field or changes its meaning, so every valid 2.3 file is valid 2.4 once its `version` is updated ([deprecation policy](/format/spec/v2/format-reference#deprecation-policy)). The [changelog](/format/changelog) lists what each revision added.
## Deprecation
A field or value goes through three states ([field lifecycle](/format/spec/v2/format-reference#field-lifecycle)):
```mermaid
flowchart LR
A["Active"] -->|"announced in a minor revision"| D["Deprecated still valid, readers warn"]
D -->|"only in a new major version"| R["Removed"]
```
A deprecated field is still accepted and preserved. Readers may warn and suggest the replacement, and editors may offer to migrate it, but only when the user asks ([parser behavior](/format/spec/v2/format-reference#parser-behavior)). Removal happens only in a new major version, with a documented rule for reading old files.
The one deprecation in version 2 so far is `well_type: "artesian"` (since 2.1, removal in v3): an artesian well is usually a `tubular` well with a negative static level ([`well_type`](/format/spec/v2/format-reference#well_type--recommended-values)).
## What to write today
- New files: `"version": "2.4"`. `serializeWell` from `@welldot/core` writes it for you.
- Existing files with `"version": 2` stay valid. Rewriting them as `"2.4"` is safe once all your readers are on `@welldot/core` 0.5.0 or later.
- Archives of v1 files can stay as they are; every v2 reader upgrades them on read.
## Related
- [Changelog](/format/changelog)
- [v1 specification (archived)](/format/spec/v1/well-format)
- [Versions and time zones](/core/guides/versions-and-time-zones) in `@welldot/core`
# Extensibility
> How to carry data the spec does not define, with x- fields, x- blocks and x- vocabulary values, and why every reader must keep them.
No format can foresee every field a project needs. `.well` reserves the `x-` prefix for data the spec does not define, and requires every reader to keep that data untouched. A tool can add its own information without breaking any other tool, and the information survives a round trip through software that does not understand it.
The rules are in [Extensibility](/format/spec/v2/format-reference#extensibility).
## Three kinds of extension
**Fields.** Any object can carry fields whose names start with `x-`. Their content needs no prefix ([custom fields](/format/spec/v2/format-reference#custom-fields--x--prefix)).
```json
{
"id": "evt-07",
"type": "constant_rate",
"datetime": "2006-03-14T08:00:00-03:00",
"x-siagas": { "registro": "SP-0042819" }
}
```
**Top-level blocks.** Whole blocks the spec does not define must start with `x-`, for example a block of gas readings or geophysical logs ([custom top-level blocks](/format/spec/v2/format-reference#custom-top-level-blocks--x--prefix-required)).
```json
{
"version": "2.4",
"x-acme-gas_monitoring": [
{ "datetime": "2025-02-01T09:00:00-03:00", "ch4_pct": 0.2 }
]
}
```
**Vocabulary values.** In an open vocabulary, a value outside the recommended list should start with `x-`, such as `"type": "x-slug_test"` for a hydrodynamic event ([custom vocabulary values](/format/spec/v2/format-reference#custom-vocabulary-values--x--prefix)).
## Name your extensions after yourself
Since 2.4 the spec recommends `x--`, for example `x-welldot-example` or `x-acme-gas_monitoring`. Two tools that both invent `x-custom` would collide; two that use their own name do not.
## Readers keep what they do not understand
A reader must preserve `x-` fields and blocks it does not recognize and write them back unchanged. The same applies to any other member it does not know, such as a field added by a newer revision of the spec ([design principles](/format/spec/v2/overview#design-principles), "Preserve unknown members"). Dropping them on save is a bug.
::do-dont
#do
Read the file, change what you need, and write every other member back as it was. `parseWell` and `serializeWell` in `@welldot/core` do this.
```ts
const well = parseWell(text);
well.name = 'PT-01 (renamed)';
const out = serializeWell(well); // x- members are still there
```
#dont
Rebuild the object from the fields your program knows. Every `x-` member and every newer field is lost.
```ts
const { version, name, bore_hole } = JSON.parse(text);
const out = JSON.stringify({ version, name, bore_hole });
```
::
## Fields without the prefix
A field that is neither in the spec nor prefixed is "technically non-conformant": readers must not reject the file, but should warn. Use the prefix for anything you add.
## Extensions are a path into the spec
An extension that many people need can become part of the format. A revision adopts the field or value, and for vocabularies the `x-` form stays equivalent until the next major version ([vocabulary value promotion](/format/spec/v2/format-reference#vocabulary-value-promotion)). [Extending the format](/format/guides/extending) explains when to extend locally and when to propose a change.
## Related: offline packages
Attachments are referenced by URL. For archives and regulators, a `.well` file can travel with its documents in a ZIP package with the `.wellz` extension. The `.well` file inside is an ordinary file ([offline packaging](/format/spec/v2/format-reference#offline-packaging-since-v24)).
# Writing a .well by hand
> Habits that keep a hand-written .well file valid and faithful as it grows, from the order of blocks to ids, dates and test readings.
Most files are written by software, but a hand-written file is often the fastest way to record a single well, prepare test data or fix a detail. This guide collects the habits that keep such a file valid. Start from [Your first .well file](/format/getting-started/first-well-file) if you have not written one yet.
## Start from something valid
Copy an [example well](/format/reference/examples) that resembles yours, or the minimal file from the first-file guide, and change it step by step. Validate after each block you add (see [Validating](/format/guides/validating)): an error is easier to find in ten new lines than in three hundred.
A JSON-aware editor helps. In VS Code, associate the extension with JSON (`"files.associations": { "*.well": "json" }` in your settings) and add a `$schema` member at the top of the file. You then get completion and inline errors from the published schema:
```json
{
"$schema": "https://welldot.org/schema/v2/well.schema.json",
"version": "2.4"
}
```
`$schema` is an unknown member to the format, so readers keep it; the example wells carry one.
## Keep the report's meaning
- **Transcribe, do not interpret.** Free-text fields (`description`, `notes`, `obs`) keep the report's own words and language. Open vocabulary fields, such as a casing `type`, take the recommended value the report states, or an `x-` value for anything else; an unprefixed custom value is valid but not recommended ([custom vocabulary values](/format/spec/v2/format-reference#custom-vocabulary-values--x--prefix)).
- **Leave out what you do not know.** A missing value is omitted, never written as `null`, `0` or an empty string ([absent values](/format/spec/v2/format-reference#absent-values-since-v24)).
- **Convert units once, carefully.** Meters, millimeters and m³/h only ([Units and precision](/format/concepts/units-and-precision)). Write the real outer diameter of a casing, not its nominal size.
## Required fields
`version` and `well_type` are required by the spec ([core identity](/format/spec/v2/format-reference#core-identity)). `bore_hole`, `well_case`, `reduction`, `well_screen`, `surface_case`, `hole_fill`, `lithology`, `fractures` and `caves` must be present, even as `[]` ([Well object](/format/reference/objects/well)). Every other block can be left out entirely.
::note
`parseWell` in `@welldot/core` currently accepts a file without `well_type`. Write it anyway: the spec requires it, and other readers may reject the file.
::
::note
`parseWell` in `@welldot/core` currently accepts a file without `version`. The spec says such a file SHOULD be rejected ([parser version handling](/format/spec/v2/format-reference#parser-version-handling)), so always write it.
::
## Ids and references
Records in the history, tests, permits, meters, production and samples have an `id`. Ids are strings, unique within their block. UUIDs are recommended for software; short readable ids such as `evt-01` or `am-2025` are fine by hand ([`id` field convention](/format/spec/v2/format-reference#id-field-convention)).
References use those ids: an analysis lists the tests it used in `source_event_ids`, a log entry points to a sample in `sample_ids`. Every reference resolves within the same file ([cross-array references](/format/spec/v2/format-reference#cross-array-references)).
```json
"hydrodynamic_events": [
{ "id": "evt-01", "type": "constant_rate", "datetime": "2019-05-20T08:00:00-03:00", "static_level": 3.9 }
],
"aquifer_analysis": [
{ "id": "an-01", "datetime": "2019-06-02T10:00:00-03:00", "source_event_ids": ["evt-01"], "method": "cooper_jacob" }
]
```
::warning
Two records with the same id in one block make the file malformed. Software must not guess which one you meant ([duplicate ids](/format/spec/v2/format-reference#duplicate-ids-within-a-scope)).
::
## Dates and times
- Construction dates and permit dates are calendar dates: `"2019-05-20"`.
- Everything that was measured or happened at a moment is an instant with its UTC offset: `"2019-05-20T08:00:00-03:00"`. An instant without an offset is invalid ([datetime conventions](/format/spec/v2/format-reference#datetime-conventions)).
- If the report gives only the day of a test, write the local time you know (often the start of the working day) and say so in `notes`.
## Pumping tests
A test is one event. Its readings sit inside steps:
```json
{
"id": "evt-02",
"type": "constant_rate",
"datetime": "2019-05-20T08:00:00-03:00",
"static_level": 3.94,
"steps": [
{
"rate": 18,
"duration": 1440,
"readings": [
{ "elapsed": 1, "depth": 9.8 },
{ "elapsed": 60, "depth": 14.2 },
{ "elapsed": 1440, "depth": 15.1 }
]
}
],
"recovery": {
"readings": [
{ "elapsed": 1, "depth": 10.4 },
{ "elapsed": 120, "depth": 4.3 }
]
}
}
```
`rate` is m³/h, `duration` and `elapsed` are minutes from the start of the step, and `depth` is the water level in meters below ground. Do not store drawdown, specific capacity or Jacob coefficients on the event: drawdown is derived from the readings, and interpretations go in `aquifer_analysis` ([design principles](/format/spec/v2/overview#design-principles), "Derived values are never stored raw"). The step and reading schemas are [`PumpingStep`](/format/spec/v2/object-schemas#pumpingstep) and [`LevelReading`](/format/spec/v2/object-schemas#levelreading).
## Corrections
To correct a measurement after a file has been shared, add a new entry whose `corrects` holds the id of the wrong one, instead of editing it. The old entry stays in the file and drops out of every calculation ([corrections](/format/spec/v2/object-schemas#corrections-since-v23)). This applies to `hydrodynamic_events`, `production` and `water_samples`, the three ledger blocks ([block kinds](/format/spec/v2/format-reference#block-kinds-since-v23)).
## When the file grows
Past a few hundred lines, a hand-written file is easier to maintain in the [welldot.org](https://welldot.org) editor, which opens and saves the same file. For many wells, write a script with [`@welldot/core`](/core) instead.
# Converting a report
> Turn a drilling report, well log or laboratory report into a .well file, by hand or with an AI assistant running the welldot-converter skill, and check the result.
Most well data starts as a document: a drilling report, a well completion log, a pumping test sheet, a laboratory report. This guide shows how to turn one into a `.well` file and what to check before you trust the result.
## Choose a route
| Route | Good for |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| The [welldot.org](https://welldot.org) editor | One well at a time, with forms and a live profile. Saves a `.well` file. |
| By hand | Short reports, or fixing a converted file. See [Writing a .well by hand](/format/guides/writing-by-hand). |
| An AI assistant with the [`welldot-converter` skill](/ai/conversion-skill) | PDFs, scans and photos, in any language. Fast, but always reviewed. |
| A script | Many records from one source, such as a registry export. See [Building on .well](/format/guides/building-on-well). |
## Map the report
Whatever the route, the same mapping applies. The [For geologists](/format/getting-started/for-geologists) page goes through it in detail.
| Section of the report | Blocks |
| ------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| Header (name, owner, location, dates, registry numbers) | `name`, `well_type`, `well_purpose`, `location`, `well_id`, `well_driller`, `construction_date` |
| Lithological description | `lithology`, `fractures`, `caves` |
| Construction profile | `bore_hole`, `well_case`, `well_screen`, `reduction`, `surface_case`, `hole_fill`, `centralizers`, `cement_pad` |
| Pumping test sheet | `hydrodynamic_events` (the readings), `aquifer_analysis` (the interpretation) |
| Laboratory report | `water_samples` |
| Permits, maintenance records | `permits`, `history_logs`, `pump_installations`, `meters` |
## Rules that matter most
These are the mistakes that produce a valid but wrong file:
- **Never estimate a number.** If the report does not give a value, leave the field out. A guessed diameter or interpolated reading cannot be told apart from a measured one.
- **Keep the report's words.** Descriptions and notes stay in the original language and wording. In open vocabulary fields, such as casing material, use the recommended value when the report states it, otherwise an `x-` value; an unprefixed custom value is valid but not recommended ([custom vocabulary values](/format/spec/v2/format-reference#custom-vocabulary-values--x--prefix)).
- **Convert units exactly.** Inches to millimeters (× 25.4), feet to meters (× 0.3048), slot numbers to millimeters, gallons per minute to m³/h ([Units](/format/spec/v2/format-reference#units)). Use real outer diameters for casing ([as-built convention](/format/spec/v2/format-reference#diameter-and-the-as-built-convention)).
- **Convert projected coordinates.** UTM coordinates must become latitude and longitude; keep the source system in `original_crs` ([CRS scope](/format/spec/v2/format-reference#crs-scope)).
- **Total depth is not usable depth.** The drilled depth belongs in the deepest `bore_hole[].to`. `well_depth` is only for a measured current depth.
- **Datetimes carry an offset.** Test and sample times are written with the local UTC offset, for example `-03:00` in most of Brazil ([datetime conventions](/format/spec/v2/format-reference#datetime-conventions)).
::do-dont
#do
Write what the report states, using the recommended value it names (here "tubo geomecânico", geomechanical PVC), and leave out what it does not say.
```json
{ "from": 0, "to": 36, "type": "geomechanical_pvc", "diameter": 168.3 }
```
#dont
Fill gaps with plausible values or map the material to the nearest recommended word.
```json
{ "from": 0, "to": 36, "type": "pvc", "diameter": 150 }
```
::
## With an AI assistant
The [`welldot-converter` skill](/ai/conversion-skill) is a written procedure an assistant follows to read the report, extract every section, write the file and report what it left out. It works with Claude, ChatGPT, Gemini, Cursor and other assistants.
```mermaid
flowchart LR
R["Report (PDF, scan, DOCX)"] --> A["Assistant + welldot-converter"]
A --> W[".well draft"]
W --> V{"validate_well (docs MCP server)"}
V -->|errors| A
V -->|valid| H["Human review against the report"]
H --> F["Final .well"]
```
Connect the assistant to the [docs MCP server](/ai/mcp) so it can check its own output with `validate_well` and look up the spec instead of relying on memory.
::caution
A valid file is not a correct file. Validation checks types and required fields; it cannot know that a casing is 168.3 mm and not 152.4 mm. Compare the drawn profile with the report's drawing, and the numbers with the tables, before you rely on a converted file.
::
## Review checklist
1. The profile drawn at [welldot.org](https://welldot.org) matches the report's construction drawing.
2. Every depth interval in the report has an entry, and no entry has invented depths.
3. Diameters are in millimeters and look plausible (a casing is narrower than its hole).
4. Lithology descriptions are the report's text, and each layer has a sensible texture code.
5. The static level, test rates and durations match the test sheet; levels above ground are negative.
6. The file passes [validation](/format/guides/validating).
7. Anything uncertain is noted in `obs` or a `notes` field rather than silently decided.
## Registry imports
For Brazilian wells registered in SIAGAS, [`@welldot/utils`](/utils/guides/siagas-import) can import a well directly from the registry page, with warnings for anything it could not carry over.
# Validating
> Check that a .well file is valid, in the browser, with an AI assistant, from TypeScript or with the JSON Schema in any language, and read the error messages.
A file is valid when it follows the schema: the required blocks are present, every field has the right type and every closed vocabulary has an allowed value. There is one definition of valid, `parseWell` from [`@welldot/core`](/core), and every route below uses it or the JSON Schema generated from it.
## Ways to validate
::tabs
:::tabs-item{label="Browser" icon="i-lucide-globe"}
Open [welldot.org](https://welldot.org) and drop the file on the editor. An invalid file is reported with the path of each problem.
On this site, a `::well-preview` block validates its content and lists the errors instead of drawing.
:::
:::tabs-item{label="AI assistant" icon="i-lucide-sparkles"}
Connect your assistant to the [docs MCP server](/ai/mcp) and ask it to validate the file. The `validate_well` tool runs `parseWell` and returns errors with their paths, plus advisory warnings.
:::
:::tabs-item{label="TypeScript" icon="i-lucide-code"}
```ts
import { parseWell } from '@welldot/core';
try {
const well = parseWell(text);
} catch (error) {
// Schema problems: a ZodError whose issues each have a path.
// Invalid JSON or an unsupported version: a plain Error (SyntaxError for JSON).
}
```
See [Parsing and validating](/core/guides/parsing-and-validating).
:::
:::tabs-item{label="Other languages" icon="i-lucide-braces"}
Validate against the JSON Schema with any draft 2020-12 validator:
```text
https://welldot.org/schema/v2/well.schema.json
```
See [JSON Schema](/format/reference/json-schema).
:::
::
## Reading the errors
Each error has a path into the document and a message. Array positions count from zero.
| Error | Meaning |
| -------------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
| `well_screen[0].screen_slot: Required` | The first screen has no `screen_slot`. |
| `bore_hole[1].to: Expected number, received string` | A depth was written in quotes, or with its unit (`"60 m"`). |
| `hole_fill[2].type: Invalid enum value. Expected 'gravel_pack' \| 'seal', received 'gravel'` | `hole_fill` types are a closed list. |
| `fractures: Required` | A required list is missing; write `[]` if there are none. |
| `construction_date: construction_date must be YYYY-MM-DD` | A date in another form, such as `20/05/2019`. |
| `version: Unsupported .well format version: "3.0"` (code `unsupported_version`) | The file declares a version this reader does not know. |
The last row is not a schema error. `parseWell` throws a plain `Error` with the message `Unsupported .well format version: "3.0"` (or `Malformed .well format version: …`) before the schema runs. The MCP validator and this site report it with the path `version` and the code `unsupported_version`.
Fix the first errors first: one missing bracket or a wrong type high in the file can cause several messages below it.
## Errors and warnings
**Errors** make a file invalid. They come from the schema.
**Warnings** do not. They point at data that is valid but probably wrong, or that a calculation cannot use: a permit whose validity ends before it starts, a meter reading that goes backwards, a pump intake below the bottom of the well, a dangling reference. The spec marks these "warnings, never rejection"; see for example [permit validation](/format/spec/v2/object-schemas#validation-warnings-never-rejection-1) and [dangling references](/format/spec/v2/format-reference#dangling-references). The MCP server returns them separately, computed by [`@welldot/utils`](/utils).
## What validation does not check
Validation checks shape, not truth. It does not check that:
- a casing fits inside its hole, or that intervals do not overlap;
- `from` is smaller than `to`;
- the numbers match the source report;
- the texture code fits the description.
Some of these are good candidates for warnings in your own tools. The spec decides which rules exist; a validator never adds its own, so that every tool accepts the same files.
## The JSON Schema and the library
The JSON Schema is generated from the same Zod validators that `parseWell` uses, so they accept the same documents. A few rules can only be expressed in code, and `parseWell` also upgrades v1 files before checking them. When in doubt, `parseWell` is the reference. See [Schema validation](/format/spec/v2/interoperability#schema-validation) in the spec.
# Extending for your own needs
> Add the data your project needs to .well files with x- extensions, decide between a local extension, a profile and a spec change, and propose a change to the format.
Sooner or later a project needs something the spec does not define: an internal asset code, a field the local regulator asks for, a measurement nobody else takes. This guide shows how to add it without breaking other tools, and how to get it into the format when it is useful to everyone.
## Decide where the data belongs
```mermaid
flowchart TD
Q["Does the spec already have a field for it?"] -->|yes| U["Use it. Search the object reference."]
Q -->|no| L{"Who needs it?"}
L -->|"only your tool"| X["x-vendor-name field or block"]
L -->|"everyone under one regulator or program"| P["A profile, plus an x- extension if the field is new"]
L -->|"most well data"| S["Propose a spec change"]
```
Before adding anything, search the [object reference](/format/reference/objects) and the [recommended vocabularies](/format/spec/v2/format-reference#recommended-vocabularies). Many needs are already covered: free-text `notes` and `obs`, attachments for documents, open vocabularies that accept any value.
## A local extension
Use the `x-` prefix, with your organization's name, on a field or a whole block ([Extensibility](/format/spec/v2/format-reference#extensibility)):
```json
{
"version": "2.4",
"name": "PT-07",
"x-acme-asset": { "code": "ACM-PT-07", "cost_center": "4410" },
"well_case": [
{
"from": 0,
"to": 40,
"type": "carbon_steel",
"diameter": 219,
"x-acme-batch": "B-2291"
}
]
}
```
- Prefix with your name (`x-acme-…`) so you do not collide with another tool's `x-custom` (recommended since 2.4).
- Keep units SI and names self-describing, as the rest of the format does ([naming rules](/format/spec/v2/format-reference#naming-rules-since-v23)).
- Do not use an extension to restate a value the spec already holds; two sources for one fact will drift.
Every conforming reader keeps your extension and writes it back unchanged, so the file can pass through other tools safely. [Extensibility](/format/concepts/extensibility) explains the rule.
## A new value in an open vocabulary
Write it with the prefix: `"drilling_method": "x-jetting"`, `"category": "x-site_visit"` ([custom vocabulary values](/format/spec/v2/format-reference#custom-vocabulary-values--x--prefix)). Closed vocabularies, such as `hole_fill[].type`, cannot be extended this way; a new value there needs a spec change.
## Requirements for a group: profiles
When an agency or a monitoring network needs every file to carry certain fields or values, publish a [profile](/format/concepts/profiles): a JSON Schema at an HTTPS address that files list in `profiles`. The profile can require your `x-` extension, so the extension becomes part of the agreement without changing the core format ([profile schema requirements](/format/spec/v2/format-reference#profile-schema-requirements)).
## Propose a change to the format
If an extension would help most people who record wells, propose it for the spec.
::steps
### Open an issue
Describe the data, who records it, where it appears in real reports and an example. Format changes start with an issue on [GitHub](https://github.com/welldothq/welldot/issues); see the repository's [contributing guide](https://github.com/welldothq/welldot/blob/main/CONTRIBUTING.md).
### Agree on the shape
Field name, type, unit, whether it is required, and which block it belongs to. The new field must fit the [design principles](/format/spec/v2/overview#design-principles): SI units, no derived values, no jurisdiction-specific meaning.
### Change the spec first
The spec text in `spec/` is edited first, in plain Markdown, then the reference implementation, the JSON Schema and the docs. The [update rules](/contributing/update-rules) list everything that changes together.
::
A new optional field ships in a minor revision and never breaks existing files ([deprecation policy](/format/spec/v2/format-reference#deprecation-policy)). An `x-` vocabulary value that is adopted stays equivalent to the new value until the next major version ([vocabulary value promotion](/format/spec/v2/format-reference#vocabulary-value-promotion)).
# Building an editor or database on .well
> Design decisions for software that reads and writes .well files at scale, such as editors, registries, databases and data pipelines, with links to the rules behind each.
This guide is for developers building a second editor, a well registry, a database import or a data pipeline on the format. It lists the decisions that are easy to get wrong and the spec rules that settle them.
## Reading
- **Accept every version you can.** Read `1`, `2` and `"2.x"`; upgrade v1 on read; reject malformed and unknown major versions with a clear message ([parser version handling](/format/spec/v2/format-reference#parser-version-handling)). `parseWell` in `@welldot/core` does all of this.
- **Do not assume order.** Events, samples and log entries may appear in any order. Sort by instant (as absolute UTC time) and use `sequence` to break ties ([ordering and ties](/format/spec/v2/format-reference#ordering-and-ties)).
- **Treat absent as unknown.** A missing static level is not zero; a missing status means the status is unknown ([absent values](/format/spec/v2/format-reference#absent-values-since-v24)).
- **Show unknown vocabulary values as they are.** Do not map `x-jetting` to "other" ([recommended vocabularies](/format/spec/v2/format-reference#recommended-vocabularies)).
## Writing
- **Round-trip everything.** Preserve `x-` members and any member you do not know, at every level ([design principles](/format/spec/v2/overview#design-principles), "Preserve unknown members"). The safest pattern is to parse into an object, change the fields you edit, and serialize the same object.
- **Write the current version.** New files are `"2.4"`. Upgrading `2` to `"2.4"` is safe; `serializeWell` writes it.
- **Omit, do not null.** Remove optional fields that have no value ([absent values](/format/spec/v2/format-reference#absent-values-since-v24)).
- **Store SI, display anything.** Convert to the user's units on screen and back on input. The file never changes unit ([Units](/format/spec/v2/format-reference#units)).
- **Store keys, not labels.** Write `stainless_steel`, show "Stainless steel" or "Aço inox".
## Ids and references
- Ids are strings, unique per block; generate UUID v4 for new records ([`id` field convention](/format/spec/v2/format-reference#id-field-convention)).
- References resolve only within the same file ([cross-array references](/format/spec/v2/format-reference#cross-array-references)).
- A dangling reference is a warning; never delete it silently on import or export ([dangling references](/format/spec/v2/format-reference#dangling-references)).
- Duplicate ids are malformed. An interactive editor asks the user to merge, rename or abort; a batch importer rejects the file. Never rename automatically ([duplicate ids](/format/spec/v2/format-reference#duplicate-ids-within-a-scope)).
## Editing history correctly
Each top-level list is one of three kinds, and the kind decides how a correction is recorded ([block kinds](/format/spec/v2/format-reference#block-kinds-since-v23)):
| Kind | Blocks | How to correct |
| -------------- | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| Ledger | `hydrodynamic_events`, `production`, `water_samples` | Append a new entry with `corrects`. Editing in place is allowed but not recommended after export. |
| Mutable record | `history_logs`, `permits`, `operating_regime` | Edit in place and update `updated_at`. |
| Installation | `pump_installations`, `meters` | Edit in place; a reinstalled unit is a new entry with the same serial. |
Construction and geology are edited in place. An `aquifer_analysis` is an interpretation: a new interpretation is a new entry, and earlier ones stay.
## Derived values
Never store what can be computed: drawdown, specific capacity from readings, production volumes, permit status, condition deadlines, exceedances ([design principles](/format/spec/v2/overview#design-principles)). Compute them when you display or query. [`@welldot/utils`](/utils) implements the spec's derivations, including the normative ones for [volumes](/format/spec/v2/object-schemas#volume-derivation-normative) and [permit deadlines](/format/spec/v2/object-schemas#deadline-generation-normative), and [Querying current state](/format/spec/v2/object-schemas#querying-current-state) describes how to answer "what is the well's status now".
## Databases
A `.well` file is a document, and the simplest durable storage is the document itself: a JSON column, a document store or files in object storage. Index what you query (identifiers, location, depth, status) in separate columns, derived from the document on write.
When you normalize into tables:
- Key a well by its `well_id` entries (authority plus id), not by `name`; the pair is unique within a file ([`well_id`](/format/spec/v2/format-reference#well_id)).
- Key child records by the well plus their `id`; ids are only unique within one block of one file.
- Keep the original document, or at least its `x-` members and unknown fields, so you can export a faithful file again.
- Collective permits and campaign blanks are repeated in each well's file with the same id; you may merge them by id ([repeated records](/format/spec/v2/format-reference#repeated-records-since-v24)).
## Sharing and privacy
To publish a well without its owner data, permits or location, remove those parts rather than blanking them, so the result is still valid. `redactWell` in `@welldot/core` does this by section ([Serializing and sharing](/core/guides/serializing-and-sharing)).
## Interoperability
- GroundWater ML 2 (GWML2) is the OGC standard for groundwater data; the spec maps `.well` objects to its classes ([relationship to GWML2](/format/spec/v2/interoperability#relationship-to-gwml2)).
- `@context` is reserved for a JSON-LD context; it is optional and readers must accept it ([JSON-LD context](/format/spec/v2/interoperability#json-ld-context-reserved)).
- The MIME type is `application/vnd.well+json` and the extension `.well` ([overview](/format/spec/v2/overview)).
## Testing your implementation
Run your reader and writer against all 19 [example wells](/format/reference/examples): every one must parse, and writing it back must keep every member. Include the [inconsistent data](/format/reference/examples) and [incomplete record](/format/reference/examples) examples, which are valid on purpose.
# JSON Schema
> The published JSON Schema for .well v2, where it comes from, and how to validate files with it in editors and in languages other than TypeScript.
The `.well` v2 JSON Schema describes the shape of a valid file in a form every language can use. It is published at a stable address, which is also its `$id`:
```text
https://welldot.org/schema/v2/well.schema.json
```
[Open the schema](https://welldot.org/schema/v2/well.schema.json). It uses JSON Schema draft 2020-12.
## Where it comes from
The schema is generated from the Zod validators of [`@welldot/core`](/core), the reference implementation, so the schema and the library accept the same documents. It lives in the repository at [`spec/schema/v2/well.schema.json`](https://github.com/welldothq/welldot/blob/main/spec/schema/v2/well.schema.json) and is never edited by hand; CI fails when it is out of date. It also ships in the npm package as `@welldot/core/well.schema.json`.
The prose specification remains the authority on meaning. The schema covers structure: required fields, types, enumerations and patterns. Rules that need context, such as how a reader handles a v1 file, are in the spec and in `parseWell` ([Schema validation](/format/spec/v2/interoperability#schema-validation)).
## In an editor
Add `$schema` to a file to get completion and inline errors in editors that support JSON Schema, such as VS Code (map `*.well` to JSON first):
```json
{
"$schema": "https://welldot.org/schema/v2/well.schema.json",
"version": "2.4"
}
```
## In code
::code-group
```ts [JavaScript (ajv)]
import Ajv2020 from 'ajv/dist/2020.js';
import addFormats from 'ajv-formats';
const schema = await (
await fetch('https://welldot.org/schema/v2/well.schema.json')
).json();
const ajv = new Ajv2020({ allErrors: true, strict: false });
addFormats(ajv);
const validate = ajv.compile(schema);
if (!validate(JSON.parse(text))) console.log(validate.errors);
```
```python [Python (jsonschema)]
import json, urllib.request
from jsonschema import Draft202012Validator
url = "https://welldot.org/schema/v2/well.schema.json"
schema = json.load(urllib.request.urlopen(url))
validator = Draft202012Validator(schema)
with open("PT-01.well", encoding="utf-8") as f:
for error in validator.iter_errors(json.load(f)):
print(list(error.path), error.message)
```
::
In TypeScript, prefer `parseWell` from `@welldot/core`: it is the same rule set, returns typed data and upgrades v1 files ([Parsing and validating](/core/guides/parsing-and-validating)).
## Versions of the schema
There is one schema per major version. The v2 schema accepts every v2 revision, from the integer `2` to `"2.4"`. A future v3 would get its own address ([deprecation policy](/format/spec/v2/format-reference#deprecation-policy)).
## Related
- [Object reference](/format/reference/objects): the schema rendered as one page per object.
- [Validating](/format/guides/validating): all the ways to check a file.
# Example wells
> Nineteen complete, valid .well files built on real Brazilian wells, covering crystalline, karst, sedimentary, shallow and problem cases, with live previews and download links.
The format ships with 19 complete example files. Each one is valid and each shows a different situation: rock types, construction styles, test records, operation records, permits, water quality and the messy records real archives contain. Use them to learn the format, to test software and as starting points for your own files.
They are built on real wells from SIAGAS, the groundwater registry of the Geological Survey of Brazil: region, geology, construction and the order of magnitude of test values come from the registry. Names, identifiers, permits, analyses and readings are invented but consistent. Every file says so in an `x-welldot-example` block.
## Get the files
- Download: `https://welldot.org/examples/.well` (the links below).
- From npm: `@welldot/core/examples/.well`, installed with [`@welldot/core`](/core/getting-started/installation).
- On GitHub: [`packages/core/examples`](https://github.com/welldothq/welldot/tree/main/packages/core/examples), with a README on how they were made.
- In the editor: [welldot.org](https://welldot.org) offers them when you start a new well.
## Three to start with
A crystalline supply well with a long history: telescoping hole, screens alternating with casing, centralizers, a 24 h test with its analysis, three permits, meters and production.
::well-preview{example="coluna-mg-cristalino-abastecimento"}
::
A karst well in the Bambuí limestone, with three caves and a solution fracture.
::well-preview{example="irece-ba-carste-bambui"}
::
A hand-dug well (cacimba), 10 m deep and 1.2 m wide, with brick lining.
::well-preview{example="sao-gabriel-rs-cacimba"}
::
## All examples
| Example | Place | Depth | What it shows | Id |
| -------------------------------------------------------------------------------------------------------------------- | ------------------ | ------ | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- |
| [Public supply in a crystalline aquifer](https://welldot.org/examples/coluna-mg-cristalino-abastecimento.well) | Coluna/MG | 204 m | Granite under thick saprolite, Nold screens, 24 h test, permits, meters and production. | `coluna-mg-cristalino-abastecimento` |
| [Industrial karst well, decommissioned](https://welldot.org/examples/sete-lagoas-mg-carste-industrial.well) | Sete Lagoas/MG | 218 m | Bambuí limestone with a cavity, old test record, 1989 analysis, plugged in 2021. | `sete-lagoas-mg-carste-industrial` |
| [Irecê karst: caves and nitrate](https://welldot.org/examples/irece-ba-carste-bambui.well) | Irecê/BA | 60 m | Bambuí limestone with caves, 85 m³/h/m specific capacity, very hard water and nitrate above the limit in 2023. | `irece-ba-carste-bambui` |
| [Public supply in the Lagoa Santa karst](https://welldot.org/examples/lagoa-santa-mg-carste-abastecimento.well) | Lagoa Santa/MG | 78 m | 100 m³/h with 3 m of drawdown, 24 h test, telemetry and a permit renewed with a lower rate inside the protected area. | `lagoa-santa-mg-carste-abastecimento` |
| [Incomplete record](https://welldot.org/examples/minas-novas-mg-cadastro-incompleto.well) | Minas Novas/MG | 210 m | Macaúbas schist; construction and lithology only, no test or analysis. | `minas-novas-mg-cadastro-incompleto` |
| [Brackish water in semi-arid crystalline rock](https://welldot.org/examples/quixeramobim-ce-cristalino-salobro.well) | Quixeramobim/CE | 60 m | Fractured biotite schist, 3.5 g/L TDS, community desalinator and salinity during the 2012–2017 drought. | `quixeramobim-ce-cristalino-salobro` |
| [Rural gneiss well: from insignificant use to a permit](https://welldot.org/examples/colatina-es-gnaisse-rural.well) | Colatina/ES | 80 m | One producing fracture at 67 m, insignificant-use certificate, irrigation permit with a meter and yearly reports. | `colatina-es-gnaisse-rural` |
| [1561 m stratigraphic well (Guarani)](https://welldot.org/examples/rinopolis-sp-estratigrafico-guarani.well) | Rinópolis/SP | 1561 m | Bauru, 1092 m of basalt and confined Guarani; flowing, alkaline thermal water. | `rinopolis-sp-estratigrafico-guarani` |
| [Telescoping well, Bauru + Serra Geral](https://welldot.org/examples/estrela-doeste-sp-bauru-serra-geral.well) | Estrela d'Oeste/SP | 201 m | Five hole diameters, five screens, mixed intake and rising nitrate. | `estrela-doeste-sp-bauru-serra-geral` |
| [Inconsistent data](https://welldot.org/examples/gastao-vidigal-sp-dados-inconsistentes.well) | Gastão Vidigal/SP | 207 m | Casing from 0.7 m, duplicated grout and an implausible test, kept on purpose. | `gastao-vidigal-sp-dados-inconsistentes` |
| [Deep urban supply well (Barreiras/Pirabas)](https://welldot.org/examples/belem-pa-pirabas-abastecimento.well) | Belém/PA | 265 m | 14"→8" reduction, stainless screens, step test, permit, telemetry and 25 layers. | `belem-pa-pirabas-abastecimento` |
| [Licensed condominium well (CNARH + permit)](https://welldot.org/examples/belem-pa-condominio-outorga.well) | Belém/PA | 60 m | Current regulatory flow: CNARH, permit with conditions, meter and monthly readings. | `belem-pa-condominio-outorga` |
| [Water quality: acidic water and censored results](https://welldot.org/examples/belem-pa-qualidade-agua-acida.well) | Icoaraci, Belém/PA | 60 m | pH below 5, values below the detection limit, duplicate, blank and bacterial contamination. | `belem-pa-qualidade-agua-acida` |
| [Clogged well](https://welldot.org/examples/belem-pa-poco-colmatado.well) | Belém/PA | 102 m | Specific capacity loss, camera inspection, failed rehabilitation and plugging. | `belem-pa-poco-colmatado` |
| [Abandoned shallow well](https://welldot.org/examples/belem-pa-poco-raso-abandonado.well) | Belém/PA | 12 m | 12 m jetted well, 48 m³/h, contaminated by sewage and abandoned unplugged. | `belem-pa-poco-raso-abandonado` |
| [Flowing well in the Alter do Chão aquifer](https://welldot.org/examples/santarem-pa-jorrante-alter-do-chao.well) | Santarém/PA | 160 m | Head above ground, 52 h test and Jacob analysis (T = 0.003 m²/s). | `santarem-pa-jorrante-alter-do-chao` |
| [Rural hand-dug well](https://welldot.org/examples/sao-gabriel-rs-cacimba.well) | São Gabriel/RS | 10 m | Hand-dug well, 2024 flood, coliforms and disinfection. | `sao-gabriel-rs-cacimba` |
| [Shallow Dunas-Barreiras well with nitrate](https://welldot.org/examples/extremoz-rn-barreiras-raso.well) | Extremoz/RN | 26 m | Stainless screens, flowing in 1978 and now 3 m below ground; nitrate above 10 mg/L N since 2020. | `extremoz-rn-barreiras-raso` |
| [Fuel station monitoring well](https://welldot.org/examples/belem-pa-monitoramento-posto.well) | Belém/PA | 12 m | 2" well, low-flow sampling and BTEX decline during remediation. | `belem-pa-monitoramento-posto` |
## Use them in these docs
Writers can draw any example on a page with its id:
```mdc
::well-preview{example="santarem-pa-jorrante-alter-do-chao"}
::
```
See [Content components](/contributing/components).
## How they are checked
The `@welldot/core` test suite checks that every example parses, survives a serialize and parse round trip, and matches the gallery index. This site's CI validates every example a page uses.
# Reference
> Lookup material for the .well format, generated from the schema and the spec: objects and fields, the JSON Schema, vocabularies and example files.
These pages are for looking things up. The object reference and the JSON Schema are generated from the reference implementation, so they always match it. The meaning of each field, and the rules around it, are in the [specification](/format/spec), and every object page links to its section there.
::card-group
:::card{title="Object reference" icon="i-lucide-table-properties" to="/format/reference/objects"}
Every block and object of a `.well` file, with its fields, types, constraints and spec section.
:::
:::card{title="JSON Schema" icon="i-lucide-file-json" to="/format/reference/json-schema"}
The published schema, how it is generated and how to validate with it in any language.
:::
:::card{title="Example wells" icon="i-lucide-library" to="/format/reference/examples"}
Nineteen complete files with live previews, for learning and testing.
:::
:::card{title="FGDC textures" icon="i-lucide-grid-3x3" to="/format/spec/reference/fgdc-textures"}
All 282 lithology pattern codes used by `texture`.
:::
::
## Vocabularies
The recommended and allowed values of each field are listed in the specification:
- [Recommended vocabularies](/format/spec/v2/format-reference#recommended-vocabularies): construction methods, uses, materials, event types, log categories, permit types and more.
- [Water quality recommended values](/format/spec/v2/water-quality#recommended-values) and the [parameter vocabulary](/format/spec/v2/water-quality#parameter-vocabulary).
- [Texture vocabularies](/format/spec/v2/object-schemas#vocabulary-values) for lithology patterns.
The same lists, with English and Portuguese labels, are exported by `@welldot/core` as data ([Vocabularies and textures](/core/guides/vocabularies-and-textures)). [Vocabularies](/format/concepts/vocabularies) explains open and closed lists.
## Units
Every field's unit is in the spec's [field-to-unit binding](/format/spec/v2/format-reference#field-to-unit-binding-normative). [Units and precision](/format/concepts/units-and-precision) summarizes it.
# The specification
> The normative .well specification, how it is organized, how it relates to this site and to the packages, and how it changes.
The specification is the standard that defines the `.well` format. It is written as plain Markdown in the [`spec/` folder](https://github.com/welldothq/welldot/tree/main/spec) of the repository and rendered on this site as it is, with heading anchors and links adapted for the web. Everything else on this site explains it; when a guide and the spec seem to disagree, the spec is right.
The current revision is **2.4**.
## The documents
| Document | What it covers |
| ----------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Overview](/format/spec/v2/overview) | The changes in each revision, the purpose of the format, its design principles and its known limitations. |
| [Format reference](/format/spec/v2/format-reference) | Top-level fields, versioning and deprecation, datetimes, recommended vocabularies, `well_id`, `location`, units, profiles, extensibility, cross-references and precision. |
| [Object schemas](/format/spec/v2/object-schemas) | Every object: construction, geology, hydrodynamic events, aquifer analysis, history logs, pumps, permits, meters, production, operating regime, and a complete example. |
| [Water quality](/format/spec/v2/water-quality) | Water samples and results, their units, the parameter vocabulary and regulatory profiles. |
| [Interoperability](/format/spec/v2/interoperability) | The reserved JSON-LD context, the mapping to GroundWater ML 2 and JSON Schema validation. |
| [FGDC textures](/format/spec/reference/fgdc-textures) | The 282 lithology pattern codes. |
| [v1 (archived)](/format/spec/v1/well-format) | The first version, kept for reference. v2 readers upgrade v1 files on read. |
## How to read it
The spec uses the keywords MUST, SHOULD and MAY in their usual standards sense: MUST is a requirement, SHOULD a strong recommendation, MAY an option. Sections marked _(since v2.x)_ were added in that revision.
If you are new to the format, start with [What is .well?](/format/getting-started/what-is-well) and the [concepts](/format/concepts/depths-and-intervals), then come back to the spec for the exact rules.
## How it relates to the code
- [`@welldot/core`](/core) implements the spec. Its tests compare the vocabulary tables in the spec with the published constants and parse the spec's complete example, so the two cannot drift apart silently.
- The [JSON Schema](/format/reference/json-schema) is generated from `@welldot/core` and published at `https://welldot.org/schema/v2/well.schema.json`.
- The [object reference](/format/reference/objects) is generated from the schema and links back to the spec sections.
## How it changes
Changes to the format start as an issue on [GitHub](https://github.com/welldothq/welldot/issues). The spec text is edited first, then the implementation and these docs, in the same change ([update rules](/contributing/update-rules)). Each revision adds a "Changes in v2.x" section to the overview; the [changelog](/format/changelog) links to them.
The spec files are licensed under Apache 2.0, like the rest of the project.
# Format changelog
> The revisions of the .well format, each linking to its "Changes in v2.x" section in the specification overview.
The format's changelog is part of the specification: each revision adds a "Changes in v2.x" section to the [overview](/format/spec/v2/overview). This page lists the revisions and links to those sections, so there is one record of what changed. Package releases have their own changelogs ([core](/core/changelog), [utils](/utils/changelog), [render](/render/changelog)).
| Revision | `version` written | Shipped in | Changes |
| -------- | ----------------- | --------------------- | ----------------------------------------------------------- |
| 2.4 | `"2.4"` | `@welldot/core` 0.5.0 | [Changes in v2.4](/format/spec/v2/overview#changes-in-v24) |
| 2.3 | `2` | `@welldot/core` 0.4.0 | [Changes in v2.3](/format/spec/v2/overview#changes-in-v23) |
| 2.1 | `2` | `@welldot/core` 0.3.0 | [Changes in v2.1](/format/spec/v2/overview#changes-in-v21) |
| 2.0 | `2` | `@welldot/core` 0.2.0 | [Changes from v1](/format/spec/v2/overview#changes-from-v1) |
| 1 | `1` | | [v1 specification (archived)](/format/spec/v1/well-format) |
## In brief
- **2.4** writes the version as the string `"M.m"` and adds the site time zone, data logger series, offline `.wellz` packages and vendor-named extensions, plus clarifications. Readers older than `@welldot/core` 0.5.0 reject `"2.4"` files. No deprecations ([details](/format/spec/v2/overview#changes-in-v24)).
- **2.3** adds the operational blocks (attachments, pumps, permits, meters, production, operating regime) and water quality samples, with recommended vocabularies for most open fields. No deprecations ([details](/format/spec/v2/overview#changes-in-v23)).
- **2.1** adds `well_purpose` and `centralizers`, and deprecates `well_type: "artesian"` ([details](/format/spec/v2/overview#changes-in-v21), [deprecations](/format/spec/v2/overview#deprecations-1)).
- **2.0** adds `location`, `well_id`, profiles, the texture object, hydrodynamic events, aquifer analysis, history logs and precision fields, and fixes all units to SI ([details](/format/spec/v2/overview#changes-from-v1)).
Within version 2, every revision is additive: a valid file from an earlier revision stays valid ([deprecation policy](/format/spec/v2/format-reference#deprecation-policy)). [Versions and deprecation](/format/concepts/versions-and-deprecation) explains how readers handle them.
# `.well` File Format Specification — Version 2.4: Overview
**Version:** 2.4 **Extension:** `.well` **Encoding:** UTF-8 **Base format:** JSON **MIME type:** `application/vnd.well+json` **JSON Schema:** `https://welldot.org/schema/v2/well.schema.json` **JSON Schema draft:** 2020-12 **JSON-LD Context (optional):** `https://welldot.org/context/v2.jsonld` **Status:** v2.0 ratified — shipped in `@welldot/core` v0.2.0 · v2.1 shipped in `@welldot/core` v0.3.0 · v2.3 shipped in `@welldot/core` v0.4.0 · v2.4 shipped in `@welldot/core` v0.5.0
**See also:** [format-reference.md](./format-reference.md) · [object-schemas.md](./object-schemas.md) · [interoperability.md](./interoperability.md) · [water-quality.md](./water-quality.md)
## Overview
The `.well` v2 specification is split into five documents. This one gives the context: what changed in each revision (v2.4, v2.3, v2.1 and from v1), why the format exists, the design principles the other parts follow, and the known limitations. The other four are:
- [format-reference.md](./format-reference.md) — the top-level fields, versioning and deprecation, datetimes, recommended vocabularies, `well_id`, `location`, units, profiles, extensibility, cross-reference rules and precision.
- [object-schemas.md](./object-schemas.md) — every object schema, the `hydrodynamic_events` ledger, `aquifer_analysis`, `history_logs`, the operational blocks (pumps, permits, meters, production, operating regime) and a complete example.
- [water-quality.md](./water-quality.md) — water samples and results, their units, the parameter vocabulary and regulatory profiles.
- [interoperability.md](./interoperability.md) — the JSON-LD context, the relationship to GWML2 and JSON Schema validation.
## Table of contents
- [Changes in v2.4](#changes-in-v24)
- [Deprecations](#deprecations)
- [Changes in v2.3](#changes-in-v23)
- [Changes in v2.1](#changes-in-v21)
- [Deprecations](#deprecations-1)
- [Changes from v1](#changes-from-v1)
- [Purpose](#purpose)
- [Design Principles](#design-principles)
- [Known Limitations of v2](#known-limitations-of-v2)
---
## Changes in v2.4
v2.4 files write `"version": "2.4"`; parsers up to `@welldot/core` 0.4.0 reject them. Beyond that, v2.4 is an additive revision: every valid v2.3 document stays valid when its `version` is rewritten, and the integer `2` still means "v2.0 to v2.3, revision unknown".
```
v2.4 additions:
- version is the string "M.m" (^\d+\.\d+$), compared as two integers,
never as text; patch levels are not allowed; readers accept 1, 2 and
"M.m"
- location.properties.timezone (optional, IANA name): the local date of
an instant is its date converted to this zone; a missing timezone in
a file with condition fulfillments emits the warning missing_timezone
- logger_series (hydrodynamic event type): a continuous level series
recorded by an installed sensor, with sensor_depth, interval,
barometric_compensation and aggregation (none, hourly, daily with
depth_min / depth_max); not a controlled test, not a static level
- LevelReading.datetime: the instant of a reading, required in a
logger_series, where elapsed is absent
- Offline packaging: an optional ZIP (.wellz) with the .well file at
the root and attachments at relative paths; relative uri allowed and
sha256 required only inside the package
- Application extensions: x-- is RECOMMENDED
(e.g. x-welldot-custom)
v2.4 clarifications:
- A known hydrodynamic event type is validated against its own schema
and no longer falls back to the open x- rule; step counts per type
are SHOULD (a free count is valid), and steps must be valid
PumpingStep entries
- An optional field without a value is omitted, never written as null
(storativity is no longer nullable); v2 integer files that still
carry it are tolerated on read
- Ledgers MAY be edited in place; editing is NOT RECOMMENDED after a
record has been exported, and corrects remains the way to retract a
measurement. Result.validation is editable too
- Repeated records (collective permits, campaign blanks) share the
same id and SHOULD have identical content; consumers MAY merge them
by id. A fulfillment event_id or sample_id resolves only in the file
of the well that produced it
- Unresolved references MUST be preserved when a file is re-exported,
never silently removed
- A duplicated operating_regime.effective_from and an
estimated_volume period_end not after period_start are warnings
- daily_operating_time above 24 is normalized to 24 with a warning
(the stored value stays as written); a negative value is an error
- Attachment.document_type condition_evidence is typically found in
permits[].conditions[].fulfillments
- The attachment lists (id uniqueness, "root is not an aggregate",
where attachments are allowed) name the same set of records
- history_logs updated_at may be earlier than datetime for a pre-dated
entry; neither ordering emits a warning
- well_id: producers SHOULD mark exactly one entry primary: true; "the
first entry is canonical" is only the fallback
```
### Deprecations
None.
---
## Changes in v2.3
v2.3 is an additive minor revision. Every valid v2.1 document is a valid v2.3 document, and documents keep declaring `"version": 2`. It adds the operational blocks (equipment, documents, permits, metering, production and operating regime) and the water quality block (`water_samples`).
```
v2.3 additions:
- attachments (optional Attachment[]): root-level documents about the
well as a whole (drilling report, as-built drawing, registry record…)
- Attachment becomes a common type and gains document_type
(drilling_report, as_built_drawing, registry_record, photo,
permit_document, condition_evidence, pump_curve, field_sheet,
test_report, lab_report, invoice)
- pump_installations (optional PumpInstallation[]): pump installation
history, with nameplate, intake depth, riser and electrical data
- permits (optional Permit[]): legal instruments governing abstraction
(outorga, dispensa, cadastro) with validity, granted flow, daily
operating time, volume limits, monthly schedule and conditions
(condicionantes) whose deadlines are derived from ISO 8601 date
durations; renewals are linked with supersedes. Each permit carries
its identifier and/or request_identifier (verbatim, any format), a
closed administrative status (requested, granted, suspended,
revoked, denied, withdrawn), an administrative history (filing,
process, notifications, fees, with done/due_date and attachments)
and, on each condition, a responsible and the fulfillments of its
deadlines (datetime, due_date, author, event_id, sample_id,
attachments)
- meters (optional Meter[]): totalizer (hidrômetro) installation
history; device facts only (type, serial, nominal diameter,
max_reading for rollover)
- production (optional ProductionEntry[]): ledger of
meter_reading entries (register values in m³) and estimated_volume
entries (volumes estimated for a period without a meter); volumes
are derived from consecutive readings, never stored as totals.
Volumes declared to a regulator are not production data: they are
the fulfillment of the permit's reporting condition
- operating_regime (optional OperatingRegime[]): declared flow rate,
daily operating time and days per week, each entry in force from
effective_from
- history_logs category status_change, with status (active,
maintenance, inactive, decommissioned, abandoned): the well's
operating status; current status is unknown without one
- history_logs category maintenance gains maintenance_type,
pump_installation_id and meter_id
- history_logs gains hydrodynamic_event_ids and sample_ids (any
category): references to the data an entry produced or observed,
never copies of measured values
- hydrodynamic_events[].corrects: retracts an earlier event (ledger
correction)
- hydrodynamic_events[].attachments, aquifer_analysis[].attachments
- Canonical units kW (power), V (voltage), mm² (cable cross-section),
m³ (volume), h (daily operating time only)
- water_samples (optional WaterSample[]): ledger of water samples,
each with its field and laboratory results in one list
(distinguished by measured_in), sampling point (point depth or
isolated interval, or the production pump), purge and stabilization
readings, laboratory and chain of custody, field QA/QC
(field_duplicate, split_sample via parent_sample_id; blanks),
per-result filtration, lab flags and data validation; corrected
reports and revalidations are new samples with corrects
- Parameter identity { code, vocabulary } with vocabularies welldot,
cas and x-…; the welldot parameter vocabulary (98 codes) is
published as versioned data in @welldot/core, with CAS
equivalences; the unit of a welldot or cas parameter comes from its
code (substances always mg/L), and unit (UCUM) exists only for x-
codes
- Canonical units mg/L, µS/cm at 25 °C, °C, mV, NTU / FNU / FAU / FTU
(by code), Hazen (uH), MPN/100 mL, CFU/100 mL, CFU/mL, Bq/L and µm
- _resolution convention: analyzed_at_resolution and
laboratory.received_at_resolution set to "day" mark an instant
known only by its date; consumers MUST ignore the time of day
- permit condition fulfillments gain sample_id (e.g. a water-quality
monitoring condition)
- well_screen[].slot_type (screen design: continuous_slot, slotted,
bridge_slot, louver, perforated, nold); type remains the screen
material
- Recommended vocabularies for drilling_method, construction materials
(well_case / well_screen / reduction type, riser_material),
well_screen slot_type,
centralizer steels, cement_pad.type, history_logs severity,
measurement_method and aquifer_analysis method; every recommended
vocabulary is published by @welldot/core with en/pt labels
v2.3 clarifications:
- Block kinds: every top-level array is a ledger, a mutable record or
an installation; the kind decides how records are corrected
- Open vocabulary values are stored as their key, never as a
translated label
- Datetime families: legal documents use calendar dates (YYYY-MM-DD),
everything else uses RFC 3339 instants
- Naming rules: no units in field names; nameplate values use the
rated_ prefix; time intervals use period_start / period_end
- supersedes (legal succession between permits) is distinct from
corrects (ledger retraction)
- Permit status and condition deadlines are derived on the local civil
date at the well site, never stored
- Volume derivation is normative: rollover via max_reading, never
across two meters, unknown (never zero or negative) where readings
do not cover; metered volume takes precedence over estimated
volumes
- history_logs category change_of_use is no longer a recommended
value; a change of use is an event entry (the current use lives in
well_purpose)
- Jurisdiction neutrality: water quality limits, exceedances and
national rules are never written to the file; limit sets (WHO, EU,
Brazil…) are data in @welldot/core and local requirements enter
through profiles
```
v2.3 contains no deprecations.
---
## Changes in v2.1
v2.1 is an additive minor revision. Every valid v2.0 document is a valid v2.1 document, and documents keep declaring `"version": 2`.
```
v2.1 additions:
- well_purpose (optional string[]): intended use(s) of the well —
production, monitoring, piezometer, water_level_indicator (INA),
observation, exploration, injection, dewatering
- centralizers (optional Centralizer[]): casing/screen centralizers
as depth interval + spacing
- history_logs category `change_of_use`
v2.1 clarifications:
- well_type describes the construction method only
- Level sign convention: water levels above ground are negative
(made explicit; already implied by "ground level as zero")
```
### Deprecations
| Deprecated | Replacement | Removal |
| ----------------------- | --------------------------------------------------------------------------------------------------------- | ------- |
| `well_type: "artesian"` | Construction method in `well_type` (usually `tubular`) + negative `static_level` in `hydrodynamic_events` | v3 |
---
## Changes from v1
```
v2.0 additions:
- location object (supersedes top-level lat/lng/elevation)
- well_id (array of authority-scoped identifiers)
- profiles (JSON Schema URLs for regulatory and domain-specific constraints)
- texture object (multi-vocabulary, replaces v1 fgdc_texture string)
- hydrodynamic_events (ledger of water-level observations)
- aquifer_analysis (interpreted aquifer parameter sets)
- history_logs (operational log of interventions and incidents,
with updated_at edit timestamp)
- optional @context (reserved for JSON-LD interoperability)
- optional *_precision fields on key measurements
- WellScreen.screen_slot renamed from screen_slot_mm
v2.0 tightenings:
- All numeric values are SI by spec. The format does not encode a
units declaration; see § Units for the canonical mapping. v1 was
de facto SI in practice and no v1 file in existence declared
non-SI units, so this is a clarification rather than a breaking
change. Display and input units are application concerns.
v2.0 preserves all v1 fields. Parsers must accept v1 documents
unchanged and must accept the following v1-to-v2 normalizations:
- Top-level lat/lng/elevation → location.lat/lng/elevation
- fgdc_texture: "" → texture: { code, vocabulary: "fgdc" }
- screen_slot_mm → screen_slot (value preserved; both versions are
in millimeters)
- bole_hole → bore_hole (v1 typo compatibility)
- diam_pol (inches) → diameter (mm), multiplied by 25.4
```
---
## Purpose
The `.well` format is an open standard for representing water well data. It is designed to serve three distinct but complementary use cases:
**Visualization.** The format contains sufficient constructive and geological detail to produce accurate technical well profile drawings — including borehole geometry, casing strings, screens, gravel packs, lithological columns, fractures, and caves — at true depth scale.
**Registration.** The format captures the administrative and physical identity of a well, providing a complete static record suitable for submission to regulatory bodies, inclusion in national well registries, and long-term archival.
**Research.** The structured geological and constructive data, combined with the hydrodynamic event ledger and interpreted aquifer parameters, enables cross-well analysis, aquifer characterization, and hydrogeological research. Field vocabulary choices are aligned with international standards.
---
## Design Principles
- **Self-describing** — full field names are used throughout; a `.well` file can be read and understood without consulting the spec.
- **Versioned** — includes a `version` field for forward compatibility; parsers must reject unrecognized versions.
- **Self-contained** — a single file encodes the complete record of one well.
- **Units are SI, declared by spec** — all numeric values in a `.well` file are stored in SI units. The format does not encode a per-file units declaration. See § Units for the canonical unit of each quantity. Conversion to and from a user's preferred display or input units is an application concern.
- **CRS is declared, not assumed** — geographic coordinates are accompanied by an explicit coordinate reference system declaration. The default is WGS84 (`EPSG:4326`).
- **Ground level as zero** — all depth values are measured from ground level (0); elevation above the WGS84 ellipsoid (or declared datum) is stored separately in `location.elevation`. Water levels above ground are negative.
- **Geographic north** — all azimuth values are referenced to geographic north.
- **Three block kinds** — every top-level array is one of three kinds, and the kind decides how records are corrected:
- **Ledger** (`hydrodynamic_events`, `production`, `water_samples`) — grows by appending. Entries MAY be edited in place, but editing is NOT RECOMMENDED after an entry has been exported. A measurement correction (a re-measurement, a revised laboratory report) is best written as a new entry whose `corrects` points to the retracted entry; a retracted entry stays in the file but is excluded from every derivation. The format provides `id`, `datetime`, `sequence` and `corrects` to support this discipline; it does not enforce it.
- **Mutable record** (`history_logs`, `permits`, `operating_regime`) — edited in place; `updated_at` records the last edit.
- **Installation** (`pump_installations`, `meters`) — equipment present in the well for a period (`installed_at` / `removed_at`); edited in place with `updated_at`.
- **Derived values are never stored raw** — `s/Q`, `Q/s`, drawdown `s`, and any other value computable from stored fields must not appear as stored fields. Applications compute them on demand.
- **Jurisdiction neutrality** — the file records what was measured, never what it means under a given rule. Water quality limits, exceedances and national requirements are not stored and no field names a national standard; limit sets (WHO, EU, Brazil, …) are versioned data in `@welldot/core`, chosen by the consumer at display time, and local obligations (required parameters, sampling frequencies) enter through profiles.
- **Interpreted results are versioned** — `aquifer_analysis` records which method was used, who performed the analysis, and which events were used as input. Multiple analyses may coexist.
- **Graceful incompleteness** — a record with only a flow rate and a dynamic level is valid. A record with only a static level measurement is valid. Validators must not reject incomplete events.
- **Mutable operational log with edit traceability** — `history_logs` entries are editable to allow correction of field errors. Each entry carries a separate `updated_at` timestamp that records when the entry itself was last modified, distinct from the `datetime` field that records when the logged event occurred.
- **Preserve unknown members** — parsers must preserve and pass through any unrecognized fields or top-level blocks without modification, following the GeoJSON foreign members convention. This ensures forward compatibility.
---
## Known Limitations of v2
The following are recognized limitations of this version, reserved for future versions:
- **No internationalization of text fields.** All free-text fields (`description`, `notes`, `author`, etc.) are opaque strings with no language tag. A file produced in Brazil will have Portuguese content; one from Norway will have Norwegian. v3 will consider BCP 47 language objects for text fields, with backward-compatible string fallback.
- **No interpreted water quality assessment.** `water_samples` records samples and measured results only. Interpreted assessments (water class, hydrochemical facies as a stored judgment, fitness for use) have no block yet; facies, ion balance and exceedances are derived on demand.
- **Collective permits are repeated per well.** A grant covering several wells is repeated in each well's file until cross-file references arrive in v3.
- **No geophysical logs block.** Downhole geophysical surveys (resistivity, gamma ray, caliper) are out of scope for v2 and reserved for v3.
- **No multi-well linking.** Storativity determination requires observation well data. The top-level `references` field name is reserved for v3 to link wells across files.
---
_`.well` Format Specification v2.4_
# `.well` File Format Specification — Version 2.4: Format Reference
**See also:** [overview.md](./overview.md) · [object-schemas.md](./object-schemas.md) · [interoperability.md](./interoperability.md) · [water-quality.md](./water-quality.md)
## Overview
This part of the `.well` v2.4 specification defines the file as a whole: the top-level structure and fields, how versions and deprecations work, datetime conventions, the recommended values of every open vocabulary, `well_id` and `location`, the canonical units and which unit each field uses, `profiles`, the `x-` extension rules, cross-reference and uniqueness rules, and precision fields. The objects inside each block are specified in [object-schemas.md](./object-schemas.md).
## Table of contents
- [`.well` File Format Specification — Version 2.4: Format Reference](#well-file-format-specification--version-24-format-reference)
- [Overview](#overview)
- [Table of contents](#table-of-contents)
- [Top-level Structure](#top-level-structure)
- [Absent values _(since v2.4)_](#absent-values-since-v24)
- [Versioning](#versioning)
- [Parser version handling](#parser-version-handling)
- [Deprecation Policy](#deprecation-policy)
- [Field lifecycle](#field-lifecycle)
- [Parser behavior](#parser-behavior)
- [Vocabulary value promotion](#vocabulary-value-promotion)
- [Canonical deprecation registry](#canonical-deprecation-registry)
- [Top-level Fields](#top-level-fields)
- [Core identity](#core-identity)
- [v2 additions](#v2-additions)
- [v2.1 additions](#v21-additions)
- [v2.3 additions](#v23-additions)
- [Block kinds _(since v2.3)_](#block-kinds-since-v23)
- [Naming rules _(since v2.3)_](#naming-rules-since-v23)
- [Datetime Conventions](#datetime-conventions)
- [Instants known only by date _(since v2.3)_](#instants-known-only-by-date-since-v23)
- [Ordering and ties](#ordering-and-ties)
- [Recommended vocabularies](#recommended-vocabularies)
- [`well_type` — Recommended values](#well_type--recommended-values)
- [`well_purpose` — Recommended values _(since v2.1)_](#well_purpose--recommended-values-since-v21)
- [`bore_hole[].drilling_method` — Recommended values _(since v2.3)_](#bore_holedrilling_method--recommended-values-since-v23)
- [Construction materials — Recommended values _(since v2.3)_](#construction-materials--recommended-values-since-v23)
- [`well_screen[].slot_type` — Recommended values _(since v2.3)_](#well_screenslot_type--recommended-values-since-v23)
- [`centralizers[].type` — Recommended values _(since v2.1)_](#centralizerstype--recommended-values-since-v21)
- [`cement_pad.type` — Recommended values _(since v2.3)_](#cement_padtype--recommended-values-since-v23)
- [`pump_installations[].type` — Recommended values _(since v2.3)_](#pump_installationstype--recommended-values-since-v23)
- [`pump_installations[].power_source` — Recommended values _(since v2.3)_](#pump_installationspower_source--recommended-values-since-v23)
- [`permits[].type` — Recommended values _(since v2.3)_](#permitstype--recommended-values-since-v23)
- [`permits[].water_use` — Recommended values _(since v2.3)_](#permitswater_use--recommended-values-since-v23)
- [`permits[].conditions[].category` — Recommended values _(since v2.3)_](#permitsconditionscategory--recommended-values-since-v23)
- [`permits[].history[].type` — Recommended values _(since v2.3)_](#permitshistorytype--recommended-values-since-v23)
- [`meters[].type` — Recommended values _(since v2.3)_](#meterstype--recommended-values-since-v23)
- [`production[]` — Recommended values _(since v2.3)_](#production--recommended-values-since-v23)
- [`water_samples[]` — Recommended values _(since v2.3)_](#water_samples--recommended-values-since-v23)
- [`hydrodynamic_events[].measurement_method` — Recommended values](#hydrodynamic_eventsmeasurement_method--recommended-values)
- [`aquifer_analysis[].method` — Recommended values](#aquifer_analysismethod--recommended-values)
- [`history_logs[].category` — Recommended values](#history_logscategory--recommended-values)
- [`history_logs[].severity` — Recommended values](#history_logsseverity--recommended-values)
- [`history_logs[].maintenance_type` — Recommended values _(since v2.3)_](#history_logsmaintenance_type--recommended-values-since-v23)
- [`history_logs[].status` — Allowed values _(since v2.3)_](#history_logsstatus--allowed-values-since-v23)
- [`Attachment.document_type` — Recommended values _(since v2.3)_](#attachmentdocument_type--recommended-values-since-v23)
- [`well_id`](#well_id)
- [Numeric ID conversion](#numeric-id-conversion)
- [Uniqueness](#uniqueness)
- [`location`](#location)
- [`LocationProperties`](#locationproperties)
- [`elevation_datum` — Recommended values](#elevation_datum--recommended-values)
- [CRS scope](#crs-scope)
- [Units](#units)
- [Canonical units](#canonical-units)
- [Field-to-unit binding (normative)](#field-to-unit-binding-normative)
- [Water quality units _(since v2.3)_](#water-quality-units-since-v23)
- [Level sign convention](#level-sign-convention)
- [Diameter and the as-built convention](#diameter-and-the-as-built-convention)
- [Screen slot](#screen-slot)
- [Rationale](#rationale)
- [Water quality parameter vocabulary _(since v2.3)_](#water-quality-parameter-vocabulary-since-v23)
- [`profiles`](#profiles)
- [Profile schema requirements](#profile-schema-requirements)
- [Example: a regulatory water quality profile _(since v2.3)_](#example-a-regulatory-water-quality-profile-since-v23)
- [Profile validation](#profile-validation)
- [Extensibility](#extensibility)
- [Custom fields — `x-` prefix](#custom-fields--x--prefix)
- [Custom top-level blocks — `x-` prefix required](#custom-top-level-blocks--x--prefix-required)
- [Custom vocabulary values — `x-` prefix](#custom-vocabulary-values--x--prefix)
- [Offline packaging _(since v2.4)_](#offline-packaging-since-v24)
- [Cross-reference and Uniqueness Rules](#cross-reference-and-uniqueness-rules)
- [Uniqueness scope](#uniqueness-scope)
- [Cross-array references](#cross-array-references)
- [Dangling references](#dangling-references)
- [Repeated records _(since v2.4)_](#repeated-records-since-v24)
- [Duplicate IDs within a scope](#duplicate-ids-within-a-scope)
- [`id` field convention](#id-field-convention)
- [Precision and Uncertainty](#precision-and-uncertainty)
- [Supported precision fields](#supported-precision-fields)
---
## Top-level Structure
```json
{
"version": "2.4",
"@context": "https://welldot.org/context/v2.jsonld",
"well_id": [ ... ],
"location": { ... },
"name": "...",
"well_type": "...",
"well_purpose": [ ... ],
"well_driller": "...",
"construction_date": "YYYY-MM-DD",
"obs": "...",
"profiles": [ ... ],
"bore_hole": [ ... ],
"well_case": [ ... ],
"reduction": [ ... ],
"well_screen": [ ... ],
"surface_case": [ ... ],
"hole_fill": [ ... ],
"centralizers": [ ... ],
"cement_pad": { ... },
"lithology": [ ... ],
"fractures": [ ... ],
"caves": [ ... ],
"hydrodynamic_events": [ ... ],
"aquifer_analysis": [ ... ],
"history_logs": [ ... ],
"attachments": [ ... ],
"pump_installations": [ ... ],
"permits": [ ... ],
"meters": [ ... ],
"production": [ ... ],
"operating_regime": [ ... ],
"water_samples": [ ... ]
}
```
### Absent values _(since v2.4)_
An optional field without a value is omitted, never written as `null`. A `null` in an optional field is malformed. Absent means unknown.
Readers accept `null` in `aquifer_analysis[].storativity` only in files whose `version` is the integer `2`, and treat it as absent.
---
## Versioning
| Version | Description |
| ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `1` | Initial release — constructive and geologic data only |
| `2` | Adds location, well_id (array), profiles, texture object, hydrodynamic_events, aquifer_analysis, history_logs, optional @context, \*\_precision fields, and extensibility conventions. Tightens unit semantics: all values are canonical SI, no per-file declaration. |
| `"2.4"` | Version written as the string `"M.m"` (_since v2.4_). Read as v2; the integer `2` means v2.0 to v2.3. |
Minor revisions (`2.1`, `2.3`, …) are additive. Up to v2.3 they did not change the `version` integer: a v2.3 document declares `"version": 2`. A v2.0 parser reading a v2.1 document sees the v2.1 fields (`well_purpose`, `centralizers`) as unrecognized members without the `x-` prefix. Per § Extensibility it MUST preserve them and MUST NOT reject the file, and it SHOULD emit a warning.
The same rule applies to v2.3: a v2.0 or v2.1 parser reading a v2.3 document sees `attachments`, `pump_installations`, `permits`, `meters`, `production`, `operating_regime`, `water_samples`, `hydrodynamic_events[].corrects`, the `history_logs` data links (`hydrodynamic_event_ids`, `sample_ids`) and category-specific fields (`maintenance`, `status_change`) and the nested `attachments` arrays as unrecognized members. It MUST preserve them, MUST NOT reject the file, and SHOULD emit a warning. Such a parser does not know `corrects`, so it may still count a retracted event; this is the expected degradation.
_(since v2.4)_ The version is written as the string `"M.m"` (for example `"2.4"`). Versions are compared as two integers, never as text: `"2.10"` is greater than `"2.9"`. Patch levels (`"2.4.1"`) are not allowed. The integer `2` means v2.0 to v2.3 with the revision unknown, and it remains valid for reading. There is no separate `spec_version` field.
**Intentional break.** A parser of `@welldot/core` 0.4.0 or earlier rejects `"2.4"` files because it only accepts the integers `1` and `2`. This is the one intentional exception to the minor-revision compatibility rule above. Upgrade readers before writing v2.4 files.
### Parser version handling
The `version` field MUST be written by producers. It takes one of three forms: the integer `1`, the integer `2`, or a string `"M.m"` matching `^\d+\.\d+$`.
- A `version` that is none of these forms (`2.4` as a JSON number, `"2.4.1"`, `"v2.4"`, `"2"`) is malformed. A parser MUST reject the file with an error that names the value.
- A major other than `1` or `2` (`3`, `"3.0"`) is unsupported. A parser MUST reject the file with an error that names the value.
- Any minor of major `2` (`"2.4"`, a future `"2.9"`) is read as v2. Unknown members follow § Extensibility.
- A v2 parser encountering `version: 1` MUST apply the v1-to-v2 normalizations listed in the changes section and treat the document as v2-equivalent for all subsequent processing.
- A v1 parser encountering `version: 2` MUST reject the file with a clear error indicating an unsupported format version. v1 parsers MUST NOT attempt partial reads of v2 documents.
- Either parser encountering an unrecognized integer version MUST reject the file with a clear error.
- A file without a `version` field SHOULD be rejected. Parsers MAY emit a warning and attempt to read the file as v1 if all required v1 fields are present and no v2-specific blocks are detected, but this fallback behavior is implementation-specific and not guaranteed by the spec.
Files with `version: 1` MUST NOT contain any v2-only blocks (`hydrodynamic_events`, `aquifer_analysis`, `history_logs`, `attachments`, `pump_installations`, `permits`, `meters`, `production`, `operating_regime`, `water_samples`, `profiles`, `well_id` as array, `location` object, `@context`). A v1 file containing such fields is malformed.
---
## Deprecation Policy
The `.well` format evolves in two version dimensions:
- **Major versions** (`1`, `2`, `3`, …): may remove deprecated fields and introduce breaking changes. Each major version has its own canonical JSON Schema URL.
- **Minor revisions** within a major version (`2.0`, `2.1`, …): additive only. Minor revisions may introduce new fields, new vocabulary values, and **deprecation markers** for existing fields. Minor revisions MUST NOT remove fields, change field semantics, or break round-trip compatibility for files conforming to earlier minor revisions of the same major version.
### Field lifecycle
A field moves through three states:
1. **Active** — defined and recommended by the current minor revision.
2. **Deprecated** — still defined and still valid, but flagged for removal in the next major version. Deprecation is announced in a minor revision (e.g. introduced in v2.0, deprecated in v2.1).
3. **Removed** — no longer defined. May only occur in a new major version (e.g. removed in v3.0).
Each deprecation announcement MUST include, in the spec text for the relevant field:
- The minor revision in which the field was deprecated (e.g. _"Deprecated in v2.1"_).
- The major version in which the field will be removed (e.g. _"Will be removed in v3.0"_).
- The replacement field or pattern, when applicable.
- A v2-to-vNext normalization rule that parsers for the next major version MUST implement to read files using the deprecated field.
### Parser behavior
Parsers conformant to the version that introduced a deprecation:
- MUST accept the deprecated field as valid input.
- MUST preserve the deprecated field on round-trip unless explicitly migrating the document to a newer version.
- SHOULD emit a warning when encountering a deprecated field, naming the replacement.
- MAY offer a migration mode that rewrites the field to its replacement in place. Migration mode MUST be opt-in (e.g. an explicit flag passed to the parser), not the default behavior of read operations.
Parsers for the major version after removal:
- MUST apply the documented normalization when reading older files (e.g. a v3 parser reading a v2 file that used a v2-deprecated field).
- MUST NOT accept the removed field name in files declaring the new major version (e.g. a file with `version: 3` containing a v2-deprecated field is malformed).
### Vocabulary value promotion
When a vocabulary value previously available only with the `x-` prefix is promoted to canonical (e.g. `x-slug_test` becomes `slug_test`):
- The minor revision that performs the promotion MUST document both forms as equivalent.
- Parsers from that revision onward MUST treat both forms as equivalent for the remainder of the major version.
- The `x-` prefixed form is implicitly deprecated upon promotion and will be removed in the next major version, following the standard field lifecycle.
### Canonical deprecation registry
Each minor revision of this spec MUST include a "Deprecations" subsection in its changelog listing all fields and vocabulary values deprecated in that revision, with their replacement and removal timeline. The v2.0 release contains no deprecations.
---
## Top-level Fields
### Core identity
| Field | Type | Required | Description |
| ------------------- | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `version` | integer \| string | yes | Format version. v2.4 files write `"2.4"`. |
| `well_type` | string | yes | Construction method of the well. See vocabulary below. |
| `name` | string | no | Well name or local identifier. |
| `well_driller` | string | no | Name of the drilling company or individual. |
| `construction_date` | string | no | ISO 8601 calendar date of well completion (`YYYY-MM-DD`). Date-only by design — time of day for construction completion is not meaningful at the precision the well registry cares about. Interpreted as the local civil date at the well site. |
| `obs` | string | no | Free-text observations about the well. |
| `well_depth` | number | no | The well's current usable depth in meters from ground level, as measured or reported. May be less than the deepest constructive element (see `bore_hole`) due to siltation, debris, or partial backfill. |
### v2 additions
| Field | Type | Required | Description |
| ---------- | ------------------------- | -------- | ---------------------------------------------------------------------------- |
| `@context` | string \| array \| object | no | JSON-LD context. Reserved field; see § JSON-LD Context. |
| `well_id` | `WellId[]` | no | Array of authority-scoped identifiers. See § well_id. |
| `location` | `Location` | no | Geographic position and elevation. See § location. |
| `profiles` | string[] | no | Array of JSON Schema URLs declaring conformance to profiles. See § profiles. |
### v2.1 additions
| Field | Type | Required | Description |
| -------------- | --------------- | -------- | ---------------------------------------------------------------- |
| `well_purpose` | string[] | no | Intended use(s) of the well. See § well_purpose. |
| `centralizers` | `Centralizer[]` | no | Casing/screen centralizers. See object-schemas.md § Centralizer. |
### v2.3 additions
| Field | Type | Required | Description |
| -------------------- | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `attachments` | `Attachment[]` | no | Documents about the well as a whole, not tied to any record. See object-schemas.md § Attachment. |
| `pump_installations` | `PumpInstallation[]` | no | Pump installation history (installation block). See object-schemas.md § `pump_installations[]`. |
| `permits` | `Permit[]` | no | Legal instruments governing abstraction (mutable record block). See object-schemas.md § `permits[]`. |
| `meters` | `Meter[]` | no | Totalizer (hidrômetro) installation history (installation block). See object-schemas.md § `meters[]`. |
| `production` | `ProductionEntry[]` | no | Ledger of meter readings and declared volumes (ledger block). See object-schemas.md § `production[]`. |
| `operating_regime` | `OperatingRegime[]` | no | Declared operating regimes, each in force from `effective_from` (mutable record block). See object-schemas.md § `operating_regime[]`. |
| `water_samples` | `WaterSample[]` | no | Water samples and their field and laboratory results (ledger block). See water-quality.md. |
### Block kinds _(since v2.3)_
Every top-level array is one of three kinds. The kind decides how records are corrected.
| Kind | Blocks | Correction | Edit tracking |
| -------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
| Ledger | `hydrodynamic_events`, `production`, `water_samples` | MAY be edited in place (NOT RECOMMENDED after export). `corrects` is the recommended mechanism for measurement corrections. | `updated_at` where the block defines it; a `corrects` entry is the history of a correction. |
| Mutable record | `history_logs`, `permits`, `operating_regime` | Edit in place. | `updated_at` (instant) on the record. |
| Installation | `pump_installations`, `meters` | Edit in place. | `updated_at` (instant) on the record. |
Constructive and geologic arrays describe the well as built and are edited in place. `aquifer_analysis` entries are interpretations: a new interpretation is a new entry, and earlier ones coexist.
**Ledger corrections.** A ledger entry referenced by any `corrects` field is excluded from every derivation but stays in the file. Chains are allowed (C corrects B, which corrected A); only the last uncorrected entry counts. A `corrects` cycle is malformed and emits a warning. An `aquifer_analysis` whose `source_event_ids` or `static_level_source_id` points to a retracted event emits a warning naming both ids.
**Installation pattern.** Installation blocks describe equipment present in the well for a period, with `installed_at` (required) and `removed_at` (absent means currently installed).
- `removed_at` earlier than or equal to `installed_at` is malformed and emits a warning.
- Each entry is one installation, not one piece of equipment. A unit pulled and reinstalled gets a new entry with the same `serial`.
- Overlapping open installations in the same block emit a warning, not an error. Standby pumps and secondary meters exist.
### Naming rules _(since v2.3)_
- No unit in a field name. Units are bound by the field-to-unit table (§ Units).
- `flow_rate` is the canonical name for a volumetric rate; nameplate values use the `rated_` prefix (`rated_flow_rate`, `rated_head`, `rated_power`).
- `from` / `to` are reserved for depth intervals in meters. Time intervals use `period_start` / `period_end`.
- References to another array end in `_id` (single) or `_ids` (array), plus the two relationship fields `corrects` and `supersedes`.
**`supersedes` vs `corrects`.** `supersedes` expresses legal succession between two valid permits and appears only on `permits`. It is not a correction mechanism: `corrects` retracts ledger entries.
---
## Datetime Conventions
The `.well` format distinguishes two kinds of temporal values:
**Calendar dates** — `YYYY-MM-DD`, no time, no offset. Used for `construction_date` and, since v2.3, for legal documents: `permits[].issued_at`, `valid_from`, `valid_until`, `renewal_requested_at`; `permits[].conditions[].first_due`, `last_due`; `permits[].conditions[].fulfillments[].due_date`; and `permits[].history[].date`, `due_date`. Interpreted as the local civil date at the well site. Calendar dates are sortable lexicographically and applications MUST NOT attempt to derive an instant from them.
**Instants** — RFC 3339 datetime strings with a mandatory UTC offset (e.g. `2006-03-14T08:00:00-03:00` or `2006-03-14T11:00:00Z`). Used for all event, analysis, and log timestamps:
- `hydrodynamic_events[].datetime`
- `aquifer_analysis[].datetime`
- `history_logs[].datetime`
- `history_logs[].updated_at`
- `pump_installations[].installed_at`, `removed_at`, `updated_at` _(since v2.3)_
- `permits[].updated_at`, `permits[].history[].updated_at`, `permits[].conditions[].fulfillments[].datetime`, `updated_at` _(since v2.3)_
- `meters[].installed_at`, `removed_at`, `updated_at` _(since v2.3)_
- `production[].datetime` (`meter_reading`); `production[].period_start`, `period_end` (`estimated_volume`) _(since v2.3)_
- `operating_regime[].effective_from`, `updated_at` _(since v2.3)_
- `water_samples[].datetime`, `laboratory.received_at`, `results[].analyzed_at`, `results[].validation.validated_at` _(since v2.3)_
Validity and deadline checks compare calendar dates, never UTC instants. When an instant is compared with a calendar date — for example a condition fulfillment's `datetime` against its `due_date` — the instant's local date is the instant converted to `location.properties.timezone` _(since v2.4)_. Without `timezone`, it is the date part of the string, in the offset it carries; producers SHOULD then write such instants in the well site's local offset. A file without `timezone` whose permits have condition fulfillments emits a warning.
Relative deadlines and recurrences are ISO 8601 date durations (`P90D`, `P6M`, `P1Y6M`); see object-schemas.md § Date duration.
### Instants known only by date _(since v2.3)_
Laboratory reports often give only the date of receipt or analysis. Such an instant is written at 00:00 local time at the well site, and a sibling field named after it with the `_resolution` suffix is set to `"day"`:
```json
{ "analyzed_at": "2026-09-17T00:00:00-03:00", "analyzed_at_resolution": "day" }
```
- Consumers MUST ignore the time of day when `_resolution` is `day`. Ordering, holding-time and deadline calculations on that field use day resolution.
- `day` is the only allowed value; any other value is malformed. An absent `_resolution` means the instant is exact.
- In v2.3 the convention applies to `water_samples[].laboratory.received_at` and `water_samples[].results[].analyzed_at`. It is the candidate general convention for any instant known only by its date.
A datetime without an offset (naked `YYYY-MM-DDTHH:MM:SS`) is malformed and parsers MUST reject it. Time zone abbreviations (`EST`, `BRT`) are not RFC 3339 and MUST NOT be used.
### Ordering and ties
Instants are ordered by absolute UTC instant, not by the lexical form of the string. The strings `2006-03-14T08:00:00-03:00` and `2006-03-14T11:00:00Z` represent the same instant and tie for ordering purposes.
When two events share the same instant, the optional `sequence` integer breaks the tie (lower values sort first). When two events share the same instant and no `sequence` is provided, ordering is implementation-defined; producers SHOULD provide `sequence` when authoring events with identical instants.
---
## Recommended vocabularies
Many string fields are **open**: they accept any value, but recommend a vocabulary for interoperability. The sections below list each one. Non-canonical values SHOULD use the `x-` prefix, and tools MUST preserve unrecognized values and display them as-is.
Every recommended vocabulary is also published as data by `@welldot/core` (e.g. `PERMIT_TYPES`, `CONSTRUCTION_MATERIALS`, `SAMPLE_TYPES`), each entry carrying its `value` and display labels keyed by BCP 47 language tag (at least `en` and `pt`). The field types use the `OpenVocab` pattern (`T | (string & {})`): the recommended values are typed while any other string stays valid. The Portuguese column in the tables below may be more descriptive than the short display label.
## `well_type` — Recommended values
The `well_type` field accepts any string and describes the well's **construction method**. The values below are the recommended vocabulary for interoperability. Tools should treat unrecognized values gracefully.
| Value | Portuguese (BR) | Description |
| ---------------------- | ----------------------- | ---------------------------------------------- |
| `tubular` | Tubular | Conventional rotary or percussion drilled well |
| `hand_dug` | Cacimba / poço escavado | Manually excavated well or cistern |
| `horizontal` | Horizontal | Horizontal or sub-horizontal well |
| `infiltration_gallery` | Galeria de infiltração | Horizontal subsurface infiltration gallery |
| `artesian` | Artesiano | **Deprecated in v2.1** — see below |
Non-canonical values SHOULD use the `x-` prefix (e.g. `x-radial_collector`).
**`artesian` (deprecated in v2.1, removal in v3).** Artesianism is a hydraulic condition, not a construction method: an artesian well is usually a `tubular` well. A flowing artesian condition is recorded as a `hydrodynamic_events` entry whose `static_level` is negative (see § Level sign convention), and applications derive the label from it. Parsers MUST continue to accept `well_type: "artesian"` and MUST NOT rewrite it. Authoring tools SHOULD offer to migrate it to the actual construction method on edit.
---
## `well_purpose` — Recommended values _(since v2.1)_
`well_purpose` is an optional array of strings giving the well's current intended use(s). A well may have more than one purpose (e.g. `["production", "monitoring"]`). Changes of use over time belong in `history_logs` (category `event`). Non-canonical values SHOULD use the `x-` prefix.
| Value | Portuguese (BR) | Description |
| ----------------------- | ------------------------------- | ---------------------------------------------------------------- |
| `production` | Produção / captação | Water supply abstraction |
| `monitoring` | Poço de monitoramento | Groundwater level and/or quality monitoring |
| `piezometer` | Piezômetro | Measures hydraulic head at a discrete depth interval |
| `water_level_indicator` | INA — indicador de nível d'água | Open standpipe for water table readings (typically geotechnical) |
| `observation` | Poço de observação | Observation well for aquifer tests on a nearby pumping well |
| `exploration` | Pesquisa / exploratório | Investigation or test well |
| `injection` | Injeção / recarga | Managed aquifer recharge or injection |
| `dewatering` | Rebaixamento | Construction or mining dewatering |
---
## `bore_hole[].drilling_method` — Recommended values _(since v2.3)_
| Value | English | Portuguese (BR) |
| ------------ | ---------- | ------------------ |
| `rotary` | Rotary | Rotativo |
| `percussion` | Percussion | Percussão |
| `cable_tool` | Cable tool | Percussão a cabo |
| `auger` | Auger | Trado |
| `air_hammer` | Air hammer | Martelo pneumático |
Non-canonical values SHOULD use the `x-` prefix.
## Construction materials — Recommended values _(since v2.3)_
Shared by `well_case[].type`, `well_screen[].type`, `reduction[].type` and `pump_installations[].riser_material`.
| Value | English | Portuguese (BR) |
| ------------------- | ----------------- | --------------- |
| `pvc` | PVC | PVC |
| `geomechanical_pvc` | Geomechanical PVC | PVC geomecânico |
| `carbon_steel` | Carbon steel | Aço carbono |
| `galvanized_steel` | Galvanized steel | Aço galvanizado |
| `stainless_steel` | Stainless steel | Aço inox |
| `fiberglass` | Fiberglass | Fibra de vidro |
Non-canonical values SHOULD use the `x-` prefix.
## `well_screen[].slot_type` — Recommended values _(since v2.3)_
The screen design, independent of its material (`well_screen[].type`).
| Value | English | Portuguese (BR) |
| ----------------- | --------------- | ------------------------- |
| `continuous_slot` | Continuous slot | Espiralado (tipo Johnson) |
| `slotted` | Slotted | Ranhurado |
| `bridge_slot` | Bridge slot | Tipo ponte |
| `louver` | Louver | Veneziana |
| `perforated` | Perforated | Perfurado |
| `nold` | Nold type | Tipo Nold |
`continuous_slot` is the wire-wrapped (wire-wound) screen. `nold` is the steel screen sold under that trade designation (_filtro tipo Nold_). Non-canonical values SHOULD use the `x-` prefix.
## `centralizers[].type` — Recommended values _(since v2.1)_
| Value | English | Portuguese (BR) |
| ------------------ | ---------------- | ----------------- |
| `spring_bow` | Spring bow | Mola (spring bow) |
| `rigid` | Rigid | Rígido |
| `semi_rigid` | Semi-rigid | Semirrígido |
| `polymer` | Polymer | Polimérico |
| `carbon_steel` | Carbon steel | Aço carbono |
| `galvanized_steel` | Galvanized steel | Aço galvanizado |
| `stainless_steel` | Stainless steel | Aço inox |
The steel values name rigid centralizers by their material. Non-canonical values SHOULD use the `x-` prefix.
## `cement_pad.type` — Recommended values _(since v2.3)_
| Value | English | Portuguese (BR) |
| --------------------- | ------------------- | --------------- |
| `concrete` | Concrete | Concreto |
| `reinforced_concrete` | Reinforced concrete | Concreto armado |
| `cement` | Cement | Cimento |
| `mortar` | Mortar | Argamassa |
`type` describes the pad material; other free text (e.g. a shape) remains valid.
---
## `pump_installations[].type` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) | Description |
| -------------------- | ---------------------- | ------------------------------------------------- |
| `submersible` | Bomba submersa | Electric submersible pump. |
| `vertical_turbine` | Bomba de eixo vertical | Surface motor with line shaft. |
| `jet` | Bomba injetora | Surface jet pump. |
| `progressive_cavity` | Bomba helicoidal | Progressive cavity pump, common with solar power. |
| `hand_pump` | Bomba manual | |
| `compressor_airlift` | Compressor (air-lift) | Permanent airlift production. |
Solar is a `power_source`, never a `type`. Non-canonical values SHOULD use the `x-` prefix.
## `pump_installations[].power_source` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) |
| -------- | -------------------- |
| `grid` | Rede elétrica |
| `solar` | Solar (fotovoltaico) |
| `diesel` | Gerador a diesel |
| `hybrid` | Híbrido |
## `permits[].type` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) | Description |
| -------------------- | -------------------------------------- | ----------------------------------- |
| `abstraction_permit` | Outorga de direito de uso | Grant to abstract groundwater. |
| `preliminary_permit` | Outorga prévia / declaração de reserva | Permission before drilling. |
| `exemption` | Dispensa / uso insignificante | Formal exemption for small users. |
| `registration` | Cadastro (e.g. CNARH) | Registration without a grant. |
| `dewatering_permit` | Outorga para rebaixamento | Mining and construction dewatering. |
| `drilling_permit` | Autorização de perfuração | Authorization to drill the well. |
`renewal` is not a type: a renewal is a new permit of the same type whose `supersedes` points to the previous one. Non-canonical values SHOULD use the `x-` prefix.
## `permits[].water_use` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) |
| -------------- | -------------------------------------- |
| `human_supply` | Abastecimento público / consumo humano |
| `industrial` | Industrial |
| `mining` | Mineração |
| `irrigation` | Irrigação |
| `livestock` | Dessedentação animal |
| `commercial` | Comercial / serviços |
`water_use` describes what the abstracted water is for; `well_purpose` describes the well's role.
## `permits[].conditions[].category` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) | Covers |
| ------------------------ | -------------------------- | -------------------------------------------------------------------------- |
| `monitoring` | Monitoramento | Water level, flow, abstracted volume and water quality monitoring/sampling |
| `reporting` | Relatórios e declarações | Periodic reports and volume/flow declarations submitted to the authority |
| `equipment_installation` | Instalação de equipamentos | Meters (hidrômetro), level gauges, sampling taps, telemetry |
| `well_protection` | Proteção do poço | Sanitary slab, seal, protection perimeter, wellhead closure |
| `environmental` | Ambiental | Environmental measures, compensation, licensing of the activity |
| `legal` | Legal / administrativo | Renewal request, fees, registrations, document submissions |
Non-canonical values SHOULD use the `x-` prefix.
## `permits[].history[].type` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) |
| -------------- | -------------------------------- |
| `filing` | Protocolo / requerimento |
| `process` | Andamento do processo / despacho |
| `notification` | Notificação / ofício |
| `fee` | Taxa / emolumento |
| `inspection` | Vistoria |
| `decision` | Decisão / publicação |
| `renewal` | Renovação |
Non-canonical values SHOULD use the `x-` prefix. The administrative `status` of a permit is a closed vocabulary; see object-schemas.md § `permits[]` — Administrative status.
## `meters[].type` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) |
| ----------------- | ----------------------- |
| `mechanical` | Hidrômetro mecânico |
| `electromagnetic` | Medidor eletromagnético |
| `ultrasonic` | Medidor ultrassônico |
Non-canonical values SHOULD use the `x-` prefix.
## `production[]` — Recommended values _(since v2.3)_
**`type`** — the entry discriminator: `meter_reading` (a totalizer register value) or `estimated_volume` (a volume estimated for a period, without a meter). Non-canonical entry types SHOULD use the `x-` prefix; parsers preserve them and exclude them from volume derivations.
**`source`** (`meter_reading`):
| Value | Portuguese (BR) |
| ----------- | --------------- |
| `manual` | Leitura manual |
| `telemetry` | Telemetria |
**`method`** (`estimated_volume`) — the basis of the estimate. Descriptive only: it never changes how the volume is counted.
| Value | Portuguese (BR) | Description |
| ---------------- | ------------------------- | ------------------------------------------------------------------ |
| `flow_time` | Vazão × tempo de operação | Flow rate times operating time (hour meter, operating regime). |
| `energy` | Consumo de energia | Derived from the pump's energy consumption. |
| `user_statement` | Informado pelo usuário | Stated by the owner or operator, without a documented calculation. |
## `water_samples[]` — Recommended values _(since v2.3)_
Recommended values for `sample_type`, `sampling_method`, `sampling_point.type` and `sampling_point.device` are listed in [water-quality.md](water-quality.md) § Recommended values.
## `hydrodynamic_events[].measurement_method` — Recommended values
Used by `spot_measurement` and `logger_series` _(since v2.4)_ events.
| Value | English | Portuguese (BR) |
| --------------------- | ------------------- | --------------------- |
| `electric_probe` | Electric probe | Sonda elétrica |
| `pressure_transducer` | Pressure transducer | Transdutor de pressão |
| `air_line` | Air line | Linha de ar |
| `tape` | Tape | Trena |
Non-canonical values SHOULD use the `x-` prefix.
## `aquifer_analysis[].method` — Recommended values
| Value | English | Portuguese (BR) |
| ------------------- | ----------------- | --------------- |
| `cooper_jacob` | Cooper-Jacob | Cooper-Jacob |
| `theis` | Theis | Theis |
| `neuman` | Neuman | Neuman |
| `hantush` | Hantush | Hantush |
| `birsoy_summers` | Birsoy-Summers | Birsoy-Summers |
| `eden_hazel` | Eden-Hazel | Eden-Hazel |
| `visual_inspection` | Visual inspection | Inspeção visual |
Non-canonical values SHOULD use the `x-` prefix.
## `history_logs[].category` — Recommended values
| Value | Portuguese (BR) | Category-specific fields |
| --------------- | ------------------- | --------------------------------------------------------------------- |
| `maintenance` | Manutenção | `maintenance_type`, `pump_installation_id`, `meter_id` _(since v2.3)_ |
| `inspection` | Inspeção | — |
| `incident` | Incidente | — |
| `event` | Evento | — |
| `status_change` | Mudança de situação | `status` _(since v2.3)_ |
Descriptions are in object-schemas.md § `history_logs[]`. Category-specific fields MUST be absent on entries of other categories; their presence emits a warning. Non-canonical values SHOULD use the `x-` prefix.
## `history_logs[].severity` — Recommended values
| Value | English | Portuguese (BR) |
| ---------- | -------- | --------------- |
| `low` | Low | Baixa |
| `medium` | Medium | Média |
| `high` | High | Alta |
| `critical` | Critical | Crítica |
Non-canonical values SHOULD use the `x-` prefix.
## `history_logs[].maintenance_type` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) |
| ------------------- | --------------------------- |
| `inspection` | Inspeção |
| `cleaning` | Limpeza |
| `redevelopment` | Redesenvolvimento |
| `disinfection` | Desinfecção |
| `pump_service` | Manutenção da bomba |
| `meter_calibration` | Aferição do hidrômetro |
| `video_inspection` | Filmagem / perfilagem ótica |
| `level_measurement` | Medição de nível |
| `pump_test` | Teste de bombeamento |
| `water_sampling` | Coleta de água |
Non-canonical values SHOULD use the `x-` prefix.
## `history_logs[].status` — Allowed values _(since v2.3)_
Used by entries of category `status_change`. This is a **closed vocabulary**: `status` MUST be one of the five values below. Any other value, including an `x-` value, is a schema error and the file is rejected.
| Value | Portuguese (BR) | Semantics |
| ---------------- | ---------------------- | --------------------------------------- |
| `active` | Ativo / em operação | Normal operation. |
| `maintenance` | Em manutenção | Temporarily out of service for work. |
| `inactive` | Paralisado | Out of service, recoverable. |
| `decommissioned` | Desativado / tamponado | Permanently closed and properly sealed. |
| `abandoned` | Abandonado | Left without proper sealing. |
The well's current status is the `status` of the `status_change` entry with the latest `datetime`; with no such entry, the status is unknown.
## `Attachment.document_type` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) | Typical location |
| -------------------- | ------------------------------------------- | ------------------------------------- |
| `drilling_report` | Relatório de perfuração / relatório técnico | Root |
| `as_built_drawing` | Perfil construtivo as-built | Root |
| `registry_record` | Ficha cadastral (SIAGAS, CNARH) | Root |
| `photo` | Fotografia | Any |
| `permit_document` | Portaria / certificado de outorga | `permits` |
| `condition_evidence` | Comprovante de cumprimento de condicionante | `permits[].conditions[].fulfillments` |
| `pump_curve` | Curva da bomba | `pump_installations` |
| `field_sheet` | Planilha de campo | `hydrodynamic_events` |
| `test_report` | Relatório de teste de bombeamento | `aquifer_analysis` |
| `lab_report` | Laudo laboratorial | `water_samples` |
| `invoice` | Nota fiscal | Any |
The vocabulary is open; non-canonical values SHOULD use the `x-` prefix.
---
## `well_id`
Array of authority-scoped identifiers linking the well to external registries. A well may be registered in multiple national, regional, or institutional registries simultaneously.
| Field | Type | Required | Description |
| ----------- | ------- | -------- | --------------------------------------------------------------------------------- |
| `authority` | string | yes | Name of the issuing registry or institution (e.g. `"SIAGAS"`, `"ANA"`, `"NGWD"`). |
| `id` | string | yes | The identifier as issued by the authority. |
| `primary` | boolean | no | Marks the canonical identifier. At most one entry may have `primary: true`. |
Producers SHOULD mark exactly one entry `primary: true`. The rule "the first entry is canonical" applies only as a fallback when no entry is marked, because it is fragile when the order changes. Parsers MUST warn when multiple entries have `primary: true`.
### Numeric ID conversion
All `id` values are strings. When the source identifier from an external system is an integer, the string representation MUST be the canonical base-10 representation with no leading zeros, no thousands separator, and no locale-specific formatting. The integer `42` becomes `"42"`, not `"042"` or `"42,"`.
When the source identifier is a fixed-width string with significant leading zeros (e.g. `"00042819"` from a registry where width carries meaning), the original string MUST be preserved verbatim. Composite identifiers containing significant leading zeros in their numeric portion (e.g. SIAGAS `"SP-0042819"`) MUST be preserved as-is.
### Uniqueness
The combination `(authority, id)` MUST be unique within the `well_id` array. See § Cross-reference and uniqueness rules for the duplicate-handling policy.
```json
"well_id": [
{ "authority": "SIAGAS", "id": "SP-0042819", "primary": true },
{ "authority": "ANA", "id": "02000.001234/2006-78" },
{ "authority": "utility-internal", "id": "PP-01" }
]
```
---
## `location`
Geographic position and elevation of the wellhead. Supersedes the v1 top-level `lat`, `lng`, and `elevation` fields.
The three primary measurements — `lat`, `lng`, `elevation` — are top-level fields on the location object for ergonomics. All ancillary metadata (datum, CRS, precision, source CRS) lives under a nested `properties` object.
| Field | Type | Required | Description |
| ------------ | -------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------- |
| `lat` | number | yes | Latitude in decimal degrees. |
| `lng` | number | yes | Longitude in decimal degrees. |
| `elevation` | number | no | Wellhead elevation in meters above the WGS84 reference ellipsoid (or the datum named in `properties.elevation_datum`). |
| `properties` | `LocationProperties` | no | Ancillary metadata. When absent, all defaults apply. |
### `LocationProperties`
| Field | Type | Required | Description |
| --------------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `elevation_datum` | string | no | Reference datum for `elevation`. See vocabulary below. Default: `wgs84_ellipsoid`. |
| `crs` | string | no | EPSG code of the coordinate reference system. Must be a geographic (not projected) CRS. Default: `"EPSG:4326"` (WGS84). |
| `lat_precision` | number | no | One-sigma precision of `lat` in decimal degrees. |
| `lng_precision` | number | no | One-sigma precision of `lng` in decimal degrees. |
| `elevation_precision` | number | no | One-sigma precision of `elevation` in meters. |
| `original_crs` | string | no | EPSG code of the source CRS, preserved for traceability when source data was in a projected CRS that the converter transformed to geographic. Informational only. |
| `timezone` | string | no | IANA time zone of the well site (e.g. `America/Belem`). A value that is not a valid IANA name is malformed. _(since v2.4)_ |
### `elevation_datum` — Recommended values
| Value | Description |
| ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `wgs84_ellipsoid` | WGS84 reference ellipsoidal height. Default. |
| `egm2008` | Earth Gravitational Model 2008 geoid |
| `egm96` | Earth Gravitational Model 1996 geoid |
| `local_msl` | Local mean sea level (imprecise; specific datum SHOULD be named via `x-` prefix, e.g. `x-imbituba` for Brazil's national vertical reference) |
When `elevation_datum` is omitted, parsers MUST assume `wgs84_ellipsoid`.
### CRS scope
Only geographic coordinate reference systems are permitted in `properties.crs`. Projected CRS values (e.g. UTM zones) are not valid. If source data uses a projected CRS, the converting application MUST transform to geographic coordinates before writing the file. The `original_crs` field MAY be populated with the source CRS for traceability.
```json
"location": {
"lat": -1.4558,
"lng": -48.5039,
"elevation": 12.5,
"properties": {
"elevation_datum": "wgs84_ellipsoid",
"crs": "EPSG:4326",
"lat_precision": 0.0001,
"lng_precision": 0.0001,
"elevation_precision": 0.5,
"original_crs": "EPSG:31983"
}
}
```
---
## Units
All numeric values in a `.well` file are in **SI**. The format does not encode unit declarations; the units below are the single, canonical interpretation for every field of the corresponding kind. No file may override these.
### Canonical units
| Quantity | Unit | UCUM | Stored as |
| ------------------------ | -------------------------------------------------- | ---------------------------------------------- | ---------------------------------------- |
| Depth, length, elevation | meter | `m` | number |
| Diameter | millimeter | `mm` | number |
| Volumetric flow rate | cubic meter per hour | `m3/h` | number |
| Elapsed time, duration | minute | `min` | number |
| Daily operating time | hour | `h` | number (above 24 read as 24) |
| Volume | cubic meter | `m3` | number |
| Power | kilowatt | `kW` | number |
| Voltage | volt | `V` | number |
| Cross-sectional area | square millimeter | `mm2` | number |
| Mass concentration | milligram per liter | `mg/L` | number _(since v2.3)_ |
| Electrical conductivity | microsiemens per centimeter at 25 °C | `uS/cm` | number _(since v2.3)_ |
| Temperature | degree Celsius | `Cel` | number _(since v2.3)_ |
| Redox potential | millivolt | `mV` | number _(since v2.3)_ |
| Turbidity | NTU, FNU, FAU, FTU (by parameter code) | — | number _(since v2.3)_ |
| Color | Hazen unit (uH = mg Pt-Co/L) | — | number _(since v2.3)_ |
| Microbiological count | MPN/100 mL, CFU/100 mL, CFU/mL (by parameter code) | `{MPN}/(100.mL)`, `{CFU}/(100.mL)`, `{CFU}/mL` | number _(since v2.3)_ |
| Activity concentration | becquerel per liter | `Bq/L` | number _(since v2.3)_ |
| Filter pore size | micrometer | `um` | number _(since v2.3)_ |
| Transmissivity | square meter per second | `m2/s` | number |
| Hydraulic conductivity | meter per second | `m/s` | number |
| Pressure | kilopascal | `kPa` | number |
| Angle (azimuth, dip) | degree | `deg` | number (0–360 for azimuth, 0–90 for dip) |
| Geographic coordinates | decimal degree (WGS84) | `deg` | number |
| Calendar date | ISO 8601 | — | string `YYYY-MM-DD` |
| Instant | RFC 3339 with UTC offset | — | string |
UCUM is the **Unified Code for Units of Measure**, the international machine-parseable unit standard used by OGC SWE and the scientific community. The UCUM column above documents the canonical encoding of each unit for use in JSON-LD contexts, profile schemas, and downstream interoperability — it is not encoded in `.well` files themselves.
### Field-to-unit binding (normative)
**Meter (`m`)** — all depths, lengths, and elevations:
- `well_depth`
- `bore_hole[].from`, `bore_hole[].to`
- `well_case[].from`, `well_case[].to`
- `reduction[].from`, `reduction[].to`
- `well_screen[].from`, `well_screen[].to`
- `surface_case[].from`, `surface_case[].to`
- `hole_fill[].from`, `hole_fill[].to`
- `centralizers[].from`, `centralizers[].to`, `centralizers[].spacing`
- `cement_pad.width`, `cement_pad.length`, `cement_pad.thickness`
- `lithology[].from`, `lithology[].to`
- `fractures[].depth`, `fractures[].depth_precision`
- `caves[].from`, `caves[].to`
- `hydrodynamic_events[].static_level`, `static_level_precision`
- `PumpingStep.readings[].depth`, `depth_precision`
- `RecoveryPhase.readings[].depth`, `depth_precision`
- `logger_series.sensor_depth`, `readings[].depth`, `depth_precision`, `depth_min`, `depth_max` _(since v2.4)_
- `aquifer_analysis[].static_level`, `static_level_precision`
- `aquifer_analysis[].dynamic_level`, `dynamic_level_precision`
- `aquifer_analysis[].aquifer_thickness`
- `location.elevation`, `location.properties.elevation_precision`
- `pump_installations[].intake_depth`, `rated_head`, `electrical.cable_length` _(since v2.3)_
- `water_samples[].sampling_point.depth`, `depth_precision`, `from`, `to` _(since v2.3)_
**Millimeter (`mm`)** — all diameters and slot openings:
- `bore_hole[].diameter`
- `well_case[].diameter`
- `reduction[].diam_from`, `reduction[].diam_to`
- `well_screen[].diameter`, `well_screen[].screen_slot`
- `surface_case[].diameter`
- `hole_fill[].diameter`
- `centralizers[].diameter`
- `pump_installations[].riser_diameter` _(since v2.3)_
- `meters[].nominal_diameter` _(since v2.3)_
**Cubic meter per hour (`m3/h`)** — all volumetric flow rates:
- `PumpingStep.rate`, `rate_precision`
- `aquifer_analysis[].flow_rate`, `flow_rate_precision`
- `recovery_only.pumping_rate`
- `pump_installations[].rated_flow_rate` _(since v2.3)_
- `permits[].flow_rate`, `permits[].monthly_schedule[].flow_rate` _(since v2.3)_
- `operating_regime[].flow_rate` _(since v2.3)_
- `water_samples[].purge.flow_rate` _(since v2.3)_
**Minute (`min`)** — all elapsed times and durations:
- `PumpingStep.duration`
- `LevelReading.elapsed`
- `recovery_only.pumping_duration`
- `logger_series.interval` _(since v2.4)_
- `water_samples[].purge.duration`, `purge.readings[].elapsed` _(since v2.3)_
**Cubic meter (`m3`)** — volume _(since v2.3)_:
- `permits[].volume_limits[].volume`
- `meters[].max_reading`
- `production[].reading` (`meter_reading`), `production[].volume` (`estimated_volume`)
- `water_samples[].purge.volume`
**Hour (`h`)** — daily operating time _(since v2.3)_:
- `permits[].daily_operating_time`, `permits[].monthly_schedule[].daily_operating_time`
- `operating_regime[].daily_operating_time`
The minute remains the canonical unit for elapsed time and durations. The hour is bound only to `daily_operating_time`, which regulators always express in hours.
**Square meter per second (`m2/s`)** — transmissivity:
- `aquifer_analysis[].transmissivity`
**Meter per second (`m/s`)** — hydraulic conductivity:
- `aquifer_analysis[].hydraulic_conductivity`
**Kilopascal (`kPa`)** — pressure:
- `LevelReading.pressure`
**Kilowatt (`kW`)** — power _(since v2.3)_:
- `pump_installations[].rated_power`
**Volt (`V`)** — voltage _(since v2.3)_:
- `pump_installations[].electrical.voltage`
**Square millimeter (`mm2`)** — cross-sectional area _(since v2.3)_:
- `pump_installations[].electrical.cable_section`
**Degree Celsius (`Cel`)** — temperature _(since v2.3)_:
- `water_samples[].laboratory.received_temperature`
**Micrometer (`um`)** — filter pore size _(since v2.3)_:
- `water_samples[].results[].filtration.pore_size`
**Unit by parameter code** _(since v2.3)_ — `water_samples[].results[].value`, `detection_limit`, `quantification_limit`, `value_precision` and `purge.readings[].value` are in the canonical unit of the result's parameter (see water-quality.md § Units).
**Degree (`deg`)** — angles and geographic coordinates:
- `fractures[].azimuth`, `fractures[].dip`
- `location.lat`, `location.lng`, `location.properties.lat_precision`, `location.properties.lng_precision`
**Unitless** — dimensionless or domain-specific values:
- `aquifer_analysis[].storativity` (dimensionless)
- `aquifer_analysis[].jacob_b`, `jacob_c` (composite units; treated as the units required to make the standard Jacob equation balance with stored `flow_rate` and `length`)
- `aquifer_analysis[].well_efficiency_pct` (percentage, 0–100)
- `aquifer_analysis[].specific_capacity` (derived ratio in m³/h per m, i.e. `m2/h`)
- Lithology `color` (CSS hex string)
- `pump_installations[].stages`, `electrical.phases` (counts)
- `permits[].monthly_schedule[].month`, `days`; `permits[].conditions[].occurrences` (counts)
- `operating_regime[].days_per_week` (count, 1–7)
### Water quality units _(since v2.3)_
Water quality results take their unit from the parameter code; only `x-` vocabulary codes declare `unit` (UCUM). The unit-by-code rule and regional unit normalization are specified in [water-quality.md](water-quality.md) § Units.
### Level sign convention
Depths are measured from ground level (0) and increase downward. Water levels above ground level are therefore **negative**. This applies to `static_level`, `LevelReading.depth`, and `aquifer_analysis[].static_level` / `dynamic_level`. A negative static level is how a flowing artesian condition is recorded (see § `well_type`). This convention was implicit in v2.0 and is made explicit in v2.1 without changing semantics. It does not apply to `pump_installations[].intake_depth`, which is an equipment depth and is always positive.
### Diameter and the as-built convention
Diameters in casing, screens, surface case, and hole fill are stored as the **as-built outer diameter in millimeters**, not as nominal manufacturer designations. A casing sold as "6 inch" with a real outer diameter of 168.3 mm is recorded as `168.3`, not `152.4` (the literal conversion of 6 inches to mm). Nominal-to-actual mapping for common pipe and screen sizes is a market convention handled by applications — typically by the converter or input form — not by the file format.
### Screen slot
`well_screen[].screen_slot` is in millimeters like every other diameter field. US-market slot-number designations (e.g. "20 slot" = 0.020 inch = 0.508 mm) are an input convenience handled by applications, not encoded in the file. The v1 field name `screen_slot_mm` was a legacy artifact and is renamed to `screen_slot` in v2; the value is in millimeters before and after.
### Rationale
An interchange format with configurable units multiplies the number of valid encodings of the same well, complicates equality and comparison, and introduces a class of bugs where the declared units do not match the stored values. The `.well` format is designed to be a canonical machine representation: one well, one encoding. Display units, input units, and locale-specific notation are concerns of the applications that read and write `.well` files, not of the format itself. This separation matches the design of GeoJSON (WGS84 only), RFC 3339 (UTC offsets always present), and most modern interchange formats.
Producers receiving non-SI source data (imperial drilling reports, US gpm flow measurements, inch casing designations, slot-number screens) MUST convert to SI before writing. Consumers presenting the data to users in non-SI units MUST convert on read.
Conversion utilities for the common transformations are provided in `@welldot/core` under the `units` module — m ↔ ft, mm ↔ in, m³/h ↔ L/s, m³/h ↔ US gpm, m²/s ↔ m²/d, DD ↔ DMS, slot-number ↔ mm, cv/hp ↔ kW, L / ft³ / US gal ↔ m³, µg/L ↔ mg/L, mS/m ↔ µS/cm, and similar.
---
## Water quality parameter vocabulary _(since v2.3)_
The `welldot` parameter vocabulary (98 codes), its rules, the CAS equivalences and the treatment of limit sets are specified in [water-quality.md](water-quality.md) § Parameter vocabulary.
---
## `profiles`
An optional array of HTTPS URLs declaring formal extensions to the spec that the file conforms to. Each URL MUST resolve to a JSON Schema document.
```json
"profiles": [
"https://welldot.org/profiles/brazil-ana/v1/schema.json",
"https://inspire.ec.europa.eu/schemas/gwhyd/4.0/profile.json"
]
```
### Profile schema requirements
- Each URL MUST be an HTTPS URL resolving to a JSON Schema document (`Content-Type: application/schema+json` or `application/json`).
- The schema SHOULD extend the canonical `.well` v2 schema via `allOf` + `$ref`, but this is not required.
- The schema MAY link to human documentation via its `description` field or a custom `x-documentation` field pointing to a Markdown or PDF URL.
- Profiles MAY impose additional required fields, constrain vocabulary values, or mandate the use of specific `x-` extensions for a regulatory context.
### Example: a regulatory water quality profile _(since v2.3)_
See [water-quality.md](water-quality.md) § Regulatory profiles.
### Profile validation
- If a profile URL is unreachable, returns a non-JSON document, or fails to parse as JSON Schema, the profile is silently ignored. Parsers MUST NOT reject files whose profile URLs are unavailable.
- A profile-aware parser SHOULD validate the file against each reachable profile schema and surface failures as **warnings**, not errors. Conformance is asserted by the author, not enforced by the spec.
- Profile URLs SHOULD be versioned via path segments or schema-internal `$id` to ensure stability.
---
## Extensibility
### Custom fields — `x-` prefix
Any implementer may add custom fields to any object by prefixing the field name with `x-`. This convention is established by OpenAPI 3.x and JSON Schema. Parsers MUST preserve and ignore `x-` fields they do not recognize. Sub-properties within an `x-` object do not need to be prefixed. For application extensions, `x--` is RECOMMENDED (e.g. `x-welldot-custom`), so tools that both use `x-custom` do not collide. _(since v2.4)_
```json
{
"id": "b2c3d4e5-...",
"type": "constant_rate",
"datetime": "2006-03-14T08:00:00-03:00",
"x-siagas": {
"registro": "SP-0042819",
"processo_ana": "02000.001234/2006-78"
}
}
```
Fields without the `x-` prefix that are not defined in this spec are technically non-conformant. Parsers MUST NOT reject such files but SHOULD emit a warning.
### Custom top-level blocks — `x-` prefix required
Top-level blocks not defined in this spec MUST use the `x-` prefix. For application extensions, `x--` is RECOMMENDED (e.g. `x-welldot-custom`), so tools that both use `x-custom` do not collide. _(since v2.4)_ Parsers MUST preserve and ignore unrecognized top-level keys.
```json
{
"version": "2.4",
"x-gas_monitoring": [ ... ],
"x-geophysical_logs": [ ... ]
}
```
### Custom vocabulary values — `x-` prefix
Open vocabulary fields in this spec (`type` in `hydrodynamic_events`, `category` in `history_logs`, `vocabulary` in `texture` and in water quality `parameter` (as `x-…` vocabularies), `method` in `aquifer_analysis`, `well_type`, and any other field documented as accepting any string; not closed vocabularies such as `history_logs[].status`) SHOULD use the `x-` prefix when the value is non-canonical — i.e. not listed in the recommended vocabulary of this spec.
Parsers MUST treat `x-` prefixed vocabulary values as valid. Promotion from `x-` form to canonical form follows the Deprecation Policy (see § Deprecation Policy — Vocabulary value promotion).
```json
{ "type": "x-slug_test" }
{ "category": "x-site_visit" }
{ "method": "x-theis_recovery" }
```
---
## Offline packaging _(since v2.4)_
A `.well` file references its attachments by absolute URL, and HTTPS links may expire. For regulators and long-term archiving, a `.well` file MAY be delivered as an offline package: a ZIP archive with the extension `.wellz`.
- The `.well` file sits at the archive root, and the attachments sit at relative paths inside the archive.
- In this mode an `Attachment.uri` MAY be a relative path to the file inside the archive, and `sha256` is required.
- Outside a package, `uri` stays an absolute URL. A relative `uri` in a bare `.well` file is not valid.
The package is a delivery wrapper, not a second format: the `.well` file inside it is an ordinary `.well` file. Validators and parsers read the `.well` alone, so the schema keeps `uri` as a URL; resolving relative paths and checking `sha256` is the job of the tool that opens the package.
---
## Cross-reference and Uniqueness Rules
The `.well` format uses string IDs to link records across arrays. This section is the single normative source for ID semantics.
### Uniqueness scope
Each ID-bearing array maintains its **own uniqueness scope**. The ID namespaces are independent:
- `hydrodynamic_events[].id` — unique within `hydrodynamic_events`
- `aquifer_analysis[].id` — unique within `aquifer_analysis`
- `history_logs[].id` — unique within `history_logs`
- `pump_installations[].id` — unique within `pump_installations` _(since v2.3)_
- `permits[].id` — unique within `permits` _(since v2.3)_
- `permits[].conditions[].id` — unique within the `conditions` array of one permit _(since v2.3)_
- `permits[].conditions[].fulfillments[].id` — unique within the `fulfillments` array of one condition _(since v2.3)_
- `permits[].history[].id` — unique within the `history` array of one permit _(since v2.3)_
- `permits[].monthly_schedule[].month` and `permits[].volume_limits[].period` — unique within one permit _(since v2.3)_
- `meters[].id` — unique within `meters` _(since v2.3)_
- `production[].id` — unique within `production` _(since v2.3)_
- `operating_regime[].id` — unique within `operating_regime` _(since v2.3)_. `operating_regime[].effective_from` is not unique: duplicates are a warning _(since v2.4)_
- `water_samples[].id` — unique within `water_samples` _(since v2.3)_
- `attachments[].id` — unique within its owning array: the root `attachments`, or the `attachments` array of one record (`history_logs`, `permits`, `permits[].history`, `permits[].conditions[].fulfillments`, `pump_installations`, `meters`, `hydrodynamic_events`, `aquifer_analysis`, `water_samples`). The same id may appear under different records.
- `well_id[]` — uniqueness is on the composite key `(authority, id)`, not on `id` alone
An ID value MAY repeat across different arrays without conflict (e.g. an event and an analysis MAY both use the ID `"001"`), though distinct values are recommended for clarity.
### Cross-array references
The following fields hold references to IDs in other arrays:
| Reference field | Points to | Resolution scope |
| ----------------------------------------------------- | -------------------------- | ---------------- |
| `aquifer_analysis[].source_event_ids[]` | `hydrodynamic_events[].id` | Same file only |
| `aquifer_analysis[].static_level_source_id` | `hydrodynamic_events[].id` | Same file only |
| `hydrodynamic_events[].corrects` | `hydrodynamic_events[].id` | Same file only |
| `production[].corrects` | `production[].id` | Same file only |
| `production[].meter_id` | `meters[].id` | Same file only |
| `permits[].supersedes` | `permits[].id` | Same file only |
| `history_logs[].pump_installation_id` | `pump_installations[].id` | Same file only |
| `history_logs[].meter_id` | `meters[].id` | Same file only |
| `history_logs[].hydrodynamic_event_ids[]` | `hydrodynamic_events[].id` | Same file only |
| `history_logs[].sample_ids[]` | `water_samples[].id` | Same file only |
| `permits[].conditions[].fulfillments[].event_id` | `hydrodynamic_events[].id` | Same file only |
| `permits[].conditions[].fulfillments[].sample_id` | `water_samples[].id` | Same file only |
| `water_samples[].corrects` | `water_samples[].id` | Same file only |
| `water_samples[].parent_sample_id` | `water_samples[].id` | Same file only |
| `water_samples[].static_level_event_id` | `hydrodynamic_events[].id` | Same file only |
| `water_samples[].sampling_point.pump_installation_id` | `pump_installations[].id` | Same file only |
Root `attachments` is not a registry: records never reference root attachments by id. Nor is it an aggregate: it holds only general files about the well as a whole (e.g. the drilling report), never copies of the attachments of `history_logs`, `permits` (including their `history` entries and condition `fulfillments`), `pump_installations`, `meters`, `hydrodynamic_events`, `aquifer_analysis` or `water_samples` entries. A document that concerns several records is repeated on each; `sha256` guarantees both copies point to the same file.
Cross-file references are not supported in v2.
### Dangling references
A **dangling reference** is a reference field whose value does not match any ID in the target array.
- Dangling references MUST emit a warning naming the referring field, the referring object's ID, and the missing target ID.
- Dangling references MUST NOT cause file rejection. Events may be legitimately deleted from history (regulatory retention rules, GDPR-style erasure) leaving analyses that reference them stranded.
- Consumers MUST preserve unresolved references when re-exporting and MUST NOT remove them silently. Removing a reference is always a deliberate user action, never a side effect of import or export. _(since v2.4)_
### Repeated records _(since v2.4)_
Collective permits and campaign blanks apply to several wells, so they are repeated in each well's file.
- Copies MUST share the same `id` and SHOULD have identical content.
- Consumers MAY merge copies by `id`.
- A fulfillment's `event_id` or `sample_id` resolves only in the file of the well that produced it. In the other copies it is an expected unresolved reference and emits no warning.
### Duplicate IDs within a scope
A **duplicate ID** occurs when two records in the same uniqueness scope share the same ID (or, for `well_id`, the same `(authority, id)` pair).
Duplicate IDs are **malformed but recoverable**:
- Parsers MUST NOT silently accept duplicate IDs. The file is considered malformed.
- Parsers MUST NOT silently rewrite duplicate IDs. Automatic renaming would change the object graph in a way that downstream references cannot detect.
- Interactive applications MUST surface the duplication to the user with sufficient context to decide:
- The IDs in conflict
- A summary of each conflicting record (datetime, type, key fields)
- The choices: **merge** (keep one, discard the other), **rename** (assign a new ID to one of them, updating any references in the same file), or **abort load**.
- Non-interactive parsers (batch importers, server-side validators) MUST reject the file with a clear error rather than guess.
- Any user-confirmed resolution MUST be logged as a `history_logs` entry with `category: "event"` describing what was changed.
The objective is deterministic load behavior: a `.well` file MUST produce the same object graph across all conformant parsers under default settings. User-mediated repair is the only path that can deviate from this, and it must be explicit.
### `id` field convention
All `id` fields are `string` type. UUID v4 is recommended for new records. Sequential strings (e.g. `"evt-001"`) are valid for manually authored files. Numeric IDs from external systems are converted per the rule documented in § well_id (Numeric ID conversion).
---
## Precision and Uncertainty
Optional one-sigma precision qualifiers are defined for the most consequential measurement fields. Parsers that do not recognize them ignore them per the foreign-members rule.
The precision field name is the measurement field name suffixed with `_precision`. The unit of the precision value is the unit of the measurement it qualifies.
### Supported precision fields
| Measurement | Precision field | Defined on |
| --------------- | ------------------------- | ------------------------------------- |
| `lat` | `lat_precision` | `LocationProperties` |
| `lng` | `lng_precision` | `LocationProperties` |
| `elevation` | `elevation_precision` | `LocationProperties` |
| `static_level` | `static_level_precision` | events, `AquiferAnalysis` |
| `dynamic_level` | `dynamic_level_precision` | `AquiferAnalysis` |
| `depth` | `depth_precision` | `LevelReading`, `Fracture` |
| `rate` | `rate_precision` | `PumpingStep` |
| `flow_rate` | `flow_rate_precision` | `AquiferAnalysis` |
| `depth` | `depth_precision` | `SamplingPoint` _(since v2.3)_ |
| `value` | `value_precision` | water quality `Result` _(since v2.3)_ |
All precision fields are optional. When omitted, the measurement value carries no documented precision. Precision is the one-sigma standard deviation in the same unit as the measurement.
Laboratory reports usually give an expanded uncertainty U with a coverage factor k (typically k = 2). Converters store `value_precision = U / k`, never U itself.
```json
{ "depth": 44.8, "depth_precision": 0.01 }
```
---
_`.well` Format Specification v2.4_
# `.well` File Format Specification — Version 2.4: Interoperability
**See also:** [overview.md](./overview.md) · [format-reference.md](./format-reference.md) · [object-schemas.md](./object-schemas.md) · [water-quality.md](./water-quality.md)
## Overview
This part of the `.well` v2.4 specification covers how `.well` connects to other standards and tools: the reserved JSON-LD `@context` and its publication plan, how the format maps onto the OGC GroundWaterML 2 (GWML2) model, and validating files against the published JSON Schema.
## Table of contents
- [JSON-LD Context (Reserved)](#json-ld-context-reserved)
- [v2.0 parser requirements](#v20-parser-requirements)
- [Recommended usage](#recommended-usage)
- [Context publication plan](#context-publication-plan)
- [Versioning and stability](#versioning-and-stability)
- [Optional fallback](#optional-fallback)
- [Relationship to GWML2](#relationship-to-gwml2)
- [Authoritative references](#authoritative-references)
- [Scope correspondence](#scope-correspondence)
- [Conceptual alignment](#conceptual-alignment)
- [Class mapping (informative)](#class-mapping-informative)
- [Notes on the mapping](#notes-on-the-mapping)
- [Profiles for regulatory adoption](#profiles-for-regulatory-adoption)
- [Schema Validation](#schema-validation)
---
## JSON-LD Context (Reserved)
The `@context` top-level field is reserved for JSON-LD interoperability with semantic web tooling and external standards such as GWML2. See § Relationship to GWML2.
### v2.0 parser requirements
- The `@context` field is **optional**.
- v2.0 parsers MUST preserve the `@context` field on round-trip, per the foreign-members rule.
- v2.0 parsers MUST NOT reject files that include or omit the field.
- v2.0 parsers are NOT required to interpret the field as JSON-LD. No semantic processing is mandated by this version of the spec.
### Recommended usage
A `.well` file MAY include `"@context": "https://welldot.org/context/v2.jsonld"` to assert that the file's field names map to the canonical `.well` v2 JSON-LD context. Tools that understand JSON-LD can then transform the document into RDF for use with SPARQL, triple stores, or GWML2-conformant pipelines.
### Context publication plan
The canonical context is staged across six phases (0–5). Each phase publishes a real, fetchable document at the canonical URL — at no phase is the URL a placeholder.
**Phase 0 — Reservation (v2.0 ratification)**
A minimal valid JSON-LD 1.1 document is published at `https://welldot.org/context/v2.jsonld` containing only the format-identity terms:
```json
{
"@context": {
"@version": 1.1,
"well": "https://welldot.org/vocab/v2#",
"version": "well:version",
"name": "well:name",
"well_type": "well:well_type"
}
}
```
The document advertises itself as a draft via an `x-welldot-status: reserved` HTTP header and an in-band `@version` marker. This phase reserves the URL and proves it resolves; it does not yet enable meaningful RDF processing.
**Phase 1 — Identity & location (v2.0 + 1 month)**
Adds mappings for `well_id`, `well_id.authority`, `well_id.id`, `well_id.primary`, `location`, `location.lat`, `location.lng`, `location.elevation`, and `construction_date`. Aliases `location.lat`/`lng` to `geo:lat`/`geo:long` from the W3C Geo vocabulary; aliases `construction_date` to `schema:dateCreated` from Schema.org.
After Phase 1, a `.well` document round-trips to a minimal valid RDF graph representing the well's identity and position.
**Phase 2 — Constructive blocks (v2.0 + 3 months)**
Adds mappings for `bore_hole`, `well_case`, `well_screen`, `reduction`, `surface_case`, `hole_fill`, `cement_pad` and their fields. Where direct GWML2 alignment is verified against the published XSDs, terms alias to the GWML2-WellConstruction namespace; otherwise terms live under the `welldot:vocab/v2#` namespace with explanatory `rdfs:comment` annotations.
**Phase 3 — Geological & hydrogeological (v2.0 + 6 months)**
Adds mappings for `lithology`, `fractures`, `caves`, `texture`, `texture.code`, `texture.vocabulary`. Aliases `lithology` to `gwml:GW_GeologyLogCoverage` once the corresponding GWML2 class name is verified against the OGC schema documents.
**Phase 4 — Hydrodynamic & analysis (v2.1)**
Adds mappings for `well_purpose`, `centralizers`, `hydrodynamic_events`, `aquifer_analysis`, `history_logs`, and supporting types (`PumpingStep`, `LevelReading`, `RecoveryPhase`, `Attachment`). Maps event readings to O&M's `OM_Observation` pattern and time-series to TimeseriesML where alignment is verified. This phase ships alongside the v2.1 spec revision.
**Phase 5 — Operational blocks (v2.3)**
Adds mappings for `attachments`, `Attachment.document_type`, `pump_installations`, `PumpInstallation` and `PumpElectrical` fields (including `installed_by` and `removed_by`), `permits` (`Permit`, `VolumeLimit`, `MonthlyGrant`, `PermitCondition`, `ConditionFulfillment`, `PermitHistoryEntry`, and the administrative `status`), `meters` (`Meter`, including `manufacturer`, `model`, `installed_by`, `removed_by` and `attachments`), `production` (`MeterReading`, `EstimatedVolume`, with `production[].corrects`), `operating_regime` (`OperatingRegime`), the `maintenance` and `status_change` log fields, and `hydrodynamic_events[].corrects`. Permit dates map to `xsd:date` and condition durations to `xsd:duration`; `supersedes` is a succession relation between two instruments, distinct from `corrects`. `corrects` maps to a retraction relation between two observation or ledger records; installation, reading and regime instants map to `xsd:dateTime`; `installed_by` and `removed_by` map to the agent of the installation or removal activity (`prov:wasAssociatedWith`), kept as a plain literal; attachments alias to `schema:MediaObject` (`uri` → `schema:contentUrl`, `media_type` → `schema:encodingFormat`, `sha256` → `schema:sha256`). Units bind to QUDT (`unit:KiloW`, `unit:V`, `unit:MilliM2`, `unit:M3` for meter readings and volumes, `unit:M3-PER-HR` for regime flow rates, `unit:HR` for daily operating time, `unit:MilliM` for meter nominal diameter). This phase ships alongside the v2.3 spec revision.
The v2.3 water quality terms ship in the same phase: `water_samples` (`WaterSample`, `SamplingPoint`, `Purge`, `PurgeReading`, `Laboratory`, `Result`, `Parameter`, `Filtration`, `Validation`) and `history_logs[].sample_ids`. Collection, receipt, analysis and validation instants map to `xsd:dateTime`; a `_resolution: "day"` sibling maps the value to `xsd:date` semantics. Each `welldot` parameter code gets a term URI under `https://welldot.org/vocab/water-quality/` (versioned with the published vocabulary), aligned where verified to the corresponding observable property in QUDT, ChEBI or the SOSA/SSN observable-property pattern. A `cas` parameter maps to the CAS Common Chemistry URI of its registry number, for example `{ "code": "71-43-2", "vocabulary": "cas" }` → `https://commonchemistry.cas.org/detail?cas_rn=71-43-2`; a `welldot` code with a published CAS equivalence carries an `owl:sameAs`-style link to the same URI. `x-` vocabularies are left unmapped. Units bind to QUDT (`unit:MilliGM-PER-L`, `unit:MicroS-PER-CentiM`, `unit:DEG_C`, `unit:MilliV`, `unit:BQ-PER-L`, `unit:MicroM`); turbidity, color and microbiological count units without a QUDT term keep their UCUM code as a literal.
### Versioning and stability
- The Phase-0 document is replaced in-place as phases ship, but term URIs under `welldot:vocab/v2#` are append-only and never re-purposed within v2.
- If a term needs to change meaning, it gets a new URI in `welldot:vocab/v3#`. v2 consumers continue to resolve the old term.
- Each phase increments an internal `version` marker on the context document; client tools can detect which phase they're consuming.
- A `CHANGELOG.md` is published alongside the context at `https://welldot.org/context/v2.changelog.md` documenting every term added.
### Optional fallback
If publishing infrastructure fails, files may inline the context object directly under `@context` instead of referencing the URL. This is supported by JSON-LD 1.1 and parsers MUST accept both forms.
---
## Relationship to GWML2
**GroundWaterML 2** (GWML2) is the OGC international standard for groundwater data exchange. It was developed and ratified by an international consortium that includes the Geological Survey of Canada (GSC), U.S. Geological Survey (USGS), CSIRO and the Bureau of Meteorology (Australia), BRGM (France), Federation University Australia, and Salzburg University, with contributing input from the British Geological Survey, the EU INSPIRE Joint Research Centre, the Polish and German geological surveys, and UNESCO's International Groundwater Resources Assessment Centre.
Two versions of the standard are currently in use:
- **OGC 16-032r2** — _WaterML 2: Part 4 — GroundWaterML 2 (GWML2)_, version 2.2, ratified in 2016 and published in 2017
- **OGC 19-013** — GWML2 version 2.2.1, ratified in 2019 and published in 2021, which updates 16-032r2 by aligning with GeoSciML 4.1 and Observations & Measurements 2.0
GWML2 defines its model in three stages — a conceptual UML schema, a logical UML schema specialized for OGC standards, and an XML/GML encoding — and explicitly anticipates future JSON or RDF encodings. The standard is organized into six modular packages: GWML2-Main, GWML2-Constituent, GWML2-Flow, GWML2-Well, GWML2-WellConstruction, and GWML2-AquiferTest.
`.well` is a JSON-native format that scopes down to the static record plus operational history of a single well. It is not a competing conceptual model — it is an opinionated encoding for a subset of the GWML2 domain, designed for tooling and developer adoption. The optional JSON-LD `@context` (see § JSON-LD Context) is the technical bridge that allows a `.well` document to also be interpreted as a GWML2-aligned RDF graph.
### Authoritative references
| Reference | Location |
| ----------------------------------- | ----------------------------------------------------- |
| GWML2 v2.2 specification (16-032r2) | `https://docs.ogc.org/is/16-032r2/16-032r2.html` |
| GWML2 v2.2.1 specification (19-013) | `https://docs.ogc.org/is/19-013/19-013.html` |
| GWML2 XML schemas (XSD) | `http://schemas.opengis.net/gwml/2.2/` |
| OGC Hydrology DWG GWML2 wiki | `https://external.ogc.org/twiki_public/HydrologyDWG/` |
### Scope correspondence
| GWML2 Module | Namespace | `.well` coverage |
| ---------------------- | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| GWML2-Main | `http://www.opengis.net/gwml-main/2.2` | Out of scope (aquifers, fluid bodies, hydrogeological units as standalone features) |
| GWML2-Constituent | `http://www.opengis.net/gwml-constituent/2.2` | Partially covered (`water_samples`, v2.3): measured constituents and properties of sampled water |
| GWML2-Flow | `http://www.opengis.net/gwml-flow/2.2` | Out of scope (recharge, discharge, inter-flow) |
| GWML2-Well | `http://www.opengis.net/gwml-well/2.2` | Covered: top-level well identity, `location`, `well_id`, well-as-sampling-feature semantics |
| GWML2-WellConstruction | `http://www.opengis.net/gwml-wellconstruction/2.2` | Covered: `bore_hole`, `well_case`, `well_screen`, `reduction`, `surface_case`, `hole_fill`, `cement_pad` |
| GWML2-AquiferTest | `http://www.opengis.net/gwml-aquifertest/2.2` | Covered: `hydrodynamic_events`, `aquifer_analysis` |
### Conceptual alignment
A `.well` document corresponds conceptually to a GWML2 **`GW_Well`** feature, which itself specializes O&M's `SF_SamplingCurve` — wells in GWML2 are modeled as sampling features along whose length observations are made. Boreholes (`Borehole` from GWML2-WellConstruction) are physical engineering artifacts distinct from wells in GWML2; in `.well` v2, both are collapsed into the constructive blocks of a single record since the format describes one composite well.
A `.well` `Lithology` array corresponds to a GWML2 **`GW_GeologyLogCoverage`**, which is a realization of `CV_DiscreteCoverage` over depth intervals. Each lithology interval references a `GW_HydrogeologicalUnit` — GWML2's specialization of GeoSciML's `GeologicUnit` — via the `geologic_unit` field; `.well` does not model the hydrogeological unit itself as a standalone feature.
### Class mapping (informative)
The mapping below is **informative, not normative**. Class names in italics are verified against published GWML2 documentation (the OGC 16-032r2 specification, the _Hydrogeology Journal_ and _Open Geospatial Data, Software and Standards_ papers, and the OGC Hydrology DWG wiki). Class names marked with `?` are **approximate** — they describe the GWML2 module a `.well` construct belongs to but the exact class name was not verified at the time of writing this draft. Implementers building converters should verify these against the authoritative XSDs at `http://schemas.opengis.net/gwml/2.2/` before relying on them.
| `.well` construct | GWML2 module | Class / property |
| -------------------------------- | ---------------------- | --------------------------------------------------------------------- |
| Well (top-level) | GWML2-Well | _`GW_Well`_ (specializes `O&M:SF_SamplingCurve`) |
| `well_id[]` | GWML2-Well | `GW_Well` identifier property (?) |
| `location` | GWML2-Well | `GW_Well` shape / position property (?) |
| `bore_hole[]` | GWML2-WellConstruction | `Borehole` (?) |
| `well_case[]` | GWML2-WellConstruction | `Casing` (?) |
| `surface_case[]` | GWML2-WellConstruction | `Casing` with surface depth range (?) |
| `well_screen[]` | GWML2-WellConstruction | `Screen` (?) |
| `reduction[]` | GWML2-WellConstruction | No direct equivalent — casing transition |
| `hole_fill[]` | GWML2-WellConstruction | Annular fill / gravel pack / seal (?) |
| `cement_pad` | GWML2-WellConstruction | No direct equivalent — wellhead furniture |
| `centralizers[]` | GWML2-WellConstruction | No verified equivalent — casing accessory (?) |
| `well_purpose[]` | GWML2-Well | `GW_Well` purpose / use property (?) |
| `lithology[]` | GWML2-Well | _`GW_GeologyLogCoverage`_ (realizes `CV_DiscreteCoverage`) |
| `lithology[].geologic_unit` | GWML2-Main | _`GW_HydrogeologicalUnit`_ (specializes GeoSciML `GeologicUnit`) |
| `fractures[]` | GWML2-Well | Fracture log interval (?) |
| `caves[]` | GWML2-Well | Void / cavity log interval (?) |
| `hydrodynamic_events[]` | GWML2-AquiferTest | `GW_AquiferTest` (?) plus `OM_Observation` series via TimeseriesML |
| `aquifer_analysis[]` | GWML2-AquiferTest | Aquifer test result properties (?) |
| `history_logs[]` | n/a | No direct equivalent — operational record outside GWML2 scope |
| `pump_installations[]` | GWML2-WellConstruction | Pump / wellhead equipment (?) — pending check against the XSD |
| `permits[]` | n/a | No direct equivalent — operational record outside GWML2 scope |
| `meters[]` | n/a | No direct equivalent — operational record outside GWML2 scope |
| `production[]` | n/a | No direct equivalent — operational record outside GWML2 scope |
| `operating_regime[]` | n/a | No direct equivalent — operational record outside GWML2 scope |
| `attachments[]` (root) | n/a | No direct equivalent — documents referenced by URL |
| `hydrodynamic_events[].corrects` | n/a | No direct equivalent — ledger correction outside GWML2 scope |
| `water_samples[]` | GWML2-Constituent | `SF_Specimen` (?) — the sample taken from the `GW_Well` |
| `water_samples[].results[]` | GWML2-Constituent | `OM_Measurement` (?) on the specimen, observed property = `parameter` |
| `water_samples[].corrects` | n/a | No direct equivalent — ledger correction outside GWML2 scope |
A complete, normative mapping will be published as a separate document once the GWML2 XSDs have been read directly and each verified class name and property path has been confirmed. Until then, this table is a structural guide and not a conversion specification.
### Notes on the mapping
- **Wells vs. boreholes in GWML2.** GWML2 distinguishes a `GW_Well` (the construction with hydrogeological purpose) from a `Borehole` (the physical hole). A single `.well` document conflates both since it describes one composite installation. A `.well → GWML2` converter must emit both features and link them; a `GWML2 → .well` converter must merge them.
- **Aquifer tests in GWML2.** GWML2-AquiferTest uses TimeseriesML (OGC 15-042r2) for time-series readings and O&M's `OM_Observation` pattern for individual measurements. The `.well` event types (`spot_measurement`, `constant_rate`, `step_drawdown`, `airlift`, `recovery_only` and, since v2.4, `logger_series`) are pragmatic field-record categories with no direct GWML2 counterpart; a converter must decompose each into one or more `GW_AquiferTest` features with appropriate `OM_Observation` results. A `logger_series` is not a test: a converter maps it to a TimeseriesML series of `OM_Observation` results on the well, without a `GW_AquiferTest`.
- **Hydrogeological units.** `.well` stores `lithology[].geologic_unit` and `aquifer_unit` as free-text strings. In GWML2 these resolve to references to `GW_HydrogeologicalUnit` features, which in real GWML2 deployments are typically catalogued in national registries (e.g. French BDLISA, Canadian GIN). A `.well → GWML2` converter SHOULD resolve these strings to authoritative unit URIs when a national vocabulary is available; absent that, it emits inline `GW_HydrogeologicalUnit` features with `gml:name` set to the string value.
- **Water samples as specimens.** A `water_samples` entry corresponds to an O&M `SF_Specimen` taken from the well (the sampled feature), with `datetime` as the sampling time and `sampling_point` as the sampling location along the well. Each result is an `OM_Measurement` whose observed property is the parameter (a CAS or `welldot` term URI), whose result is the value in the code's canonical unit, and whose procedure is `method`. GWML2-Constituent describes the constituents of a fluid body rather than individual analyses, so a converter maps `.well` results to O&M observations on the specimen and derives constituent statements only when needed. Censoring (`qualifier`), detection and quantification limits, `lab_flags` and `validation` have no direct GWML2 counterpart and travel as result quality metadata or `x-` extensions. Field QA/QC links (`parent_sample_id`), purge data and chain of custody are outside GWML2 scope. Limits and exceedances are never part of the file and have no mapping.
- **History logs are outside GWML2 scope.** GWML2 models the well as a static feature and its observations as a time-series. Maintenance interventions, incidents, status changes (`status_change`), permit condition fulfillment, and operational events are not represented. The same holds for the v2.3 operational blocks (`permits`, `meters`, `production`, `operating_regime`): metered production is an abstraction record, not an aquifer observation. A converter SHOULD preserve `.well` `history_logs` in a GeoSciML observation event sequence or as an `x-` extension on the GWML2 side, but full lossless round-trip is not guaranteed.
### Profiles for regulatory adoption
National regulators with existing data models (Brazil SIAGAS, French BSS / BDLISA, EU INSPIRE hydrogeology) can publish `.well` profiles that constrain the format to their required fields and vocabularies. Profile schemas may extend the canonical `.well` v2 schema with regulator-specific requirements (e.g. mandatory `well_id` entries for the relevant national authority, restricted enums for casing materials, required precision fields) without breaking interoperability with the core spec.
---
## Schema Validation
The canonical JSON Schema for `.well` v2 is published at:
```
https://welldot.org/schema/v2/well.schema.json
```
The schema conforms to **JSON Schema 2020-12** (`https://json-schema.org/draft/2020-12/schema`). Implementers SHOULD use a JSON Schema validator that supports this draft (Ajv 8+, python-jsonschema 4+, NetworkNT json-schema-validator). Earlier-draft validators may work for most fields but are not officially supported.
Files MAY include a `$schema` field referencing this URL:
```json
{
"$schema": "https://welldot.org/schema/v2/well.schema.json",
"version": "2.4",
...
}
```
The schema is generated from the Zod validators in `@welldot/core` to prevent drift between runtime validation and published schema. The generated file lives at [`spec/schema/v2/well.schema.json`](../schema/v2/well.schema.json) in the repository root, is published at `https://welldot.org/schema/v2/well.schema.json` and ships in `@welldot/core` as `@welldot/core/well.schema.json`. Profile schemas SHOULD extend this canonical schema via `allOf` + `$ref`.
---
_`.well` Format Specification v2.4_
# `.well` File Format Specification — Version 2.4: Object Schemas
**See also:** [overview.md](./overview.md) · [format-reference.md](./format-reference.md) · [interoperability.md](./interoperability.md) · [water-quality.md](./water-quality.md)
## Overview
This part of the `.well` v2.4 specification defines the records inside each top-level block: the common types (`Attachment`, date durations), the construction and geology objects, the `hydrodynamic_events` ledger and its supporting objects, `aquifer_analysis`, `history_logs`, and the operational blocks added in v2.3 (`pump_installations`, `permits`, `meters`, `production`, `operating_regime`). It ends with a complete example file. Water samples are specified in [water-quality.md](./water-quality.md); units and cross-reference rules in [format-reference.md](./format-reference.md).
## Table of contents
- [Common Types _(since v2.3)_](#common-types-since-v23)
- [`Attachment`](#attachment)
- [Date duration](#date-duration)
- [Object Schemas](#object-schemas)
- [`BoreHole` — `bore_hole[]`](#borehole--bore_hole)
- [`WellCase` — `well_case[]`](#wellcase--well_case)
- [`Reduction` — `reduction[]`](#reduction--reduction)
- [`WellScreen` — `well_screen[]`](#wellscreen--well_screen)
- [`SurfaceCase` — `surface_case[]`](#surfacecase--surface_case)
- [`HoleFill` — `hole_fill[]`](#holefill--hole_fill)
- [`Centralizer` — `centralizers[]` _(since v2.1)_](#centralizer--centralizers-since-v21)
- [`CementPad` — `cement_pad`](#cementpad--cement_pad)
- [`Lithology` — `lithology[]`](#lithology--lithology)
- [`Texture`](#texture)
- [`Fracture` — `fractures[]`](#fracture--fractures)
- [`Cave` — `caves[]`](#cave--caves)
- [`hydrodynamic_events[]`](#hydrodynamic_events)
- [Event types](#event-types)
- [Common fields (all event types)](#common-fields-all-event-types)
- [Corrections _(since v2.3)_](#corrections-since-v23)
- [`spot_measurement`](#spot_measurement)
- [`constant_rate`](#constant_rate)
- [`step_drawdown`](#step_drawdown)
- [`airlift`](#airlift)
- [`recovery_only`](#recovery_only)
- [`logger_series` _(since v2.4)_](#logger_series-since-v24)
- [Supporting Object Schemas](#supporting-object-schemas)
- [`PumpingStep`](#pumpingstep)
- [`LevelReading`](#levelreading)
- [`RecoveryPhase`](#recoveryphase)
- [`aquifer_analysis[]`](#aquifer_analysis)
- [`method` — Recommended values](#method--recommended-values)
- [Derived Parameters — Computation Reference](#derived-parameters--computation-reference)
- [Querying Current State](#querying-current-state)
- [`history_logs[]`](#history_logs)
- [Categories](#categories)
- [`HistoryLogEntry`](#historylogentry)
- [`updated_at` semantics](#updated_at-semantics)
- [Data links _(since v2.3)_](#data-links-since-v23)
- [Category-specific fields _(since v2.3)_](#category-specific-fields-since-v23)
- [`pump_installations[]` _(since v2.3)_](#pump_installations-since-v23)
- [`PumpInstallation`](#pumpinstallation)
- [`PumpElectrical`](#pumpelectrical)
- [Validation (warnings, never rejection)](#validation-warnings-never-rejection)
- [Derived values](#derived-values)
- [Rendering](#rendering)
- [`permits[]` _(since v2.3)_](#permits-since-v23)
- [`Permit`](#permit)
- [`PermitHistoryEntry`](#permithistoryentry)
- [`VolumeLimit`](#volumelimit)
- [`MonthlyGrant`](#monthlygrant)
- [`PermitCondition`](#permitcondition)
- [Deadline generation (normative)](#deadline-generation-normative)
- [Condition fulfillment](#condition-fulfillment)
- [Permit status (derived)](#permit-status-derived)
- [Validation (warnings, never rejection)](#validation-warnings-never-rejection-1)
- [`meters[]` _(since v2.3)_](#meters-since-v23)
- [`Meter`](#meter)
- [Validation (warnings, never rejection)](#validation-warnings-never-rejection-2)
- [`production[]` _(since v2.3)_](#production-since-v23)
- [Common fields](#common-fields)
- [`meter_reading`](#meter_reading)
- [`estimated_volume`](#estimated_volume)
- [Volume derivation (normative)](#volume-derivation-normative)
- [Precedence](#precedence)
- [Derived values](#derived-values-1)
- [Validation (warnings, never rejection)](#validation-warnings-never-rejection-3)
- [`operating_regime[]` _(since v2.3)_](#operating_regime-since-v23)
- [`OperatingRegime`](#operatingregime)
- [Validation (warnings, never rejection)](#validation-warnings-never-rejection-4)
- [`water_samples[]` _(since v2.3)_](#water_samples-since-v23)
- [Complete Example](#complete-example)
---
## Common Types _(since v2.3)_
### `Attachment`
The `.well` format is not a file container. Attachments are referenced by URL. Since v2.3, `Attachment` is a common type used in several places.
| Field | Type | Required | Description |
| --------------- | ------ | -------- | ------------------------------------------------------------------------------------------ |
| `id` | string | yes | Unique within its owning `attachments` array. UUID v4 recommended. |
| `uri` | string | yes | Full HTTPS URL. A relative path is valid only inside an offline package _(since v2.4)_. |
| `media_type` | string | yes | MIME type (e.g. `image/jpeg`, `application/pdf`). |
| `document_type` | string | no | What the document is. See format-reference.md § `Attachment.document_type`. _(since v2.3)_ |
| `filename` | string | no | Original filename for display. |
| `description` | string | no | Caption or content description. |
| `sha256` | string | no | SHA-256 hash of the file in lowercase hex. Consumers SHOULD validate after download. |
#### Where attachments are allowed
| Location | Purpose |
| --------------------------------------------------- | --------------------------------------------------------------------------- |
| Root `attachments[]` | Documents about the well as a whole, not tied to any record. _(since v2.3)_ |
| `history_logs[].attachments` | Supporting documents or photos of a log entry. |
| `permits[].attachments` | The legal document (portaria, certificate). _(since v2.3)_ |
| `permits[].history[].attachments` | Notices, receipts, protocols of an administrative step. _(since v2.3)_ |
| `permits[].conditions[].fulfillments[].attachments` | Proof of submission (`condition_evidence`). _(since v2.3)_ |
| `pump_installations[].attachments` | Pump curves, invoices, photos. _(since v2.3)_ |
| `meters[].attachments` | Installation photos, invoices, certificates. _(since v2.3)_ |
| `hydrodynamic_events[].attachments` | Field sheets, logger exports. _(since v2.3)_ |
| `aquifer_analysis[].attachments` | Interpretation reports. _(since v2.3)_ |
| `water_samples[].attachments` | Laboratory reports (`lab_report`), field sheets, photos. _(since v2.3)_ |
An attachment belongs to the record it documents. A document that concerns several records is repeated on each; the repetition is one URI and one hash, and the hash guarantees both copies point to the same file. The root array is not a registry, and records do not reference root attachments by id. Nor is it an aggregate: it holds only general files about the well as a whole (e.g. the drilling report), never copies of the attachments of `history_logs`, `permits` (including their `history` entries and condition `fulfillments`), `pump_installations`, `meters`, `hydrodynamic_events`, `aquifer_analysis` or `water_samples` entries.
`Attachment.id` is unique within its owning array: the root `attachments[]`, or the `attachments` array of one record. The same id may appear under different records.
#### URL resolvability
The `uri` field is a reference, not a guarantee. URLs may become unreachable over time; consumers MUST handle fetch failures gracefully and MUST NOT treat them as file-format errors. When `sha256` is present, consumers that successfully fetch the attachment MUST validate the hash and MUST reject content that fails validation. Producers SHOULD include `sha256` for any attachment intended for long-term preservation.
### Date duration
Relative deadlines and recurrences (`permits[].conditions[].due_after`, `recurrence`) are ISO 8601 durations restricted to date components: `PnY`, `PnM`, `PnW`, `PnD` and their combinations, such as `P1Y6M`. At least one component is required. Time components (`T…`) are malformed. Durations are added to calendar dates, never to instants; see format-reference.md § Datetime Conventions.
---
## Object Schemas
### `BoreHole` — `bore_hole[]`
A drilled interval of the borehole. Multiple entries describe a telescoping borehole.
| Field | Type | Required | Description |
| ----------------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `diameter` | number | yes | Borehole diameter in millimeters. |
| `drilling_method` | string | no | Drilling method. Open vocabulary: see [format-reference.md](format-reference.md) § `bore_hole[].drilling_method`. |
```json
{ "from": 0, "to": 80, "diameter": 250, "drilling_method": "rotary" }
```
---
### `WellCase` — `well_case[]`
Steel or plastic casing installed inside the borehole.
| Field | Type | Required | Description |
| ---------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `type` | string | yes | Casing material. Open vocabulary: see [format-reference.md](format-reference.md) § Construction materials. |
| `diameter` | number | yes | Casing outer diameter in millimeters. |
---
### `Reduction` — `reduction[]`
A transition piece connecting two casing or screen sections of different diameters.
| Field | Type | Required | Description |
| ----------- | ------ | -------- | ----------------------------------------------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `diam_from` | number | yes | Diameter at top in millimeters. |
| `diam_to` | number | yes | Diameter at bottom in millimeters. |
| `type` | string | yes | Reducer material. Open vocabulary: see [format-reference.md](format-reference.md) § Construction materials. |
---
### `WellScreen` — `well_screen[]`
Slotted or wire-wound screen section.
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `type` | string | yes | Screen material. Open vocabulary: see [format-reference.md](format-reference.md) § Construction materials. The screen design goes in `slot_type`. |
| `diameter` | number | yes | Screen outer diameter in millimeters. |
| `screen_slot` | number | yes | Slot opening size in millimeters. |
| `slot_type` | string | no | Screen design _(since v2.3)_. Open vocabulary: see [format-reference.md](format-reference.md) § `well_screen[].slot_type`. |
> **v1 migration:** v1's `screen_slot_mm` field is renamed to `screen_slot` in v2. The value is in millimeters before and after — no conversion needed. See § Units for the rule that screen slot is always in millimeters regardless of any application's display preference.
---
### `SurfaceCase` — `surface_case[]`
Outer protective casing installed near the surface.
| Field | Type | Required | Description |
| ---------- | ------ | -------- | ------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `diameter` | number | yes | Casing outer diameter in millimeters. |
---
### `HoleFill` — `hole_fill[]`
Material placed in the annular space between casing and borehole wall.
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ---------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `type` | string | yes | Either `gravel_pack` or `seal`. |
| `diameter` | number | yes | Outer diameter of the filled annulus in millimeters. |
| `description` | string | yes | Material description (e.g. grain size, material). |
---
### `Centralizer` — `centralizers[]` _(since v2.1)_
Centralizers clamped to a casing or screen string to keep it concentric in the borehole, so the gravel pack and annular seal have uniform thickness. Each entry describes a depth interval over which centralizers are installed at a regular spacing, which is how drilling reports usually give them.
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters (depth of the first centralizer). |
| `to` | number | yes | End depth in meters (depth of the last centralizer). `from === to` denotes a single centralizer. |
| `spacing` | number | no | Spacing between consecutive centralizers in meters. Must be positive. Omit when the spacing is unknown. |
| `type` | string | yes | Centralizer type. Open vocabulary: see [format-reference.md](format-reference.md) § `centralizers[].type`. |
| `diameter` | number | no | As-built outer diameter in millimeters. |
| `description` | string | no | Free-text description (e.g. material, manufacturer). |
- The array is **optional**. v2.0 documents without it remain valid.
- Individual positions are derived from `from`, `to`, and `spacing` (`from`, `from + spacing`, … up to `to`). The count is derived and MUST NOT be stored.
- Each entry SHOULD fall within a `well_case` or `well_screen` interval. Validators MUST NOT reject documents that violate this.
- Centralizers do not contribute to the calculated well depth.
---
### `CementPad` — `cement_pad`
Concrete wellhead pad. **All dimensions are in meters**.
| Field | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | yes | Pad material. Open vocabulary: see [format-reference.md](format-reference.md) § `cement_pad.type`; other free text (e.g. a shape such as `circular`) remains valid. |
| `width` | number | yes | Width in meters. |
| `thickness` | number | yes | Thickness in meters. |
| `length` | number | yes | Length in meters. |
> Note: A typical residential cement pad has dimensions on the order of `1.0` meter. Applications presenting these values in non-metric units (e.g. feet for US users) must convert `cement_pad` dimensions along with all other length fields.
---
### `Lithology` — `lithology[]`
Geological description of a depth interval.
| Field | Type | Required | Description |
| --------------- | --------- | -------- | -------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `description` | string | yes | Free-text geological description. |
| `color` | string | yes | Representative color as a CSS hex value. |
| `texture` | `Texture` | yes | Lithology pattern reference. See below. |
| `geologic_unit` | string | yes | Stratigraphic or geologic unit name. |
| `aquifer_unit` | string | yes | Aquifer unit name like "Aquifero Pirabas" or "Massachusetts Aquifer" |
### `Texture`
| Field | Type | Required | Description |
| ------------ | ---------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `code` | string \| number | yes | The texture code within the declared vocabulary. |
| `vocabulary` | string | no | Either a short canonical token or an HTTPS URI identifying the vocabulary. Default: `fgdc`. See below. |
#### `vocabulary` values
The `vocabulary` field accepts two forms:
**Short canonical tokens** — well-known vocabularies maintained by the welldot project or by widely-adopted standards bodies. The v2.0 canonical set is:
| Token | Vocabulary |
| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `fgdc` | FGDC Digital Cartographic Standard for Geologic Map Symbolization (FGDC-STD-013-2006), Chapter 25 — Lithology. Codes are integers. |
| `cgi` | Commission for the Management and Application of Geoscience Information (CGI) Simple Lithology vocabulary. Codes are URIs or short tokens per CGI. |
| `custom` | Application-defined vocabulary with no interoperability guarantee. Codes are opaque strings. Use of `custom` is discouraged when a documented alternative exists; prefer publishing a vocabulary URI. |
**HTTPS URIs** — when a regulator, geological survey, or institution maintains its own lithology vocabulary, the URI of its definition document is a valid `vocabulary` value. The URI SHOULD resolve to a machine-readable vocabulary description (SKOS, JSON-LD, or a documented JSON list of `{code, label}` entries), though parsers do not fetch it.
```json
{ "vocabulary": "fgdc", "code": 607 }
{ "vocabulary": "cgi", "code": "sandstone" }
{ "vocabulary": "https://brgm.fr/vocab/lithology/v1", "code": "GRES" }
{ "vocabulary": "https://welldot.org/vocab/lithology-extended/v1", "code": "LATERITA" }
```
Profiles SHOULD constrain the `vocabulary` field to a specific URI or short token relevant to their regulatory context.
> **v1 migration:** A v1 file's bare `fgdc_texture: ""` field is treated as `texture: { code: "", vocabulary: "fgdc" }`.
```json
{
"from": 0,
"to": 20,
"description": "Areia fina amarelada",
"color": "#f5deb3",
"texture": { "code": 607, "vocabulary": "fgdc" },
"geologic_unit": "Quaternário",
"aquifer_unit": "freático"
}
```
---
### `Fracture` — `fractures[]`
A discrete fracture or fracture zone.
| Field | Type | Required | Description |
| ----------------- | ------- | -------- | -------------------------------------------------------------------- |
| `depth` | number | yes | Depth of fracture in meters. |
| `water_intake` | boolean | yes | Whether the fracture produces water. |
| `description` | string | yes | Free-text description. |
| `swarm` | boolean | yes | Whether this fracture belongs to a swarm. |
| `azimuth` | number | yes | Azimuth in degrees from geographic north (0–360). |
| `dip` | number | yes | Dip angle in degrees from horizontal (0–90). |
| `depth_precision` | number | no | One-sigma precision of `depth` in meters. Default handled by editors |
---
### `Cave` — `caves[]`
A cavity or void zone.
| Field | Type | Required | Description |
| -------------- | ------- | -------- | -------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `water_intake` | boolean | yes | Whether the cave produces water. |
| `description` | string | yes | Free-text description. |
---
## `hydrodynamic_events[]`
A chronological ledger of all hydrodynamic observations; it grows by appending. Events are ordered by `datetime` ascending (UTC-normalized instants); parsers MUST NOT assume order and SHOULD sort by `datetime` when querying. When two events share the same instant, an optional `sequence` integer breaks the tie.
> **Note — interpretive contracts, not pumping methods.** Each `type` value identifies a _category of field record_ with a distinct semantic contract, not a pumping technique or equipment class. Equipment belongs in the `equipment` field; test-analysis method belongs in `method` on `aquifer_analysis`. The contract for each type specifies what data it reliably supplies, how many steps it should carry, whether recovery is permitted, and whether its event ID is valid input to `source_event_ids` on an `aquifer_analysis` entry.
### Event types
| `type` | Português (BR) | What it records | `steps` constraint | `recovery` | Valid in `source_event_ids` |
| ------------------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------ | ---------- | -------------------------------- |
| `spot_measurement` | Medição pontual | Static water level during a routine visit; optionally an informal brief pump observation. Not a controlled test. | SHOULD be 0 or 1 | Optional | As `static_level_source_id` only |
| `constant_rate` | Teste de vazão constante | Controlled pump test at exactly one fixed rate. | SHOULD be exactly 1 | Optional | Yes |
| `step_drawdown` | Teste de vazão escalonada | Controlled pump test at two or more successive rates in ascending order. | SHOULD be ≥ 2, ascending | Optional | Yes |
| `airlift` | Air-lift | Historical fact that air-lift development was performed and produced an estimated yield. | SHOULD be ≥ 1 | Optional | No — see § airlift |
| `recovery_only` | Apenas recuperação | Recovery after a pumping event whose drawdown data was not recorded. | None | Required | Yes |
| `logger_series` | Série de datalogger | Continuous level series recorded by an installed sensor. Not a controlled test nor a static level. _(since v2.4)_ | None | No | No — see § logger_series |
Each known `type` MUST satisfy its own schema: a malformed `constant_rate`, for example, is invalid rather than read as an unknown type. The step counts in the table are recommendations: `steps` MUST be an array of valid `PumpingStep` entries, but another count is valid. _(since v2.4)_
The `type` field accepts any string. Non-canonical values SHOULD use the `x-` prefix.
### Common fields (all event types)
Only `id`, `type`, and `datetime` are required. All others are optional for all types.
| Field | Type | Required | Description |
| ------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------- |
| `id` | string | yes | Unique within `hydrodynamic_events`. UUID v4 recommended. |
| `type` | string | yes | Event type. |
| `datetime` | string | yes | RFC 3339 datetime with mandatory UTC offset (e.g. `"2006-03-14T08:00:00-03:00"`). See § Datetime Conventions. |
| `sequence` | integer | no | Tiebreaker for events sharing the same instant. Lower values sort first. |
| `operator` | string | no | Person or company conducting the measurement or test. |
| `equipment` | string | no | Equipment description. |
| `notes` | string | no | Free-text observations. |
| `corrects` | string | no | `hydrodynamic_events[].id` of the event this one retracts. See below. _(since v2.3)_ |
| `attachments` | `Attachment[]` | no | Field sheets, logger exports. See § Attachment. _(since v2.3)_ |
### Corrections _(since v2.3)_
`hydrodynamic_events` is a ledger: events MAY be edited in place, but editing is NOT RECOMMENDED after the event has been exported. `corrects` remains the recommended mechanism for measurement corrections (a re-measurement): the correction is a new event whose `corrects` holds the id of the retracted event.
- A retracted event stays in the file but is excluded from every derivation (current static level, analyses, charts).
- Chains are allowed (C corrects B, which corrected A); only the last uncorrected entry counts.
- A `corrects` cycle is malformed and emits a warning. A `corrects` value that does not resolve is a dangling reference.
- An `aquifer_analysis` whose `source_event_ids` or `static_level_source_id` points to a retracted event emits a warning naming both ids.
---
### `spot_measurement`
| Field | Type | Required | Description |
| ------------------------ | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `static_level` | number | yes | Depth to water surface from ground level, in meters. Negative when the level is above ground (flowing artesian). See § Level sign convention. |
| `static_level_precision` | number | no | One-sigma precision of `static_level`. |
| `measurement_method` | string | no | Open vocabulary: see [format-reference.md](format-reference.md) § `hydrodynamic_events[].measurement_method`. |
| `steps` | `PumpingStep[]` | no | SHOULD carry at most one step; another count is valid. For an informal brief pump observation during the visit — not a controlled test. Use `constant_rate` for a formal test. |
| `recovery` | `RecoveryPhase` | no | Recovery after the optional pumping step. |
---
### `constant_rate`
| Field | Type | Required | Description |
| ------------------------ | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `static_level` | number | no | Pre-test static level in meters. Omit if not measured. |
| `static_level_precision` | number | no | One-sigma precision of `static_level`. |
| `steps` | `PumpingStep[]` | no | SHOULD carry exactly one entry; another count is valid. A second step would make this a `step_drawdown`. Omit if only recovery data is available. |
| `recovery` | `RecoveryPhase` | no | Recovery measurements after pump shutdown. |
---
### `step_drawdown`
Jacob's `B` and `C` are derived — store them in `aquifer_analysis`, never here.
| Field | Type | Required | Description |
| ------------------------ | --------------- | -------- | --------------------------------------------------------------------------------------- |
| `static_level` | number | no | Pre-test static level in meters. |
| `static_level_precision` | number | no | One-sigma precision of `static_level`. |
| `steps` | `PumpingStep[]` | yes | SHOULD carry two or more steps in ascending order of flow rate; another count is valid. |
| `recovery` | `RecoveryPhase` | no | Recovery after the final step. |
---
### `airlift`
This type exists to record the historical fact that air-lift development was performed and produced an estimated yield. It is **not** suitable input for `aquifer_analysis`: tools producing `aquifer_analysis` entries MUST refuse to accept `airlift` event IDs in `source_event_ids` and MUST emit a warning if one is supplied.
Static level is not reliably measurable during air-lift and SHOULD be omitted.
| Field | Type | Required | Description |
| ---------- | --------------- | -------- | --------------------------------------------------------------------------------------------------- |
| `steps` | `PumpingStep[]` | yes | SHOULD carry one or more steps; another count is valid. Each step may carry time-series `readings`. |
| `recovery` | `RecoveryPhase` | no | Recovery measurements after air-lift shutdown, if recorded. |
---
### `recovery_only`
| Field | Type | Required | Description |
| ------------------ | --------------- | -------- | ------------------------------------------------ |
| `pumping_rate` | number | no | Estimated preceding flow rate in m³/h. |
| `pumping_duration` | number | no | Estimated preceding pumping duration in minutes. |
| `recovery` | `RecoveryPhase` | yes | Recovery time-series. |
---
### `logger_series` _(since v2.4)_
A continuous level series recorded by an installed sensor: a pressure transducer with a datalogger, as used in monitoring networks and in mining (dewatering, slope piezometers, permit conditions). It is neither a controlled test nor a static level.
| Contract | Value |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `steps` | None. `steps` MUST NOT be present. |
| `recovery` | No. `recovery` MUST NOT be present. |
| Valid in `source_event_ids` | No. |
| Valid as `static_level_source_id` | No. |
| Inherited common fields | `equipment`, `operator`, `attachments` (the raw logger export, with `sha256`), `corrects`, and the `history_logs[].hydrodynamic_event_ids` link. |
| Field | Type | Required | Description |
| ------------------------- | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `measurement_method` | string | no | Open vocabulary: see [format-reference.md](format-reference.md) § `hydrodynamic_events[].measurement_method`. |
| `sensor_depth` | number | no | Sensor depth in meters from ground level. A level below it indicates an invalid reading. |
| `interval` | number | no | Nominal interval between readings, in minutes. Greater than zero. |
| `barometric_compensation` | string | no | Closed: `barologger` (Barologger / Barologger), `vented` (Vented cable / Cabo ventilado) or `none` (None / Nenhuma). |
| `aggregation` | string | no | Closed: `none` (Raw readings / Leituras brutas), `hourly` (Hourly / Horária) or `daily` (Daily / Diária). When `hourly` or `daily`, every reading MUST carry `depth_min` and `depth_max`. |
| `readings` | `LevelReading[]` | yes | One or more readings. Each reading MUST carry `datetime` and MUST NOT carry `elapsed`. See § LevelReading. |
A series MAY grow with new readings instead of becoming one event per download.
> **Note — pump state is not stored per reading.** A series has no per-reading `pumping` flag. In a well that pumps, the series mixes static and dynamic levels; interpretation crosses the series with `production` and `pump_installations`.
Validation (warnings, never rejection):
- An `aquifer_analysis` whose `source_event_ids` or `static_level_source_id` points to a `logger_series` emits a warning naming both ids.
- A reading whose `depth` is greater than `sensor_depth` (the level is below the sensor) emits a warning naming the event.
```json
{
"id": "8f2c1e4a-3b6d-4f0e-9a7c-5d1b2e3f4a5b",
"type": "logger_series",
"datetime": "2026-07-01T00:00:00-03:00",
"equipment": "Solinst Levelogger 5 LT, SN 2081234",
"measurement_method": "pressure_transducer",
"sensor_depth": 42.0,
"interval": 15,
"barometric_compensation": "barologger",
"aggregation": "none",
"readings": [
{
"datetime": "2026-07-01T00:00:00-03:00",
"depth": 18.42,
"pressure": 233.9
},
{
"datetime": "2026-07-01T00:15:00-03:00",
"depth": 18.44,
"pressure": 233.7
}
]
}
```
---
## Supporting Object Schemas
### `PumpingStep`
| Field | Type | Required | Description |
| ---------------- | ---------------- | -------- | ----------------------------------------------------------------- |
| `rate` | number | yes | Pumping rate in m³/h. |
| `rate_precision` | number | no | One-sigma precision of `rate`. |
| `duration` | number | no | Duration in minutes. |
| `readings` | `LevelReading[]` | no | Time-series during this step. A single terminal reading is valid. |
### `LevelReading`
| Field | Type | Required | Description |
| ----------------- | ------ | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| `elapsed` | number | yes, except in `logger_series` | Time since step start (or pump shutdown for recovery) in minutes. MUST NOT be present in `logger_series` readings. |
| `datetime` | string | in `logger_series` only | RFC 3339 instant of the reading, with UTC offset. Required in `logger_series`; optional elsewhere. _(since v2.4)_ |
| `depth` | number | yes | Depth to water surface from ground level in meters. In an aggregated `logger_series`, the representative value of the interval. |
| `depth_precision` | number | no | One-sigma precision of `depth`. |
| `pressure` | number | no | Pressure transducer reading in kPa. |
| `depth_min` | number | aggregated `logger_series` only | Shallowest depth in the interval, in meters. Required when `aggregation` is `hourly` or `daily`; used only there. _(since v2.4)_ |
| `depth_max` | number | aggregated `logger_series` only | Deepest depth in the interval, in meters. Required when `aggregation` is `hourly` or `daily`; used only there. _(since v2.4)_ |
### `RecoveryPhase`
| Field | Type | Required | Description |
| ---------- | ---------------- | -------- | ------------------------------------------------------------------ |
| `readings` | `LevelReading[]` | yes | Time-series after pump shutdown. `elapsed` measured from shutdown. |
---
## `aquifer_analysis[]`
An array of interpreted aquifer parameter sets. Multiple entries may coexist, representing different methods or analysts.
| Field | Type | Required | Description |
| ------------------------- | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | yes | Unique within `aquifer_analysis`. |
| `datetime` | string | yes | RFC 3339 datetime with mandatory UTC offset. |
| `analyst` | string | no | Name of the hydrogeologist. |
| `source_event_ids` | string[] | yes | IDs of source `hydrodynamic_events` from this same file. See § Cross-reference and Uniqueness Rules. |
| `method` | string | no | See vocabulary below. |
| `static_level` | number | no | Static level used as reference, in meters. |
| `static_level_precision` | number | no | One-sigma precision of `static_level`. |
| `static_level_source_id` | string | no | ID of the event from which `static_level` was taken. |
| `dynamic_level` | number | no | Stabilized dynamic level in meters. |
| `dynamic_level_precision` | number | no | One-sigma precision of `dynamic_level`. |
| `flow_rate` | number | no | Flow rate associated with `dynamic_level`, in m³/h. |
| `flow_rate_precision` | number | no | One-sigma precision of `flow_rate`. |
| `max_flow_rate` | number | no | Maximum recommended sustained extraction rate in m³/h, as judged by the analyst from the source events. This is an interpretive recommendation, not the test rate. Distinct from `flow_rate`, which is the rate actually applied during the test that produced `dynamic_level`. |
| `max_flow_rate_precision` | number | no | One-sigma precision of `max_flow_rate` in m³/h. |
| `max_flow_rate_basis` | string | no | Free-text justification: safety factor applied, regulatory framework referenced (e.g. `"80% of test rate per ANA practice"`, `"limited by available drawdown to top of screen"`), or assumptions about long-term recharge. |
| `specific_capacity` | number | no | `Q/s` in m³/h per m (i.e. m²/h). |
| `transmissivity` | number | no | Aquifer transmissivity `T` in m²/s. |
| `storativity` | number | no | Dimensionless storativity `S`. |
| `hydraulic_conductivity` | number | no | Hydraulic conductivity `K` in m/s. Requires `aquifer_thickness`. |
| `aquifer_thickness` | number | no | Saturated aquifer thickness in meters. |
| `jacob_b` | number | no | Formation loss coefficient from Jacob's equation. |
| `jacob_c` | number | no | Well loss coefficient from Jacob's equation. |
| `well_efficiency_pct` | number | no | Well efficiency as a percentage. |
| `attachments` | `Attachment[]` | no | Interpretation reports. See § Attachment. _(since v2.3)_ |
| `notes` | string | no | Methodology notes, assumptions, data quality remarks. |
### `method` — Recommended values
`cooper_jacob`, `theis`, `neuman`, `hantush`, `birsoy_summers`, `eden_hazel`, `visual_inspection`; labels in [format-reference.md](format-reference.md) § `aquifer_analysis[].method`. Non-canonical values SHOULD use the `x-` prefix.
---
## Derived Parameters — Computation Reference
The following values are **never stored**. Applications compute them on demand.
| Parameter | Formula | Inputs |
| ---------------------------- | -------------------------------------------- | ---------------------------------------------- |
| Drawdown `s` at time `t` | `depth(t) − static_level` | `LevelReading.depth`, event `static_level` |
| Residual drawdown (recovery) | `depth(t) − static_level` | `RecoveryPhase.readings`, event `static_level` |
| Specific capacity `Q/s` | `flow_rate / (dynamic_level − static_level)` | `AquiferAnalysis` fields |
| Unit drawdown `s/Q` | `(dynamic_level − static_level) / flow_rate` | `AquiferAnalysis` fields |
| Formation loss | `jacob_b × Q` | `AquiferAnalysis.jacob_b`, `flow_rate` |
| Well loss | `jacob_c × Q²` | `AquiferAnalysis.jacob_c`, `flow_rate` |
| Hydraulic conductivity `K` | `transmissivity / aquifer_thickness` | `AquiferAnalysis` fields |
---
## Querying Current State
**Current static level (NE):** Exclude retracted events (§ Corrections), filter the remaining events where `static_level` is present, sort by `datetime` descending (UTC-normalized), take the first result.
**Current specific capacity:** Find the most recent `aquifer_analysis` with `specific_capacity` present.
**Current transmissivity:** Find the most recent `aquifer_analysis` with `transmissivity` present.
**Recommended maximum flow:** Read the most recent `aquifer_analysis[].max_flow_rate`, with `max_flow_rate_basis` and `max_flow_rate_precision`.
**Current pump:** The `pump_installations` entry without `removed_at`. See § `pump_installations[]`.
**Current meter(s):** The `meters` entries without `removed_at`. More than one open meter is valid (secondary meters) but emits a warning. See § `meters[]`.
**Regime in force:** The `operating_regime` entry with the latest `effective_from` not after the instant of interest. Before the first entry, the regime is unknown. See § `operating_regime[]`.
**Current well status:** The `status` of the `history_logs` entry of category `status_change` with the latest `datetime`. With no `status_change` entry, the status is **unknown** — applications MUST NOT assume `active`. See § `history_logs[]` — `status_change`.
---
## `history_logs[]`
A mutable chronological record of physical interventions, inspections, and incidents. Entries may be edited or removed to correct field errors.
Each entry carries two distinct timestamps:
- **`datetime`** — when the logged event actually occurred in the field.
- **`updated_at`** — when this log entry was most recently created or edited in the record system. This timestamp tracks the data lineage of the entry itself, not the event it describes.
These timestamps may differ substantially: a maintenance intervention `datetime` of 2018-06-12 may be paired with an `updated_at` of 2024-03-15 if the entry was added retroactively or corrected.
### Categories
| `category` | Portuguese (BR) | Description |
| --------------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `maintenance` | Manutenção | Physical intervention or service task: pump replacement, casing repair, cleaning, redevelopment, meter calibration. Carries the category-specific fields below _(since v2.3)_. |
| `inspection` | Inspeção | Site visit without physical alteration: visual survey, camera inspection, sample collection. |
| `incident` | Incidente | Unplanned event: partial collapse, contamination, prolonged drought, vandalism. |
| `event` | Evento | Generic milestone: construction completion, commissioning, change of use, ownership transfer, rehabilitation, data-integrity repair. |
| `status_change` | Mudança de situação | _(since v2.3)_ The well's operating status changed (e.g. active → inactive). Carries the category-specific fields below. |
Vocabulary is open. Non-canonical values SHOULD use the `x-` prefix.
### `HistoryLogEntry`
| Field | Type | Required | Description |
| ------------------------ | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `id` | string | yes | Unique within `history_logs`. |
| `datetime` | string | yes | RFC 3339 datetime with mandatory UTC offset. When the logged event occurred. |
| `updated_at` | string | no | RFC 3339 datetime with mandatory UTC offset. When this entry was most recently created or edited. When omitted, treated as equal to `datetime`. Producers SHOULD set this field whenever they create or modify an entry. |
| `category` | string | yes | Event category. |
| `description` | string | yes | Free-text account: work performed, findings, or incident narrative. |
| `author` | string | no | Person or company responsible for the record. |
| `severity` | string | no | Open vocabulary: see [format-reference.md](format-reference.md) § `history_logs[].severity`. |
| `attachments` | `Attachment[]` | no | Supporting documents or photos. See § Attachment. |
| `hydrodynamic_event_ids` | string[] | no | _(since v2.3)_ `hydrodynamic_events[].id`s holding the data produced or observed by this entry. Valid on every category. See § Data links. |
| `sample_ids` | string[] | no | _(since v2.3)_ `water_samples[].id`s collected or concerned by this entry. Valid on every category. See § Data links. |
### `updated_at` semantics
- `updated_at` is usually later than `datetime`. It MAY be earlier when an entry is pre-dated (retroactive entry of a historical event); both orderings are valid and neither emits a warning.
- When a producer modifies the `description`, `category`, `severity`, `author`, or `attachments` of an existing entry, it SHOULD update `updated_at` to the current instant.
- When a producer modifies only the `id` (e.g. during duplicate-resolution), it SHOULD NOT update `updated_at`, since the substantive content of the entry has not changed.
- The `updated_at` field is not retroactive: parsers reading a v1 or early-v2.0 file without `updated_at` MUST NOT synthesize one.
`Attachment` is a common type since v2.3; see § Common Types — `Attachment`.
### Data links _(since v2.3)_
A log entry records that something was done or observed; measured values belong in their own block and are referenced by id, never copied into the log. `hydrodynamic_event_ids` and `sample_ids` link an entry of any category to that data: a `level_measurement` maintenance points to its `spot_measurement` event, a `pump_test` to its `constant_rate` or `step_drawdown` event, a `water_sampling` task or an `inspection` visit to the samples it collected (a sample, its field duplicate and a blank are three ids), and an `incident` to the sample that revealed a contamination. The log keeps what the data block does not: crew, cost, invoices and the narrative of the task.
Both fields are arrays of distinct ids from this same file. An id that does not resolve emits a warning. A link to a ledger entry that was later retracted (`corrects`) is not rewritten; consumers resolve it to the entry in force when needed.
### Category-specific fields _(since v2.3)_
Category-specific fields MUST be absent on entries of other categories; their presence emits a warning. They are optional in the schema: a missing required category field (e.g. `maintenance_type` on a `maintenance` entry) surfaces as a warning, never a rejection.
Permit condition fulfillment is not a log category: it is recorded on the condition itself (see § `permits[]` — Condition fulfillment).
#### `maintenance`
| Field | Type | Required | Description |
| ---------------------- | ------ | -------- | ------------------------------------------------------------- |
| `maintenance_type` | string | yes | See format-reference.md § `history_logs[].maintenance_type`. |
| `pump_installation_id` | string | no | `pump_installations[].id` the task concerns. |
| `meter_id` | string | no | `meters[].id` the task concerns (e.g. a `meter_calibration`). |
The data a task produced is linked with `hydrodynamic_event_ids` / `sample_ids` (see § Data links), never copied into the log.
#### `status_change`
| Field | Type | Required | Description |
| -------- | ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `status` | string (enum) | yes | One of the five values below (closed vocabulary; any other value is rejected). See format-reference.md § `history_logs[].status`. |
| Value | Portuguese (BR) | Semantics |
| ---------------- | ---------------------- | --------------------------------------- |
| `active` | Ativo / em operação | Normal operation. |
| `maintenance` | Em manutenção | Temporarily out of service for work. |
| `inactive` | Paralisado | Out of service, recoverable. |
| `decommissioned` | Desativado / tamponado | Permanently closed and properly sealed. |
| `abandoned` | Abandonado | Left without proper sealing. |
The current status is the `status` of the `status_change` entry with the latest `datetime`. With no such entry, the status is unknown. `status` is a closed vocabulary: the `x-` extension mechanism does not apply, and a file carrying any other value is rejected.
Any non-`status_change` entry dated after a `decommissioned` or `abandoned` status emits a warning. A stopped well is recorded with a `status_change`, never with an `operating_regime` entry whose `flow_rate` is `0`.
---
## `pump_installations[]` _(since v2.3)_
An installation block: each entry is one installation of a pump in the well, present from `installed_at` until `removed_at`. Entries are edited in place, and `updated_at` records the last edit. The current pump and its submergence are derived, never stored.
- `removed_at` earlier than or equal to `installed_at` is malformed and emits a warning.
- `installed_by` and `removed_by` name who did the work (person or company, free text). `removed_by` on an entry without `removed_at` carries no meaning and consumers ignore it.
- Each entry is one installation, not one piece of equipment. A unit pulled and reinstalled gets a new entry with the same `serial`.
- Overlapping open installations emit a warning, not an error. Standby pumps exist.
### `PumpInstallation`
| Field | Type | Required | Unit | Description |
| ----------------- | ---------------- | -------- | ---- | --------------------------------------------------------------------------- |
| `id` | string | yes | | Unique within `pump_installations`. UUID v4 recommended. |
| `installed_at` | string (instant) | yes | | RFC 3339 instant when the pump entered service in this well. |
| `removed_at` | string (instant) | no | | When it left. Absent means currently installed. |
| `installed_by` | string | no | | Person or company that installed the pump. |
| `removed_by` | string | no | | Person or company that removed the pump. Only meaningful with `removed_at`. |
| `type` | string | yes | | See format-reference.md § `pump_installations[].type`. |
| `power_source` | string | no | | `grid`, `solar`, `diesel`, `hybrid`, `x-…` |
| `manufacturer` | string | no | | |
| `model` | string | no | | |
| `serial` | string | no | | Links reinstallations of the same unit. |
| `intake_depth` | number | no | m | Depth of the pump intake (crivo), from ground level. |
| `rated_flow_rate` | number | no | m³/h | Nameplate duty-point flow. |
| `rated_head` | number | no | m | Nameplate duty-point head. |
| `rated_power` | number | no | kW | Motor power. |
| `stages` | integer | no | | Number of stages. |
| `riser_diameter` | number | no | mm | Riser pipe (edutor), as-built outer diameter. |
| `riser_material` | string | no | | Open vocabulary: see format-reference.md § Construction materials. |
| `check_valve` | boolean | no | | Whether a check valve is installed. |
| `electrical` | `PumpElectrical` | no | | See below. |
| `notes` | string | no | | |
| `updated_at` | string (instant) | no | | Last edit of this record. |
| `attachments` | `Attachment[]` | no | | Pump curves, invoices, photos. See § Attachment. |
### `PumpElectrical`
All fields optional.
| Field | Type | Unit |
| --------------- | ------- | ---------- |
| `voltage` | number | V |
| `phases` | integer | `1` or `3` |
| `cable_section` | number | mm² |
| `cable_length` | number | m |
### Validation (warnings, never rejection)
- `intake_depth` greater than `well_depth`, or below the bottom of the deepest `bore_hole` interval.
- `intake_depth` inside a `well_screen` interval.
- More than one entry without `removed_at`.
- `removed_at` earlier than or equal to `installed_at`.
### Derived values
- **Current pump:** the entry without `removed_at`; if all are removed, none.
- **Submergence:** `intake_depth − dynamic_level`, using the most recent applicable reading during pumping. A negative value means the intake is above the water level.
- **Time in service per unit:** sum of installation durations sharing a `serial`.
### Rendering
Renderers draw the current pump ending at `intake_depth` and its riser from the surface. Earlier installations are not drawn by default.
---
## `permits[]` _(since v2.3)_
A mutable record block: each entry transcribes one legal instrument governing abstraction from the well (outorga, dispensa, cadastro), from its request on. Entries are edited in place, and `updated_at` records the last edit. The attached document is authoritative. The administrative `status` set by the authority is stored; the validity status and condition deadlines are always derived, never stored.
### `Permit`
| Field | Type | Required | Unit | Description |
| ---------------------- | ---------------------- | -------- | ---- | ----------------------------------------------------------------------------- |
| `id` | string | yes | | Unique within `permits`. UUID v4 recommended. |
| `type` | string | yes | | See format-reference.md § `permits[].type`. |
| `authority` | string | yes | | Issuing body, e.g. `ANA`, `SEMAS-PA`. Same semantics as `well_id.authority`. |
| `identifier` | string | no | | Identifier of the granted instrument (portaria, license code), verbatim. |
| `request_identifier` | string | no | | Identifier of the request or administrative process (protocolo), verbatim. |
| `status` | string (enum) | no | | Administrative status. Closed vocabulary, see below. Absent means `granted`. |
| `issued_at` | string (date) | no | | Issue date. |
| `valid_from` | string (date) | no | | Absent means valid from `issued_at`. |
| `valid_until` | string (date) | no | | Absent means no fixed expiry. |
| `renewal_requested_at` | string (date) | no | | Date a renewal request was filed. |
| `water_use` | string[] | no | | See format-reference.md § `permits[].water_use`. |
| `flow_rate` | number | no | m³/h | Maximum granted flow. |
| `daily_operating_time` | number | no | h | Maximum granted daily operating time. Above 24 is read as 24, with a warning. |
| `volume_limits` | `VolumeLimit[]` | no | | Only volumes stated in the document. |
| `monthly_schedule` | `MonthlyGrant[]` | no | | Month-by-month grant. |
| `conditions` | `PermitCondition[]` | no | | Obligations (condicionantes). |
| `history` | `PermitHistoryEntry[]` | no | | Administrative steps: filing, process, notifications, fees. |
| `supersedes` | string | no | | `permits[].id` of the instrument this one legally replaces. |
| `notes` | string | no | | |
| `updated_at` | string (instant) | no | | Last edit of this record. |
| `attachments` | `Attachment[]` | no | | The legal document. See § Attachment. |
`water_use` describes what the abstracted water is for. It is distinct from the top-level `well_purpose`, which describes the well's role.
A renewal is not a permit type: it is a new permit of the same type whose `supersedes` points to the previous one. `supersedes` expresses legal succession between two valid instruments and is not a correction mechanism.
Identifiers are free strings preserved verbatim, because agencies use diverse formats (numbers, letters, slashes). A permit that has only been requested has no `identifier` yet: it is identified by `request_identifier`. At least one of the two SHOULD be present.
#### Administrative status
`status` records what the authority decided, which dates alone cannot express. It is a closed vocabulary: the `x-` extension mechanism does not apply, and a file carrying any other value is rejected.
| Value | Portuguese (BR) | Semantics |
| ----------- | --------------- | --------------------------------------------------------------- |
| `requested` | Requerida | Filed and awaiting a decision. No grant yet. |
| `granted` | Concedida | Issued. The validity status is derived from the dates. Default. |
| `suspended` | Suspensa | Temporarily halted by the authority. |
| `revoked` | Revogada | Cancelled by the authority. |
| `denied` | Indeferida | The request was refused. |
| `withdrawn` | Cancelada | The request or permit was given up by the holder. |
The change itself (notice of suspension, decision) is recorded as a `history` step, with the document attached.
### `PermitHistoryEntry`
| Field | Type | Required | Description |
| ------------- | ---------------- | -------- | ----------------------------------------------------------------------------------- |
| `id` | string | yes | Unique within the permit's `history`. UUID v4 recommended. |
| `date` | string (date) | yes | When the step happened. |
| `type` | string | no | See format-reference.md § `permits[].history[].type`. |
| `description` | string | yes | What happened: protocol, dispatch, notice, fee, inspection, decision. |
| `done` | boolean | no | For actionable steps (fee paid, notification answered). Absent means informational. |
| `due_date` | string (date) | no | Date by which an actionable step must be done. |
| `updated_at` | string (instant) | no | Last edit of this record. |
| `attachments` | `Attachment[]` | no | Notices, receipts, protocols. See § Attachment. |
A step whose `due_date` has passed and that is not `done` is past due. Applications SHOULD surface it.
### `VolumeLimit`
| Field | Type | Required | Unit | Description |
| -------- | ------ | -------- | ---- | --------------------------------------------------------------- |
| `period` | string | yes | | `daily`, `monthly`, `annual`. Each period appears at most once. |
| `volume` | number | yes | m³ | |
Volumes implied by flow × time × days are derived and MUST NOT be stored here.
### `MonthlyGrant`
| Field | Type | Required | Unit | Description |
| ---------------------- | ------- | -------- | ---- | --------------------------------------- |
| `month` | integer | yes | | 1–12, unique within the schedule. |
| `flow_rate` | number | no | m³/h | |
| `daily_operating_time` | number | no | h | Above 24 is read as 24, with a warning. |
| `days` | integer | no | | Days of operation granted in the month. |
When `monthly_schedule` is present, a month absent from it has no abstraction granted. The top-level `flow_rate` and `daily_operating_time` hold the maximum across months; a monthly value above them emits a warning.
### `PermitCondition`
| Field | Type | Required | Description |
| -------------- | ------------------------ | -------- | ---------------------------------------------------------------- |
| `id` | string | yes | Unique within the permit's `conditions` array. |
| `description` | string | yes | Text of the condition as written in the document. |
| `category` | string | no | See format-reference.md § `permits[].conditions[].category`. |
| `first_due` | string (date) | no | Explicit date of the first deadline. |
| `due_after` | string (duration) | no | First deadline relative to the permit's start date, e.g. `P90D`. |
| `recurrence` | string (duration) | no | Interval between deadlines, e.g. `P6M`. Absent means one-time. |
| `last_due` | string (date) | no | No deadline is generated after this date. |
| `occurrences` | integer | no | Maximum number of deadlines, counting the first. |
| `responsible` | string | no | Person or team accountable for meeting the obligation. |
| `fulfillments` | `ConditionFulfillment[]` | no | Deadlines met. See § Condition fulfillment. |
`first_due` and `due_after` are mutually exclusive; a condition carrying both is malformed and `first_due` wins. A condition with neither is undated: it is valid and is displayed, but generates no deadlines. Durations follow § Common Types — Date duration.
### Deadline generation (normative)
Applications generate a condition's deadlines as follows.
1. **Start date** of the permit: `valid_from`, else `issued_at`. Without either, `due_after` cannot be resolved and the condition is undated.
2. **Anchor**: `first_due`, else start date + `due_after`.
3. **Deadline n** (n = 0, 1, 2 …) = anchor + n × `recurrence`, always computed from the anchor, never from the previous deadline. Years and months are added first; when the resulting day does not exist in the target month, that month's last day is used. Weeks and days are then added. Without `recurrence`, only n = 0 exists.
4. **Stop** when any limit is reached: `occurrences` deadlines generated; the date passes `last_due`; or the date passes the permit's effective end.
5. **Effective end** of the permit: if another permit supersedes it, the day before the successor's start date; otherwise `valid_until`; while the status is `active_pending_renewal`, there is no end. With no end, applications generate deadlines up to a horizon of their choice.
6. **Administrative status**: a `requested`, `denied` or `withdrawn` permit generates no deadlines. A `suspended` or `revoked` permit generates none after today; those already due remain.
Computing every deadline from the anchor prevents drift: a monthly obligation anchored on 31 January falls on 28 or 29 February and back on 31 March.
| Document wording | Encoding |
| -------------------------------------------- | ------------------------------------------------------ |
| Install a meter within 90 days | `due_after: "P90D"` |
| Semiannual report from issuance | `due_after: "P6M", recurrence: "P6M"` |
| Semiannual report by 31 Jan and 31 Jul | `first_due: "2027-01-31", recurrence: "P6M"` |
| Monthly level readings during the first year | `due_after: "P1M", recurrence: "P1M", occurrences: 12` |
| Annual water analysis | `due_after: "P1Y", recurrence: "P1Y"` |
| Request renewal 90 days before expiry | `first_due` set to that date by the author |
### Condition fulfillment
Fulfillment is recorded in the condition's `fulfillments`. Each entry fulfills one deadline, identified by its `due_date`.
#### `ConditionFulfillment`
| Field | Type | Required | Description |
| ------------- | ---------------- | -------- | ---------------------------------------------------------------------------------- |
| `id` | string | yes | Unique within the condition's `fulfillments`. UUID v4 recommended. |
| `datetime` | string (instant) | yes | When the obligation was fulfilled, e.g. when a report was filed. |
| `due_date` | string (date) | no | The deadline fulfilled. Absent for undated conditions. |
| `description` | string | no | |
| `author` | string | no | Who fulfilled the obligation. |
| `event_id` | string | no | `hydrodynamic_events[].id` that satisfied the obligation, when applicable. |
| `sample_id` | string | no | `water_samples[].id` that satisfied the obligation, when applicable. |
| `updated_at` | string (instant) | no | Last edit of this record. |
| `attachments` | `Attachment[]` | no | Proof of submission, with `document_type: "condition_evidence"`. See § Attachment. |
A water-quality `monitoring` condition is fulfilled by pointing to the sample with `sample_id`.
Derived status of each deadline, evaluated on the local civil date at the well site:
- **fulfilled** — a fulfillment has its `due_date`. **Fulfilled late** when that fulfillment's local date is after `due_date`. The local date is the `datetime` converted to `location.properties.timezone` _(since v2.4)_; without `timezone`, the date part of the string in the offset it carries.
- **upcoming** — no match and today is on or before `due_date`.
- **overdue** — no match and today is after `due_date`.
An undated condition is fulfilled by an entry without `due_date`. A fulfillment whose `due_date` matches no generated deadline, a fulfillment without `due_date` on a dated condition, or an `event_id` / `sample_id` that does not resolve emits a warning. Conditions belong to their permit: when a permit is superseded, its deadlines stop at its effective end and the successor's conditions take over.
### Permit status (derived)
Evaluated on the local civil date at the well site, in order:
1. A stored `status` other than `granted` → that value (`requested`, `suspended`, `revoked`, `denied`, `withdrawn`).
2. Another permit `supersedes` it → `superseded`.
3. Today is before its start date → `not_yet_valid`.
4. `valid_until` is absent, or today is on or before it → `active`.
5. `renewal_requested_at` is present and on or before `valid_until` → `active_pending_renewal`.
6. Otherwise → `expired`.
The lead time a renewal request requires varies by jurisdiction; it is evaluated by applications or regulatory profiles, not by the format.
### Validation (warnings, never rejection)
- `valid_until` earlier than the start date.
- A `supersedes` cycle, or a `supersedes` reference that does not resolve.
- Two granted, non-superseded permits of the same `type` with overlapping validity.
- A `daily_operating_time` above 24, on the permit or in `monthly_schedule`. It is read as 24 by every derivation; the stored value is not rewritten. A negative value is a schema error.
- A `monthly_schedule` value above the permit's top-level maximum.
- A duplicate `month`, `volume_limits` period, condition `id`, `history` `id` or fulfillment `id` within one permit.
- A condition with both `first_due` and `due_after`.
- A permit with neither `identifier` nor `request_identifier`, or a granted permit without `identifier`.
- A condition fulfillment that matches no deadline, or whose `event_id` / `sample_id` does not resolve.
- Permits with condition fulfillments in a file without `location.properties.timezone` _(since v2.4)_.
A collective grant covering several wells is repeated in each well's file until cross-file references arrive in v3.
---
## `meters[]` _(since v2.3)_
An installation block: each entry is one installation of a totalizer (hidrômetro) in the well, present from `installed_at` until `removed_at`. Entries are edited in place, and `updated_at` records the last edit. A meter holds device facts only: every register value, including the installation and removal readings, lives in `production`. The installation-pattern rules of § `pump_installations[]` apply; overlapping open meters emit a warning, not an error, because secondary meters exist.
### `Meter`
| Field | Type | Required | Unit | Description |
| ------------------ | ---------------- | -------- | ---- | ---------------------------------------------------------------------------- |
| `id` | string | yes | | Unique within `meters`. UUID v4 recommended. |
| `installed_at` | string (instant) | yes | | RFC 3339 instant when the meter entered service in this well. |
| `removed_at` | string (instant) | no | | When it left. Absent means currently installed. |
| `installed_by` | string | no | | Person or company that installed the meter. |
| `removed_by` | string | no | | Person or company that removed the meter. Only meaningful with `removed_at`. |
| `type` | string | no | | See format-reference.md § `meters[].type`. |
| `manufacturer` | string | no | | |
| `model` | string | no | | |
| `serial` | string | no | | Links reinstallations of the same unit. |
| `nominal_diameter` | number | no | mm | Nominal diameter (DN). |
| `max_reading` | number | no | m³ | Register capacity, used to detect rollover. Greater than 0. |
| `notes` | string | no | | |
| `updated_at` | string (instant) | no | | Last edit of this record. |
| `attachments` | `Attachment[]` | no | | Installation photos, invoices, certificates. See § Attachment. |
The register values at installation and removal are recorded as ordinary `meter_reading` entries in `production`, with `datetime` equal to `installed_at` or `removed_at`. Meter calibrations are `history_logs` entries of category `maintenance` with `maintenance_type: "meter_calibration"` and a `meter_id`.
### Validation (warnings, never rejection)
- `removed_at` earlier than or equal to `installed_at`.
- More than one entry without `removed_at`.
- A `production` reading for this meter outside its installation window.
---
## `production[]` _(since v2.3)_
A ledger: the record of water produced by the well, which grows by appending. Entries MAY be edited in place, but editing is NOT RECOMMENDED after the entry has been exported. `corrects` remains the recommended mechanism for measurement corrections: the correction is a new entry whose `corrects` holds the id of the retracted entry, following § `hydrodynamic_events[]` — Corrections (retracted entries stay in the file and are excluded from every derivation; chains are allowed; a cycle emits a warning). Entries are discriminated by `type`. Volumes are always derived, never stored as totals.
### Common fields
| Field | Type | Required | Description |
| ---------- | ------- | -------- | -------------------------------------------------------------------------------------- |
| `id` | string | yes | Unique within `production`. UUID v4 recommended. |
| `type` | string | yes | `meter_reading` or `estimated_volume`. Non-canonical types SHOULD use the `x-` prefix. |
| `corrects` | string | no | `production[].id` of the entry this one retracts. |
| `sequence` | integer | no | Tie-breaker for identical instants, lower first. |
| `notes` | string | no | |
An entry of a known `type` that lacks its required fields is rejected. Entries of unknown (`x-`) types are preserved with their extra members and are excluded from volume derivations.
### `meter_reading`
| Field | Type | Required | Unit | Description |
| ---------- | ---------------- | -------- | ---- | ---------------------------------------------------------------- |
| `datetime` | string (instant) | yes | | RFC 3339 instant of the reading. |
| `meter_id` | string | yes | | `meters[].id`. |
| `reading` | number | yes | m³ | Register value. Meters reading in liters are converted on input. |
| `source` | string | no | | `manual`, `telemetry`, `x-…` |
A totalizer value is cumulative, so telemetry needs no aggregation field: storing fewer points loses no volume. The producing application decides the density written to the file.
### `estimated_volume`
A volume produced in a period, estimated without a meter (e.g. before a meter was installed, or while it was out of service).
| Field | Type | Required | Unit | Description |
| -------------- | ---------------- | -------- | ---- | ---------------------------------------------------------------------------------------- |
| `period_start` | string (instant) | yes | | Start of the declared period. |
| `period_end` | string (instant) | yes | | End of the declared period. SHOULD be later than `period_start`; otherwise a warning. |
| `volume` | number | yes | m³ | Volume produced in the period. |
| `method` | string | no | | Basis of the estimate: `flow_time`, `energy`, `user_statement`, `x-…`. Descriptive only. |
### Volume derivation (normative)
Corrected entries are ignored throughout. Readings of each meter are ordered by instant, then `sequence`.
1. For consecutive readings r₁, r₂ of the same meter, if r₂ ≥ r₁ the volume is r₂ − r₁.
2. If r₂ < r₁ and the meter has `max_reading`, the volume is (`max_reading` − r₁) + r₂ (rollover).
3. If r₂ < r₁ and there is no `max_reading`, the interval is unknown and a warning is emitted. It is never negative.
4. Volume is never computed across two different meters. When readings exist at the removal and installation instants, coverage is complete up to the removal and from the installation; the gap between them is unmetered and not counted. Without those readings, the volume between the two meters is unknown.
5. Production before a meter's first reading is unknown, not zero.
### Precedence
- An `estimated_volume` counts only for time not covered by meter readings, prorated linearly by the uncovered fraction of its period. Overlap emits a warning, and the meter wins. `method` plays no part in this rule.
- Volumes declared to a regulator (annual use declarations) are not production entries. They are the fulfillment of the permit's reporting condition (`permits[].conditions[].fulfillments`), with the declaration attached.
### Derived values
Volume per period (day, month, year), prorated linearly by time across period boundaries; average flow over a period; compliance against `permits[].volume_limits` and `monthly_schedule`. None of these are stored.
### Validation (warnings, never rejection)
- A reading outside its meter's installation window, or a `meter_id` that does not resolve.
- A decreasing reading on a meter without `max_reading` (unknown interval).
- An `estimated_volume` overlapping metered time.
- An `estimated_volume` whose `period_end` is not later than `period_start`.
- A `corrects` cycle, or a `corrects` reference that does not resolve.
---
## `operating_regime[]` _(since v2.3)_
A mutable record block: each entry declares how the well is intended to run from a given instant. Entries are edited in place, and `updated_at` records the last edit. A change of regime is a new entry; the entry with the latest `effective_from` not after a given instant is the regime in force. There is no end field.
### `OperatingRegime`
| Field | Type | Required | Unit | Description |
| ---------------------- | ---------------- | -------- | ---- | ------------------------------------------------------- |
| `id` | string | yes | | Unique within `operating_regime`. |
| `effective_from` | string (instant) | yes | | When the regime takes effect. Duplicates are a warning. |
| `flow_rate` | number | no | m³/h | |
| `daily_operating_time` | number | no | h | Above 24 is read as 24, with a warning. |
| `days_per_week` | integer | no | | 1–7. |
| `notes` | string | no | | |
| `updated_at` | string (instant) | no | | Last edit of this record. |
- An absent field means unknown, never zero.
- A stopped well is recorded with a `history_logs` `status_change`, not a regime with `flow_rate: 0`.
- A `daily_operating_time` above 24 is stored as written, read as 24 by every derivation, and emits a warning. A negative `daily_operating_time` is a schema error, as is a `days_per_week` outside 1–7 (or not an integer).
### Validation (warnings, never rejection)
- A `daily_operating_time` above 24.
- Two entries with the same `effective_from`.
- Regime values above those of the permit active at `effective_from` (`flow_rate`, `daily_operating_time`).
---
## `water_samples[]` _(since v2.3)_
A ledger of water samples, each with its field and laboratory results. The full schema — `WaterSample`, `SamplingPoint`, `Purge`, `PurgeReading`, `Laboratory`, `Result`, `Parameter`, `Filtration`, `Validation`, result rules, validation and derived values — is specified in [water-quality.md](water-quality.md).
---
## Complete Example
```json
{
"$schema": "https://welldot.org/schema/v2/well.schema.json",
"@context": "https://welldot.org/context/v2.jsonld",
"version": "2.4",
"well_type": "tubular",
"well_purpose": ["production"],
"name": "Poço PP-01",
"well_driller": "Perfuradora XYZ",
"construction_date": "2006-03-10",
"obs": "Sem anomalias observadas durante a perfuração.",
"well_id": [
{ "authority": "SIAGAS", "id": "SP-0042819", "primary": true },
{ "authority": "ANA", "id": "02000.001234/2006-78" }
],
"location": {
"lat": -1.4558,
"lng": -48.5039,
"elevation": 12.5,
"properties": {
"timezone": "America/Belem",
"elevation_datum": "wgs84_ellipsoid",
"crs": "EPSG:4326",
"elevation_precision": 0.5
}
},
"profiles": ["https://welldot.org/profiles/brazil-ana/v1/schema.json"],
"bore_hole": [
{ "from": 0, "to": 80, "diameter": 250, "drilling_method": "rotary" }
],
"well_case": [
{ "from": 0, "to": 60, "type": "carbon_steel", "diameter": 200 }
],
"reduction": [],
"well_screen": [
{
"from": 60,
"to": 80,
"type": "stainless_steel",
"diameter": 150,
"screen_slot": 0.5,
"slot_type": "continuous_slot"
}
],
"surface_case": [{ "from": 0, "to": 3, "diameter": 300 }],
"hole_fill": [
{
"from": 60,
"to": 80,
"type": "gravel_pack",
"diameter": 250,
"description": "Seixo 2-4mm"
},
{
"from": 3,
"to": 60,
"type": "seal",
"diameter": 250,
"description": "Cimento"
}
],
"centralizers": [
{
"from": 6,
"to": 54,
"spacing": 12,
"type": "spring_bow",
"diameter": 245
}
],
"cement_pad": {
"type": "concrete",
"width": 1.0,
"thickness": 0.15,
"length": 1.0
},
"lithology": [
{
"from": 0,
"to": 20,
"description": "Areia fina amarelada",
"color": "#f5deb3",
"texture": { "code": 607, "vocabulary": "fgdc" },
"geologic_unit": "Quaternário",
"aquifer_unit": "freático"
},
{
"from": 20,
"to": 80,
"description": "Granito fraturado cinza",
"color": "#a9a9a9",
"texture": { "code": 718, "vocabulary": "fgdc" },
"geologic_unit": "Embasamento Cristalino",
"aquifer_unit": "fraturado"
}
],
"fractures": [
{
"depth": 45.2,
"depth_precision": 0.1,
"water_intake": true,
"description": "Fratura aberta",
"swarm": false,
"azimuth": 120,
"dip": 35
}
],
"caves": [],
"hydrodynamic_events": [
{
"id": "d4e5f6a7-b8c9-0123-defa-234567890123",
"type": "airlift",
"datetime": "2006-03-10T14:00:00-03:00",
"operator": "Perfuradora XYZ",
"steps": [
{
"rate": 340.0,
"duration": 30,
"readings": [
{ "elapsed": 30, "depth": 44.8, "depth_precision": 0.05 }
]
}
],
"notes": "Air-lift at end of drilling. Static level not reliable."
},
{
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"type": "constant_rate",
"datetime": "2006-03-14T08:00:00-03:00",
"operator": "Perfuradora XYZ",
"equipment": "Submersible pump 15 CV",
"static_level": 28.74,
"static_level_precision": 0.01,
"steps": [
{
"rate": 340.0,
"duration": 1440,
"readings": [
{ "elapsed": 1, "depth": 32.1 },
{ "elapsed": 5, "depth": 38.4 },
{ "elapsed": 30, "depth": 42.8 },
{ "elapsed": 60, "depth": 43.9 },
{ "elapsed": 1440, "depth": 44.8 }
]
}
],
"recovery": {
"readings": [
{ "elapsed": 5, "depth": 38.2 },
{ "elapsed": 15, "depth": 33.6 },
{ "elapsed": 30, "depth": 30.1 }
]
},
"notes": "Test terminated at 24h. Recovery monitored for 30 min."
},
{
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "spot_measurement",
"datetime": "2010-07-22T09:15:00-03:00",
"static_level": 31.2,
"measurement_method": "electric_probe",
"notes": "Routine annual monitoring."
},
{
"id": "e5f6a7b8-c9d0-1234-efab-567890123456",
"type": "spot_measurement",
"datetime": "2026-09-15T09:10:00-03:00",
"static_level": 33.4,
"measurement_method": "electric_probe",
"notes": "Level before water sampling."
}
],
"aquifer_analysis": [
{
"id": "f6a7b8c9-d0e1-2345-fabc-456789012345",
"datetime": "2006-03-15T16:00:00-03:00",
"analyst": "Dr. Maria Silva",
"source_event_ids": ["b2c3d4e5-f6a7-8901-bcde-f12345678901"],
"method": "cooper_jacob",
"static_level": 28.74,
"static_level_source_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"dynamic_level": 44.8,
"flow_rate": 340.0,
"specific_capacity": 21.17,
"transmissivity": 4.2e-4,
"notes": "Cooper-Jacob straight-line fit, t=10min to t=1440min. S not determinable — no observation well."
}
],
"attachments": [
{
"id": "c1d2e3f4-a5b6-7890-cdef-123456789abc",
"uri": "https://files.wellmanager.example.com/wells/pp-01/relatorio-perfuracao.pdf",
"media_type": "application/pdf",
"document_type": "drilling_report",
"filename": "relatorio-perfuracao.pdf"
}
],
"pump_installations": [
{
"id": "e1f2a3b4-c5d6-7890-efab-234567890abc",
"installed_at": "2006-03-20T10:00:00-03:00",
"removed_at": "2018-06-12T09:30:00-03:00",
"installed_by": "Perfurações Silva Ltda.",
"removed_by": "Bombas Norte Serviços",
"type": "submersible",
"power_source": "grid",
"rated_power": 11.0,
"intake_depth": 52
},
{
"id": "f2a3b4c5-d6e7-8901-fabc-345678901bcd",
"installed_at": "2018-06-12T15:00:00-03:00",
"updated_at": "2024-03-15T11:42:00-03:00",
"type": "submersible",
"power_source": "grid",
"manufacturer": "Schneider",
"model": "ME-25",
"intake_depth": 54,
"rated_power": 7.36,
"riser_diameter": 114.3,
"riser_material": "galvanized_steel",
"check_valve": true,
"electrical": {
"voltage": 380,
"phases": 3,
"cable_section": 10,
"cable_length": 70
},
"attachments": [
{
"id": "b8c9d0e1-f2a3-4567-bcde-678901234567",
"uri": "https://files.wellmanager.example.com/wells/pp-01/attachments/nota-fiscal-bomba-2018.pdf",
"media_type": "application/pdf",
"document_type": "invoice",
"filename": "nota-fiscal-bomba-2018.pdf",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
]
}
],
"permits": [
{
"id": "9a8b7c6d-5e4f-3210-abcd-0123456789ab",
"type": "abstraction_permit",
"authority": "SEMAS-PA",
"identifier": "1234/2025",
"request_identifier": "2024/0099-PRT",
"status": "granted",
"issued_at": "2025-02-10",
"valid_until": "2029-02-10",
"water_use": ["human_supply"],
"flow_rate": 340.0,
"daily_operating_time": 20,
"volume_limits": [{ "period": "annual", "volume": 2482000 }],
"conditions": [
{
"id": "c1",
"description": "Instalar hidrômetro na saída do poço",
"category": "equipment_installation",
"due_after": "P90D",
"responsible": "Equipe de operação",
"fulfillments": [
{
"id": "e2f3a4b5-c6d7-8901-efab-23456789abcd",
"datetime": "2025-04-02T10:00:00-03:00",
"due_date": "2025-05-11",
"description": "Hidrômetro instalado e comunicado ao órgão.",
"author": "Equipe de operação"
}
]
},
{
"id": "c2",
"description": "Relatório semestral de nível e vazão",
"category": "reporting",
"first_due": "2025-07-31",
"recurrence": "P6M"
},
{
"id": "c3",
"description": "Análise anual de qualidade da água",
"category": "monitoring",
"first_due": "2026-09-30",
"recurrence": "P1Y",
"fulfillments": [
{
"id": "b5c6d7e8-f9a0-1234-bcde-56789abcdef0",
"datetime": "2026-09-22T16:00:00-03:00",
"due_date": "2026-09-30",
"description": "Laudo LD-4471/26 protocolado no órgão gestor.",
"sample_id": "ws-2026-09-a"
}
]
}
],
"history": [
{
"id": "h-01",
"date": "2024-11-04",
"type": "filing",
"description": "Requerimento de outorga protocolado."
},
{
"id": "h-02",
"date": "2024-12-02",
"type": "fee",
"description": "Taxa de análise do requerimento.",
"due_date": "2024-12-20",
"done": true
},
{
"id": "h-03",
"date": "2025-02-10",
"type": "decision",
"description": "Portaria 1234/2025 publicada."
}
],
"attachments": [
{
"id": "d4e5f6a7-b8c9-0123-defa-456789abcdef",
"uri": "https://files.wellmanager.example.com/wells/pp-01/portaria-1234-2025.pdf",
"media_type": "application/pdf",
"document_type": "permit_document"
}
]
}
],
"meters": [
{
"id": "7c8d9e0f-a1b2-4c3d-8e4f-5a6b7c8d9e0f",
"installed_at": "2025-04-02T09:00:00-03:00",
"installed_by": "Hidro Serviços",
"type": "electromagnetic",
"manufacturer": "Incontrol",
"model": "VMS Pro",
"serial": "EM150-55821",
"nominal_diameter": 150,
"max_reading": 99999999
}
],
"production": [
{
"id": "p-0001",
"type": "meter_reading",
"datetime": "2025-04-02T09:00:00-03:00",
"meter_id": "7c8d9e0f-a1b2-4c3d-8e4f-5a6b7c8d9e0f",
"reading": 0,
"source": "manual"
},
{
"id": "p-0002",
"type": "meter_reading",
"datetime": "2025-12-31T17:00:00-03:00",
"meter_id": "7c8d9e0f-a1b2-4c3d-8e4f-5a6b7c8d9e0f",
"reading": 1452300,
"source": "telemetry"
},
{
"id": "p-0003",
"type": "estimated_volume",
"period_start": "2025-01-01T00:00:00-03:00",
"period_end": "2025-04-02T09:00:00-03:00",
"volume": 436800,
"method": "flow_time",
"notes": "Regime r-0001 (300 m³/h × 16 h/dia × 91 dias), antes do hidrômetro."
},
{
"id": "p-0004",
"type": "meter_reading",
"datetime": "2026-06-30T17:00:00-03:00",
"meter_id": "7c8d9e0f-a1b2-4c3d-8e4f-5a6b7c8d9e0f",
"reading": 2541800,
"source": "manual"
},
{
"id": "p-0005",
"type": "meter_reading",
"datetime": "2026-06-30T17:00:00-03:00",
"meter_id": "7c8d9e0f-a1b2-4c3d-8e4f-5a6b7c8d9e0f",
"reading": 2451800,
"source": "manual",
"corrects": "p-0004",
"notes": "Corrige erro de digitação na leitura p-0004."
}
],
"operating_regime": [
{
"id": "r-0001",
"effective_from": "2018-06-12T15:00:00-03:00",
"flow_rate": 300,
"daily_operating_time": 16,
"days_per_week": 7
},
{
"id": "r-0002",
"effective_from": "2025-04-02T09:00:00-03:00",
"flow_rate": 320,
"daily_operating_time": 18,
"days_per_week": 7,
"updated_at": "2025-04-02T12:00:00-03:00"
}
],
"water_samples": [
{
"id": "ws-2026-09-a",
"datetime": "2026-09-15T09:30:00-03:00",
"sample_type": "routine",
"campaign": "2026-Q3",
"sampling_method": "low_flow",
"sampling_point": {
"type": "in_well",
"depth": 65,
"device": "low_flow_pump"
},
"purge": {
"duration": 35,
"volume": 0.0105,
"flow_rate": 0.018,
"stabilized": true,
"readings": [
{
"elapsed": 25,
"parameter": { "code": "ph", "vocabulary": "welldot" },
"value": 4.9
},
{
"elapsed": 30,
"parameter": { "code": "ph", "vocabulary": "welldot" },
"value": 4.8
},
{
"elapsed": 35,
"parameter": { "code": "ph", "vocabulary": "welldot" },
"value": 4.8
}
]
},
"static_level_event_id": "e5f6a7b8-c9d0-1234-efab-567890123456",
"collected_by": "Field team A",
"chain_of_custody": "CC-0918",
"laboratory": {
"name": "Lab X",
"accreditation": "ISO/IEC 17025 #1234",
"report_number": "LD-4471/26",
"batch_id": "WO-88213",
"sample_id": "88213-01",
"received_at": "2026-09-15T18:20:00-03:00",
"received_temperature": 4.1
},
"attachments": [
{
"id": "a1",
"uri": "https://files.wellmanager.example.com/wells/pp-01/LD-4471-26.pdf",
"media_type": "application/pdf",
"document_type": "lab_report"
}
],
"results": [
{
"parameter": { "code": "ph", "vocabulary": "welldot" },
"value": 4.8,
"measured_in": "field"
},
{
"parameter": {
"code": "specific_conductance",
"vocabulary": "welldot"
},
"value": 1240,
"measured_in": "field"
},
{
"parameter": { "code": "turbidity_fnu", "vocabulary": "welldot" },
"value": 3.1,
"measured_in": "field"
},
{
"parameter": { "code": "sulfate", "vocabulary": "welldot" },
"value": 412,
"method": "US EPA 300.0",
"measured_in": "lab",
"analyzed_at": "2026-09-17T00:00:00-03:00",
"analyzed_at_resolution": "day",
"validation": { "status": "validated", "validated_by": "QA reviewer" }
},
{
"parameter": { "code": "iron", "vocabulary": "welldot" },
"value": 18.6,
"fraction": "dissolved",
"filtration": { "pore_size": 0.45, "location": "field" },
"method": "US EPA 200.8",
"measured_in": "lab"
},
{
"parameter": { "code": "cyanide_wad_as_cn", "vocabulary": "welldot" },
"qualifier": "estimated",
"value": 0.004,
"detection_limit": 0.002,
"quantification_limit": 0.005,
"measured_in": "lab",
"lab_flags": ["J"],
"validation": { "status": "qualified", "qualifier": "J" }
},
{
"parameter": { "code": "71-43-2", "vocabulary": "cas" },
"qualifier": "<",
"value": 0.001,
"measured_in": "lab"
},
{
"parameter": { "code": "e_coli", "vocabulary": "welldot" },
"presence": false,
"measured_in": "lab",
"analyzed_at": "2026-09-15T19:10:00-03:00"
}
]
},
{
"id": "ws-2026-09-b",
"datetime": "2026-09-15T09:35:00-03:00",
"sample_type": "field_duplicate",
"parent_sample_id": "ws-2026-09-a",
"campaign": "2026-Q3",
"sampling_point": {
"type": "in_well",
"depth": 65,
"device": "low_flow_pump"
},
"results": [
{
"parameter": { "code": "sulfate", "vocabulary": "welldot" },
"value": 398,
"measured_in": "lab"
}
]
}
],
"history_logs": [
{
"id": "d0e1f2a3-b4c5-6789-defa-890123456789",
"datetime": "2006-03-10T00:00:00-03:00",
"updated_at": "2006-03-10T00:00:00-03:00",
"category": "event",
"description": "Well construction completed. Commissioned for public supply by COSANPA. Initial flow rate 340 m³/h via air-lift.",
"author": "Prefeitura Municipal de Belém"
},
{
"id": "c3d4e5f6-a7b8-9012-cdef-3456789abcde",
"datetime": "2006-03-20T10:00:00-03:00",
"category": "status_change",
"description": "Poço entra em operação após instalação da bomba.",
"status": "active"
},
{
"id": "a7b8c9d0-e1f2-3456-abcd-567890123456",
"datetime": "2018-06-12T09:30:00-03:00",
"updated_at": "2024-03-15T11:42:00-03:00",
"category": "maintenance",
"maintenance_type": "pump_service",
"pump_installation_id": "f2a3b4c5-d6e7-8901-fabc-345678901bcd",
"description": "Original 15 CV submersible pump replaced after motor burnout. New pump: Schneider ME-25 10 CV. (Description corrected 2024-03-15: model was ME-25, not ME-22 as originally logged.)",
"author": "Manutenção Rápida Ltda.",
"severity": "high",
"attachments": [
{
"id": "b8c9d0e1-f2a3-4567-bcde-678901234567",
"uri": "https://files.wellmanager.example.com/wells/pp-01/attachments/nota-fiscal-bomba-2018.pdf",
"media_type": "application/pdf",
"document_type": "invoice",
"filename": "nota-fiscal-bomba-2018.pdf",
"description": "Purchase invoice for replacement pump",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
}
]
},
{
"id": "f3a4b5c6-d7e8-9012-fabc-3456789abcde",
"datetime": "2026-04-10T14:00:00-03:00",
"category": "maintenance",
"maintenance_type": "meter_calibration",
"meter_id": "7c8d9e0f-a1b2-4c3d-8e4f-5a6b7c8d9e0f",
"description": "Aferição do medidor eletromagnético; erro dentro da tolerância.",
"author": "Metrologia Norte Ltda."
},
{
"id": "a4b5c6d7-e8f9-0123-abcd-456789abcdef",
"datetime": "2026-09-15T09:30:00-03:00",
"category": "maintenance",
"maintenance_type": "water_sampling",
"sample_ids": ["ws-2026-09-a", "ws-2026-09-b"],
"hydrodynamic_event_ids": ["e5f6a7b8-c9d0-1234-efab-567890123456"],
"description": "Coleta de baixa vazão com duplicata de campo; nível medido antes da coleta.",
"author": "Field team A"
}
]
}
```
---
_`.well` Format Specification v2.4_
# `.well` File Format Specification — Version 2.4: Water Quality
**See also:** [overview.md](./overview.md) · [format-reference.md](./format-reference.md) · [object-schemas.md](./object-schemas.md) · [interoperability.md](./interoperability.md)
## Overview
This document specifies the `water_samples` block _(since v2.3)_: the sample and result schemas, the recommended values, the unit rules, the `welldot` parameter vocabulary and how jurisdiction requirements are expressed. Cross-cutting rules that also cover water samples stay in their general sections: datetimes and the `_resolution: "day"` convention, canonical units and field bindings, cross-references, uniqueness and precision in [format-reference.md](format-reference.md); attachments, ledger corrections and `history_logs` links in [object-schemas.md](object-schemas.md). The Complete Example in object-schemas.md includes water samples.
## Table of contents
- [`water_samples[]`](#water_samples)
- [`WaterSample`](#watersample)
- [`SamplingPoint`](#samplingpoint)
- [`Purge`](#purge)
- [`PurgeReading`](#purgereading)
- [`Laboratory`](#laboratory)
- [`Result`](#result)
- [`Parameter`](#parameter)
- [`Filtration`](#filtration)
- [`Validation`](#validation)
- [Result rules](#result-rules)
- [Malformed (rejected)](#malformed-rejected)
- [Validation (warnings, never rejection)](#validation-warnings-never-rejection)
- [Derived values](#derived-values)
- [History log links](#history-log-links)
- [Recommended values](#recommended-values)
- [`water_samples[].sample_type` — Recommended values _(since v2.3)_](#water_samplessample_type--recommended-values-since-v23)
- [`water_samples[].sampling_method` — Recommended values _(since v2.3)_](#water_samplessampling_method--recommended-values-since-v23)
- [`water_samples[].sampling_point.type` — Recommended values _(since v2.3)_](#water_samplessampling_pointtype--recommended-values-since-v23)
- [`water_samples[].sampling_point.device` — Recommended values _(since v2.3)_](#water_samplessampling_pointdevice--recommended-values-since-v23)
- [Units](#units)
- [Parameter vocabulary](#parameter-vocabulary)
- [Vocabulary rules](#vocabulary-rules)
- [Core vocabulary](#core-vocabulary)
- [Limit sets are not part of the file](#limit-sets-are-not-part-of-the-file)
- [Regulatory profiles](#regulatory-profiles)
---
## `water_samples[]`
A ledger: each entry is one collection of water from the well, with the results measured on it in the field and in the laboratory. Entries MAY be edited in place, but editing is NOT RECOMMENDED after the entry has been exported. A revalidation of data already recorded is an edit of `Result.validation` in place. A corrected laboratory report is best written as a new sample whose `corrects` holds the id of the retracted sample, following object-schemas.md § `hydrodynamic_events[]` — Corrections (retracted samples stay in the file and are excluded from every derivation; chains are allowed; a cycle emits a warning).
The block serves drinking-water compliance, environmental and mining monitoring under licence, and hydrochemistry, in any jurisdiction. It follows the rigor of audited monitoring (chain of custody, field QA/QC, filtration, lab flags, data validation) with almost every field optional. It is jurisdiction-neutral: limits, exceedances and national rules are never stored; see § Parameter vocabulary — Limit sets are not part of the file.
Out of scope: limits and exceedances, sampling schedules, high-frequency telemetry (only aggregates enter), interpreted assessment (class, facies), and the laboratory's internal QC (method blanks, matrix spikes).
### `WaterSample`
| Field | Type | Required | Unit | Description |
| ----------------------- | ---------------- | ----------- | ---- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | yes | | Unique within `water_samples`. UUID v4 recommended. |
| `datetime` | string (instant) | yes | | RFC 3339 instant of collection. |
| `sample_type` | string | yes | | `routine`, `field_duplicate`, `split_sample`, `field_blank`, `trip_blank`, `equipment_blank`, `x-…`. See format-reference.md § `water_samples[].sample_type`. |
| `parent_sample_id` | string | conditional | | `water_samples[].id` of the original sample. Expected for `field_duplicate` and `split_sample` (absence emits a warning). |
| `sequence` | integer | no | | Tie-breaker among samples at the same instant, lower first. Orders the samples of a vertical profile. |
| `campaign` | string | no | | Free identifier of the sampling campaign. |
| `sampling_method` | string | no | | How the well was purged: `low_flow`, `volumetric_purge`, `no_purge`, `pump_discharge`, `x-…`. |
| `sampling_point` | `SamplingPoint` | no | | Where the water was taken. See below. |
| `purge` | `Purge` | no | | Purge before collection. See below. |
| `static_level_event_id` | string | no | | `hydrodynamic_events[].id` with the water level measured at collection. |
| `collected_by` | string | no | | Person or team that collected the sample. |
| `preservation` | string | no | | Preservation and packaging, free text. |
| `chain_of_custody` | string | no | | Chain of custody number. |
| `laboratory` | `Laboratory` | no | | See below. |
| `corrects` | string | no | | `water_samples[].id` of the sample this one retracts. |
| `notes` | string | no | | |
| `attachments` | `Attachment[]` | no | | The laboratory report with `document_type: "lab_report"`. See § Attachment. |
| `results` | `Result[]` | yes | | At least one. Field and laboratory results in one list, distinguished by `measured_in`. |
### `SamplingPoint`
| Field | Type | Required | Unit | Description |
| ---------------------- | ------ | -------- | ---- | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | yes | | `pump_discharge`, `wellhead_tap`, `in_well`, `x-…`. See format-reference.md § `water_samples[].sampling_point.type`. |
| `depth` | number | no | m | Point depth of the sampler intake, from ground level. Mutually exclusive with `from`/`to`. |
| `depth_precision` | number | no | m | One-sigma precision of `depth`. |
| `from` | number | no | m | Top of the isolated interval. |
| `to` | number | no | m | Bottom of the isolated interval. |
| `device` | string | no | | What collected the water: `bailer`, `discrete_depth_sampler`, `passive_diffusion_bag`, `grab_sleeve`, `low_flow_pump`, `packer_pump`, `x-…`. |
| `pump_installation_id` | string | no | | `pump_installations[].id`, when the sample is taken at the production pump. |
A sample's depth is a point, an interval, derived from the production pump, or unknown. All depths are measured from ground level.
| Situation | `type` | Geometry | Example |
| ----------------- | ---------------- | ------------------------------------------------------------- | ---------------------------------- |
| Production pump | `pump_discharge` | `pump_installation_id`; depth derived from its `intake_depth` | Supply well |
| Wellhead tap | `wellhead_tap` | None | Routine potability |
| Point sampler | `in_well` | `depth` | Bailer, HydraSleeve, diffusion bag |
| Low-flow | `in_well` | `depth` of the sampling pump intake | ASTM D6771, ISO 5667-11 |
| Isolated interval | `in_well` | `from` and `to` | Packer, multilevel well |
| Unknown | `in_well` | None | Historical record without depth |
- `depth` together with `from` or `to` is malformed.
- With `pump_installation_id`, the depth is not repeated on the sampling point.
- Field measurements taken from the top of casing are converted to ground level by the producer, by the same rule as water levels.
- A vertical profile or a multilevel well is several samples at different depths, with the same `campaign` and `sequence` for ordering.
### `Purge`
All fields optional.
| Field | Type | Unit | Description |
| ------------ | ---------------- | ---- | --------------------------------------------------- |
| `duration` | number | min | Purge duration. |
| `volume` | number | m³ | Purged volume. |
| `flow_rate` | number | m³/h | Purge flow rate. |
| `stabilized` | boolean | | Field parameters were stabilized before collection. |
| `readings` | `PurgeReading[]` | | Stabilization readings taken during the purge. |
### `PurgeReading`
| Field | Type | Required | Unit | Description |
| ----------- | ----------- | -------- | --------- | ---------------------------------------- |
| `elapsed` | number | yes | min | Time since the purge started. |
| `parameter` | `Parameter` | yes | | `{ code, vocabulary }`, as in `results`. |
| `value` | number | yes | canonical | Value in the parameter's canonical unit. |
`purge.readings` holds the stabilization series (pH, conductivity, temperature, dissolved oxygen, ORP, turbidity) that audits require. The final values at the time of collection go in `results` with `measured_in: "field"`.
### `Laboratory`
| Field | Type | Required | Unit | Description |
| ------------------------ | ---------------- | -------- | ---- | ----------------------------------------------------------------------------------------- |
| `name` | string | yes | | |
| `accreditation` | string | no | | ISO/IEC 17025 (or equivalent) accreditation identifier. |
| `report_number` | string | no | | Report number, preserved as issued. |
| `batch_id` | string | no | | Laboratory batch or work order. |
| `sample_id` | string | no | | The laboratory's own sample identifier. |
| `received_at` | string (instant) | no | | When the laboratory received the sample. |
| `received_at_resolution` | string | no | | `day` when only the date is known. See format-reference.md § Instants known only by date. |
| `received_temperature` | number | no | °C | Sample temperature on receipt. |
### `Result`
| Field | Type | Required | Unit | Description |
| ------------------------ | ---------------- | --------- | --------- | ----------------------------------------------------------------------------- |
| `parameter` | `Parameter` | yes | | What was measured. See below. |
| `value` | number | one form | canonical | Numeric result. |
| `presence` | boolean | one form | | Presence (`true`) or absence (`false`). |
| `text` | string | one form | | Qualitative result (odor, taste). |
| `qualifier` | string (enum) | no | | `<`, `>`, `not_detected`, `estimated`. Censoring and estimation only. |
| `unit` | string | `x-` only | | UCUM unit. Required for `x-` vocabularies, malformed for `welldot` and `cas`. |
| `detection_limit` | number | no | canonical | Detection limit (LD / LOD). |
| `quantification_limit` | number | no | canonical | Quantification limit (LQ / LOQ). |
| `value_precision` | number | no | canonical | One-sigma uncertainty. Expanded uncertainty from a report is divided by k. |
| `fraction` | string (enum) | no | | `total`, `dissolved`, `suspended`. |
| `filtration` | `Filtration` | no | | Filtration applied before analysis. Expected with `fraction: "dissolved"`. |
| `measured_in` | string (enum) | no | | `field` or `lab`. |
| `method` | string | no | | Analytical method, e.g. `ISO 7027`, `US EPA 200.8`, `SMEWW 4500-NO3 B`. |
| `analyzed_at` | string (instant) | no | | When the result was analyzed. |
| `analyzed_at_resolution` | string | no | | `day` when only the date is known. |
| `lab_flags` | string[] | no | | Laboratory flags as issued, uninterpreted. |
| `validation` | `Validation` | no | | Data validation by a reviewer. See below. |
| `notes` | string | no | | |
"Canonical" is the unit of the parameter in the vocabulary, or `unit` for `x-` codes. See § Units.
### `Parameter`
| Field | Type | Required | Description |
| ------------ | ------ | -------- | --------------------------------------------------------------------------------------------- |
| `code` | string | yes | Code in the vocabulary: a `welldot` code, a CAS Registry Number (`71-43-2`) or a custom code. |
| `vocabulary` | string | yes | `welldot`, `cas` or `x-…`. Any other value is malformed. |
The `welldot` codes, their units, value forms and CAS equivalences are listed in § Parameter vocabulary.
### `Filtration`
All fields optional.
| Field | Type | Unit | Description |
| ----------- | ------------- | ---- | ------------------------------ |
| `pore_size` | number | µm | Filter pore size, e.g. `0.45`. |
| `location` | string (enum) | | `field` or `lab`. |
### `Validation`
| Field | Type | Required | Description |
| -------------- | ---------------- | -------- | -------------------------------------------------------------- |
| `status` | string (enum) | yes | `unvalidated`, `validated`, `qualified`, `rejected`. |
| `qualifier` | string | no | Code of the validation guideline applied, e.g. `J`, `UJ`, `R`. |
| `guideline` | string | no | Guideline used, free text. |
| `validated_by` | string | no | |
| `validated_at` | string (instant) | no | |
An absent `validation` is equivalent to `status: "unvalidated"`.
### Result rules
Each result has exactly one value form. Three annotation layers are kept apart: the censoring qualifier, the laboratory flags and the reviewer's validation.
| Form | Field | Used for | `qualifier` allowed |
| ------------ | ---------- | ------------------------------------------------ | -------------------------------------- |
| Numeric | `value` | Almost every parameter | `<`, `>`, `estimated` |
| Presence | `presence` | Presence codes (`total_coliforms`, `e_coli`) | None |
| Text | `text` | Odor, taste and other qualitative results | None |
| Not detected | none | Below the detection limit with no reported value | `not_detected`, with `detection_limit` |
| Layer | Field | Set by | Example |
| ---------------- | ------------ | ----------------------------------- | --------------------------------------------- |
| Censoring | `qualifier` | Normalized on import | `<` 0.001 |
| Laboratory flags | `lab_flags` | The laboratory, preserved as issued | `B` (blank contamination), `H` (holding time) |
| Validation | `validation` | The data reviewer | `qualified` with `J` |
- `<` with `value` records the value the laboratory reported, usually the quantification or detection limit. It is neither zero nor absent.
- `>` covers upper bounds, such as counts above the method range.
- `estimated` marks values between the detection and quantification limits.
- `not_detected` without `detection_limit` emits a warning, because the result has no scale.
- `value_precision` is one sigma. Reports give an expanded uncertainty (usually k = 2); converters divide it by the coverage factor.
- `fraction: "dissolved"` is expected with `filtration`; without it, a warning is emitted. Dissolved metals filtered in the laboratory and in the field are not comparable in an audit.
- `lab_flags` has no vocabulary: codes vary by laboratory and country, and interpreting them is the job of validation.
- A `rejected` result is excluded from every derivation and from comparisons with limits. `qualified` results are kept.
- Revalidating the data of a recorded sample creates a new sample with `corrects`, like any ledger correction.
- The same parameter may appear twice in a sample when `fraction` or `measured_in` differ (total and dissolved iron; field and laboratory pH). An exact duplicate emits a warning.
- A `welldot` code and its CAS equivalent are the same parameter for duplicate detection and derivations.
### Malformed (rejected)
- `results` empty or absent.
- A result with more than one value form, or none without `qualifier: "not_detected"`; `not_detected` together with a value form; `<`, `>` or `estimated` without `value`.
- `unit` on a `welldot` or `cas` parameter; an `x-` parameter without `unit`; a `vocabulary` other than `welldot`, `cas` or `x-…`.
- `sampling_point.depth` together with `from` or `to`.
- An `_resolution` field with a value other than `day`; an instant without an offset.
- A value outside the closed enums `qualifier`, `fraction`, `measured_in`, `filtration.location`, `validation.status`. `sample_type`, `sampling_method`, `sampling_point.type` and `device` are open vocabularies.
### Validation (warnings, never rejection)
- `field_duplicate` or `split_sample` without `parent_sample_id`.
- `fraction: "dissolved"` without `filtration`.
- A sample depth outside every `well_screen` interval (stagnant casing water).
- A sample depth below `well_depth`.
- A sample depth above the static level of `static_level_event_id`.
- `presence` on a numeric code, or `value` on a presence code (value form different from the vocabulary's).
- `not_detected` without `detection_limit`.
- The same parameter, `fraction` and `measured_in` repeated in one sample.
- A `welldot` code unknown to the vocabulary version.
- `analyzed_at` or `laboratory.received_at` earlier than the collection `datetime`.
- A reference that does not resolve: `corrects`, `parent_sample_id`, `static_level_event_id`, `sampling_point.pump_installation_id`.
- A `corrects` cycle.
### Derived values
Derived values are computed by `@welldot/utils` and never stored. Retracted samples and `rejected` results are excluded from all of them.
- Exceedances against a selectable limit set (WHO, EU 2020/2184, Brazil, …) or the user's own limits. `<` and `not_detected` results never exceed; for presence limits, presence exceeds. `turbidity`, `turbidity_ntu` and `turbidity_fnu` compare with nephelometric limits, and `turbidity_fau` is never compared.
- Ion balance, converting mg/L to meq/L with the molar mass and charge of each code.
- Relative percent difference (RPD) between a duplicate or split and its original sample, per parameter.
- Blank contamination: parameters detected in `field_blank`, `trip_blank` or `equipment_blank` samples of the same `campaign`.
- Holding time, from `datetime` to `analyzed_at`, at the available resolution.
- Receipt temperature compliance.
- Purge stabilization from `purge.readings`.
- Acid drainage indicators (net alkalinity, sulfate/chloride ratio).
- Piper and Stiff coordinates and the hydrochemical facies.
- Effective sample depth (from `sampling_point`, or the pump's `intake_depth`) and whether the sample represents formation water (inside a screen interval and below the static level).
### History log links
A `history_logs` entry (typically `maintenance` with `maintenance_type: "water_sampling"`, or an `inspection`) records the task and points to the samples it collected with `sample_ids`. A water-quality `monitoring` permit condition is fulfilled by a condition fulfillment pointing to the sample the same way. See object-schemas.md § `history_logs[]` — Data links and § `permits[]` — Condition fulfillment.
---
## Recommended values
Non-canonical values of these open vocabularies SHOULD use the `x-` prefix.
### `water_samples[].sample_type` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) | Description |
| ----------------- | ------------------------ | ------------------------------------------------------------------------------------- |
| `routine` | Amostra de rotina | Regular sample of the well water. |
| `field_duplicate` | Duplicata de campo | Second sample collected at the same time and point. Requires `parent_sample_id`. |
| `split_sample` | Amostra dividida (split) | One sample split between laboratories (interlaboratory). Requires `parent_sample_id`. |
| `field_blank` | Branco de campo | Analyte-free water exposed to field conditions. |
| `trip_blank` | Branco de transporte | Analyte-free water that travels with the samples, unopened. |
| `equipment_blank` | Branco de equipamento | Analyte-free water passed through the sampling equipment after decontamination. |
Non-canonical values SHOULD use the `x-` prefix. Laboratory internal QC (method blanks, matrix spikes) is out of scope.
### `water_samples[].sampling_method` — Recommended values _(since v2.3)_
`sampling_method` describes the purge; `sampling_point.device` describes what collected the water. Portuguese terms follow ABNT NBR 15847 (métodos de purga).
| Value | Portuguese (BR) | Description |
| ------------------ | --------------------------------- | --------------------------------------------------------------------------------------- |
| `low_flow` | Purga de baixa vazão (micropurga) | Low-flow purging with stabilization of field parameters (e.g. ASTM D6771, ISO 5667-11). |
| `volumetric_purge` | Purga de volume determinado | Removal of a set number of well volumes before sampling. |
| `no_purge` | Amostragem sem purga | Passive or no-purge sampling. |
| `pump_discharge` | Bomba de produção em operação | Collected from the running production pump. |
Non-canonical values SHOULD use the `x-` prefix.
### `water_samples[].sampling_point.type` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) | Description |
| ---------------- | -------------------------- | --------------------------------------------------------------------------- |
| `pump_discharge` | Saída da bomba de produção | Production pump discharge; depth derived from `pump_installation_id`. |
| `wellhead_tap` | Torneira do cavalete | Tap at the wellhead; no depth. |
| `in_well` | Dentro do poço | Sampler inside the well, at `depth`, over `from`/`to`, or at unknown depth. |
Non-canonical values SHOULD use the `x-` prefix.
### `water_samples[].sampling_point.device` — Recommended values _(since v2.3)_
| Value | Portuguese (BR) |
| ------------------------ | ------------------------------------------ |
| `bailer` | Bailer (amostrador de retenção) |
| `discrete_depth_sampler` | Amostrador pontual de profundidade |
| `passive_diffusion_bag` | Bolsa de difusão passiva |
| `grab_sleeve` | Amostrador passivo tipo luva (HydraSleeve) |
| `low_flow_pump` | Bomba de baixa vazão |
| `packer_pump` | Bomba com obturador (packer) |
Non-canonical values SHOULD use the `x-` prefix.
---
## Units
A water quality result never declares a unit for a known parameter: the unit comes from the parameter code.
1. **Substances** (vocabulary `welldot` or `cas`) are always in mg/L. µg/L, ng/L and g/L are converted on import.
2. **Parameters that are not concentrations** (pH, conductivity, temperature, redox potential, turbidity, color, microbiology, radioactivity) have the unit fixed by their `welldot` code; see § Parameter vocabulary. pH is dimensionless.
3. **`x-` vocabulary codes** require `unit`, in UCUM. This is the only exception to the rule that the file never declares units. `unit` on a `welldot` or `cas` parameter is malformed, and an `x-` code without `unit` is malformed.
4. **Turbidity and microbiological counts** are separated by code, not by unit: `turbidity_ntu`, `turbidity_fnu`, `turbidity_fau` and the generic `turbidity` (FTU, formazin with no identified method); `_mpn` and `_cfu` count codes beside the presence codes.
Regional unit names are normalized on import:
| Reported unit | Normalized to |
| -------------------- | ------------------------------------------------- |
| µg/L, ng/L, g/L | mg/L |
| mS/m | µS/cm (×10) |
| mS/cm | µS/cm (×1000) |
| uT, FTU | generic `turbidity` code |
| NTU, FNU, FAU | `turbidity_ntu`, `turbidity_fnu`, `turbidity_fau` |
| mg Pt-Co/L, PCU, TCU | uH (numerically equal) |
The basis of expression lives in the code, never in the unit: nitrate reported "as N" and "as NO₃⁻" are different parameters (`nitrate_as_n`, `nitrate_as_no3`). `@welldot/core` provides `concentrationToCanonical`, `milliSiemensPerMeterToMicroSiemensPerCm`, `milliSiemensPerCmToMicroSiemensPerCm`, `turbidityCodeForUnit`, `normalizeColorUnit` and `expandedToStandardUncertainty` for these conversions.
---
## Parameter vocabulary
A parameter is identified by an object `{ code, vocabulary }`, like `texture`:
```json
{ "code": "nitrate_as_n", "vocabulary": "welldot" }
{ "code": "71-43-2", "vocabulary": "cas" }
{ "code": "glyphosate_ampa", "vocabulary": "x-lab-codes" }
```
`vocabulary` is `welldot`, `cas` or an `x-…` vocabulary; any other value is malformed.
### Vocabulary rules
1. **Substances** (`welldot` or `cas`) are always in mg/L. Conversions such as µg/L → mg/L happen on import.
2. **Parameters that are not concentrations** have their unit fixed by the `welldot` vocabulary (pH, conductivity, temperature, redox potential, turbidity, color, microbiology, radioactivity).
3. **`x-` codes** require `unit`, in UCUM. This is the only exception to the spec's unit rule.
4. **The basis of expression is part of the code.** `nitrate_as_n` and `nitrate_as_no3` are different parameters. A CAS code always means the substance itself. There is no `basis` field.
5. **Preference.** When a `welldot` code exists it is used instead of the CAS number. The vocabulary publishes the equivalences, and parsers treat a `welldot` code and its equivalent CAS number as the same parameter.
6. **Labels and translations.** The canonical label is English. Translations, regional synonyms and descriptions live in the published vocabulary, which is versioned (`WATER_QUALITY_VOCABULARY_VERSION` in `@welldot/core`).
7. **Regional unit names** are normalized on import: uT and FTU → `turbidity`; mg Pt-Co/L, PCU and TCU → uH; mS/m → µS/cm (×10). See § Units.
A `welldot` code unknown to the vocabulary version emits a warning, never a rejection. Any other substance is recorded by its CAS number, in mg/L.
### Core vocabulary
The core list has 98 `welldot` codes. The Mining and redox group and the additional metals cover acid drainage, gold leaching (cyanide) and explosives (nitrogen species). The Form column gives the value form a result of that code uses (`value`, `presence` or `text`); see § `water_samples[]` — Result rules.
| Group | Code | Label (EN) | Portuguese (BR) | Unit | Form | CAS |
| ---------------- | --------------------------------- | ----------------------------------------------------- | ------------------------------------------- | ---------- | -------- | ---------- |
| Physical / field | `temperature` | Temperature | Temperatura | °C | value | |
| Physical / field | `ph` | pH | pH | — | value | |
| Physical / field | `specific_conductance` | Specific conductance at 25 °C | Condutividade elétrica a 25 °C | µS/cm | value | |
| Physical / field | `conductivity_uncompensated` | Electrical conductivity, uncompensated | Condutividade sem compensação | µS/cm | value | |
| Physical / field | `dissolved_oxygen` | Dissolved oxygen | Oxigênio dissolvido | mg/L | value | |
| Physical / field | `orp` | Oxidation-reduction potential vs. reference electrode | Potencial redox vs. eletrodo de referência | mV | value | |
| Physical / field | `eh` | Redox potential vs. SHE | Potencial redox corrigido vs. EPH | mV | value | |
| Physical / field | `turbidity` | Turbidity, formazin, method unspecified | Turbidez, formazina sem método identificado | FTU | value | |
| Physical / field | `turbidity_ntu` | Turbidity, nephelometric white light | Turbidez nefelométrica, luz branca | NTU | value | |
| Physical / field | `turbidity_fnu` | Turbidity, nephelometric infrared (ISO 7027) | Turbidez nefelométrica, infravermelho | FNU | value | |
| Physical / field | `turbidity_fau` | Turbidity, attenuation (ISO 7027) | Turbidez por atenuação | FAU | value | |
| Physical / field | `apparent_color` | Apparent color | Cor aparente | uH | value | |
| Physical / field | `true_color` | True color | Cor verdadeira | uH | value | |
| Physical / field | `odor` | Odor | Odor | — | text | |
| Physical / field | `taste` | Taste | Gosto | — | text | |
| Physical / field | `total_dissolved_solids` | Total dissolved solids | Sólidos totais dissolvidos | mg/L | value | |
| Physical / field | `total_suspended_solids` | Total suspended solids | Sólidos suspensos totais | mg/L | value | |
| Physical / field | `total_solids` | Total solids | Sólidos totais | mg/L | value | |
| Physical / field | `free_co2` | Free carbon dioxide | Gás carbônico livre | mg/L | value | |
| Aggregate | `alkalinity_total_as_caco3` | Total alkalinity as CaCO₃ | Alcalinidade total como CaCO₃ | mg/L | value | |
| Aggregate | `alkalinity_bicarbonate_as_caco3` | Bicarbonate alkalinity as CaCO₃ | Alcalinidade de bicarbonatos como CaCO₃ | mg/L | value | |
| Aggregate | `alkalinity_carbonate_as_caco3` | Carbonate alkalinity as CaCO₃ | Alcalinidade de carbonatos como CaCO₃ | mg/L | value | |
| Aggregate | `alkalinity_hydroxide_as_caco3` | Hydroxide alkalinity as CaCO₃ | Alcalinidade de hidróxidos como CaCO₃ | mg/L | value | |
| Aggregate | `acidity_total_as_caco3` | Total acidity as CaCO₃ | Acidez total como CaCO₃ | mg/L | value | |
| Aggregate | `hardness_total_as_caco3` | Total hardness as CaCO₃ | Dureza total como CaCO₃ | mg/L | value | |
| Aggregate | `hardness_calcium_as_caco3` | Calcium hardness as CaCO₃ | Dureza de cálcio como CaCO₃ | mg/L | value | |
| Major ion | `calcium` | Calcium | Cálcio | mg/L | value | 7440-70-2 |
| Major ion | `magnesium` | Magnesium | Magnésio | mg/L | value | 7439-95-4 |
| Major ion | `sodium` | Sodium | Sódio | mg/L | value | 7440-23-5 |
| Major ion | `potassium` | Potassium | Potássio | mg/L | value | 7440-09-7 |
| Major ion | `bicarbonate` | Bicarbonate as HCO₃⁻ | Bicarbonato como HCO₃⁻ | mg/L | value | 71-52-3 |
| Major ion | `carbonate` | Carbonate as CO₃²⁻ | Carbonato como CO₃²⁻ | mg/L | value | 3812-32-6 |
| Major ion | `chloride` | Chloride | Cloreto | mg/L | value | 16887-00-6 |
| Major ion | `sulfate` | Sulfate | Sulfato | mg/L | value | 14808-79-8 |
| Major ion | `fluoride` | Fluoride | Fluoreto | mg/L | value | 16984-48-8 |
| Major ion | `silica_as_sio2` | Silica as SiO₂ | Sílica como SiO₂ | mg/L | value | 7631-86-9 |
| Nutrient | `nitrate_as_n` | Nitrate as N | Nitrato como N | mg/L | value | |
| Nutrient | `nitrate_as_no3` | Nitrate as NO₃⁻ | Nitrato como NO₃⁻ | mg/L | value | 14797-55-8 |
| Nutrient | `nitrite_as_n` | Nitrite as N | Nitrito como N | mg/L | value | |
| Nutrient | `nitrite_as_no2` | Nitrite as NO₂⁻ | Nitrito como NO₂⁻ | mg/L | value | 14797-65-0 |
| Nutrient | `ammonia_as_n` | Ammonia nitrogen as N | Nitrogênio amoniacal como N | mg/L | value | |
| Nutrient | `ammonia_as_nh3` | Ammonia as NH₃ | Amônia como NH₃ | mg/L | value | 7664-41-7 |
| Nutrient | `kjeldahl_nitrogen_as_n` | Total Kjeldahl nitrogen as N | Nitrogênio Kjeldahl total como N | mg/L | value | |
| Nutrient | `phosphorus_total_as_p` | Total phosphorus as P | Fósforo total como P | mg/L | value | |
| Nutrient | `orthophosphate_as_po4` | Orthophosphate as PO₄³⁻ | Ortofosfato como PO₄³⁻ | mg/L | value | |
| Organic | `total_organic_carbon` | Total organic carbon | Carbono orgânico total | mg/L | value | |
| Organic | `cod` | Chemical oxygen demand | Demanda química de oxigênio (DQO) | mg/L | value | |
| Organic | `bod5` | Biochemical oxygen demand, 5-day | Demanda bioquímica de oxigênio (DBO₅) | mg/L | value | |
| Organic | `total_petroleum_hydrocarbons` | Total petroleum hydrocarbons (range per method) | Hidrocarbonetos totais de petróleo | mg/L | value | |
| Organic | `oil_and_grease` | Oil and grease | Óleos e graxas | mg/L | value | |
| Disinfection | `free_chlorine` | Free chlorine residual | Cloro residual livre | mg/L | value | |
| Disinfection | `total_chlorine` | Total chlorine residual | Cloro residual total | mg/L | value | |
| Mining and redox | `ferrous_iron` | Ferrous iron (Fe²⁺) | Ferro ferroso (Fe²⁺) | mg/L | value | |
| Mining and redox | `chromium_hexavalent` | Hexavalent chromium | Cromo hexavalente | mg/L | value | 18540-29-9 |
| Mining and redox | `cyanide_total_as_cn` | Total cyanide as CN⁻ | Cianeto total como CN⁻ | mg/L | value | |
| Mining and redox | `cyanide_wad_as_cn` | Weak acid dissociable cyanide as CN⁻ | Cianeto WAD como CN⁻ | mg/L | value | |
| Mining and redox | `cyanide_free_as_cn` | Free cyanide as CN⁻ | Cianeto livre como CN⁻ | mg/L | value | |
| Mining and redox | `thiocyanate` | Thiocyanate | Tiocianato | mg/L | value | 302-04-5 |
| Mining and redox | `sulfide_total_as_s` | Total sulfide as S | Sulfeto total como S | mg/L | value | |
| Metal / trace | `iron` | Iron | Ferro | mg/L | value | 7439-89-6 |
| Metal / trace | `manganese` | Manganese | Manganês | mg/L | value | 7439-96-5 |
| Metal / trace | `aluminum` | Aluminium | Alumínio | mg/L | value | 7429-90-5 |
| Metal / trace | `antimony` | Antimony | Antimônio | mg/L | value | 7440-36-0 |
| Metal / trace | `arsenic` | Arsenic | Arsênio | mg/L | value | 7440-38-2 |
| Metal / trace | `barium` | Barium | Bário | mg/L | value | 7440-39-3 |
| Metal / trace | `beryllium` | Beryllium | Berílio | mg/L | value | 7440-41-7 |
| Metal / trace | `boron` | Boron | Boro | mg/L | value | 7440-42-8 |
| Metal / trace | `cadmium` | Cadmium | Cádmio | mg/L | value | 7440-43-9 |
| Metal / trace | `chromium` | Chromium, total | Cromo total | mg/L | value | 7440-47-3 |
| Metal / trace | `cobalt` | Cobalt | Cobalto | mg/L | value | 7440-48-4 |
| Metal / trace | `copper` | Copper | Cobre | mg/L | value | 7440-50-8 |
| Metal / trace | `lead` | Lead | Chumbo | mg/L | value | 7439-92-1 |
| Metal / trace | `lithium` | Lithium | Lítio | mg/L | value | 7439-93-2 |
| Metal / trace | `mercury` | Mercury | Mercúrio | mg/L | value | 7439-97-6 |
| Metal / trace | `molybdenum` | Molybdenum | Molibdênio | mg/L | value | 7439-98-7 |
| Metal / trace | `nickel` | Nickel | Níquel | mg/L | value | 7440-02-0 |
| Metal / trace | `selenium` | Selenium | Selênio | mg/L | value | 7782-49-2 |
| Metal / trace | `silver` | Silver | Prata | mg/L | value | 7440-22-4 |
| Metal / trace | `strontium` | Strontium | Estrôncio | mg/L | value | 7440-24-6 |
| Metal / trace | `thallium` | Thallium | Tálio | mg/L | value | 7440-28-0 |
| Metal / trace | `uranium` | Uranium | Urânio | mg/L | value | 7440-61-1 |
| Metal / trace | `vanadium` | Vanadium | Vanádio | mg/L | value | 7440-62-2 |
| Metal / trace | `zinc` | Zinc | Zinco | mg/L | value | 7440-66-6 |
| Microbiology | `total_coliforms` | Total coliforms in 100 mL | Coliformes totais em 100 mL | — | presence | |
| Microbiology | `total_coliforms_mpn` | Total coliforms, count | Coliformes totais, contagem | MPN/100 mL | value | |
| Microbiology | `total_coliforms_cfu` | Total coliforms, count | Coliformes totais, contagem | CFU/100 mL | value | |
| Microbiology | `e_coli` | _E. coli_ in 100 mL | _E. coli_ em 100 mL | — | presence | |
| Microbiology | `e_coli_mpn` | _E. coli_, count | _E. coli_, contagem | MPN/100 mL | value | |
| Microbiology | `e_coli_cfu` | _E. coli_, count | _E. coli_, contagem | CFU/100 mL | value | |
| Microbiology | `thermotolerant_coliforms` | Thermotolerant coliforms in 100 mL | Coliformes termotolerantes em 100 mL | — | presence | |
| Microbiology | `thermotolerant_coliforms_mpn` | Thermotolerant coliforms, count | Coliformes termotolerantes, contagem | MPN/100 mL | value | |
| Microbiology | `thermotolerant_coliforms_cfu` | Thermotolerant coliforms, count | Coliformes termotolerantes, contagem | CFU/100 mL | value | |
| Microbiology | `heterotrophic_plate_count` | Heterotrophic plate count | Bactérias heterotróficas | CFU/mL | value | |
| Radioactivity | `gross_alpha` | Gross alpha activity | Atividade alfa total | Bq/L | value | |
| Radioactivity | `gross_beta` | Gross beta activity | Atividade beta total | Bq/L | value | |
| Radioactivity | `radium_226` | Radium-226 | Rádio-226 | Bq/L | value | 13982-63-3 |
| Radioactivity | `radium_228` | Radium-228 | Rádio-228 | Bq/L | value | 15262-20-1 |
| Radioactivity | `radon_222` | Radon-222 | Radônio-222 | Bq/L | value | 14859-67-7 |
The CAS numbers listed are the published equivalences: a result coded by that CAS number is treated as the corresponding `welldot` code. Codes whose basis differs from the substance (such as `nitrate_as_n`) or that measure an analytical fraction (such as `cyanide_wad_as_cn`) have no CAS equivalent. Radionuclides are in Bq/L even though they have CAS numbers, an exception to the mg/L rule; uranium by mass stays under `uranium` (mg/L).
### Limit sets are not part of the file
Limits, guideline values and exceedances are never written to a `.well` file, and no field names a national standard. `@welldot/core` bundles selectable limit sets as separate data (WHO Guidelines for Drinking-water Quality, EU Directive 2020/2184, Brazil GM/MS 888/2021), and consumers compare results against the set the user picks, or against their own limits. In turbidity comparisons, `turbidity`, `turbidity_ntu` and `turbidity_fnu` compare with nephelometric limits; `turbidity_fau` is never compared. Local obligations (which parameters, how often) are expressed by profiles; see § Regulatory profiles.
---
## Regulatory profiles
Jurisdiction-specific water quality requirements live in profiles, not in the core format. A drinking-water profile for one jurisdiction might require, for wells with `well_purpose` `production`:
- at least one `water_samples` entry per calendar year with results for a fixed parameter list (e.g. `e_coli`, `total_coliforms`, `turbidity_ntu`, `free_chlorine`, `nitrate_as_n`, `fluoride`);
- monthly `e_coli` and `total_coliforms` results (presence form) and half-yearly results for the full list;
- `laboratory.accreditation` on every sample and an attachment with `document_type: "lab_report"`.
```json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://example.org/profiles/drinking-water/v1/schema.json",
"allOf": [{ "$ref": "https://welldot.org/schema/v2/well.schema.json" }],
"required": ["water_samples"],
"properties": {
"water_samples": {
"items": {
"required": ["laboratory", "attachments"],
"properties": {
"laboratory": { "required": ["name", "accreditation"] }
}
}
}
}
}
```
Frequency rules that JSON Schema cannot express (one sample per month, one per year) are checked by profile-aware tools and surfaced as warnings, like any profile failure. The limits that results are compared against still come from a limit set, never from the file.
# `.well` File Format Specification
**Version:** 1.0
**Extension:** `.well`
**Encoding:** UTF-8
**Base format:** JSON
## Overview
This is the specification of version 1 of the `.well` format: one JSON file that holds the static record of one water well, its identity, construction (borehole, casing, screens, fills, cement pad) and geology (lithology, fractures, caves). v1 is stable and kept for reference. New files use version 2, described in [../v2/overview.md](../v2/overview.md); `deserializeWell()` in `@welldot/core` reads v1 files and normalizes them to v2.
## Table of contents
- [Purpose](#purpose)
- [Design Principles](#design-principles)
- [File Structure](#file-structure)
- [Top-level Fields](#top-level-fields)
- [`well_type` — Recommended values](#well_type--recommended-values)
- [Object Schemas](#object-schemas)
- [`BoreHole` — `bore_hole[]`](#borehole--bore_hole)
- [`WellCase` — `well_case[]`](#wellcase--well_case)
- [`Reduction` — `reduction[]`](#reduction--reduction)
- [`WellScreen` — `well_screen[]`](#wellscreen--well_screen)
- [`SurfaceCase` — `surface_case[]`](#surfacecase--surface_case)
- [`HoleFill` — `hole_fill[]`](#holefill--hole_fill)
- [`CementPad` — `cement_pad`](#cementpad--cement_pad)
- [`Lithology` — `lithology[]`](#lithology--lithology)
- [`Fracture` — `fractures[]`](#fracture--fractures)
- [`Cave` — `caves[]`](#cave--caves)
- [Complete Example](#complete-example)
- [Versioning](#versioning)
- [MIME Type](#mime-type)
---
## Purpose
The `.well` format is an open standard for representing water well data. It is designed to serve three distinct but complementary use cases:
**Visualization.** The format contains sufficient constructive and geological detail to produce accurate technical well profile drawings — including borehole geometry, casing strings, screens, gravel packs, lithological columns, fractures, and caves — at true depth scale.
**Registration.** The format captures the administrative and physical identity of a well, providing a complete static record suitable for submission to regulatory bodies, inclusion in national well registries, and long-term archival.
**Research.** The structured geological and constructive data enables cross-well analysis, aquifer characterization, and hydrogeological research. Field vocabulary choices (FGDC texture codes, geologic units, aquifer units) are aligned with standards used in scientific literature. Dynamic hydrological measurements — static level, dynamic level, pump tests, water quality — are outside the scope of v1 and are reserved for a future `hydro` extension block.
---
## Design Principles
- **Self-describing** — full field names are used throughout; a `.well` file can be read and understood without consulting the spec
- **Versioned** — includes a `version` field for forward compatibility; parsers must reject unrecognized versions
- **Self-contained** — a single file encodes the complete static record of one well
- **SI units** — all depths are in meters, all diameters in millimeters; applications are responsible for unit conversion on display
- **Geographic north** — all azimuth values are referenced to geographic north
- **Ground level as zero** — all depth values are measured from ground level (0); elevation above sea level is stored separately in the `elevation` field
---
## File Structure
A `.well` file is a JSON object with the following top-level structure:
```json
{
"version": 1,
"well_type": "tubular",
"name": "Poço PP-01",
"well_driller": "Perfuradora XYZ",
"construction_date": "2024-03-15",
"lat": -1.4558,
"lng": -48.5039,
"elevation": 12.5,
"obs": "Observações livres",
"bore_hole": [ ... ],
"well_case": [ ... ],
"reduction": [ ... ],
"well_screen": [ ... ],
"surface_case": [ ... ],
"hole_fill": [ ... ],
"cement_pad": { ... },
"lithology": [ ... ],
"fractures": [ ... ],
"caves": [ ... ]
}
```
---
## Top-level Fields
| Field | Type | Required | Description |
| ------------------- | ------- | -------- | ------------------------------------------------- |
| `version` | integer | yes | Format version. Must be `1` for this spec. |
| `well_type` | string | yes | Classification of the well. See vocabulary below. |
| `name` | string | no | Well name or local identifier. |
| `well_driller` | string | no | Name of the drilling company or individual. |
| `construction_date` | string | no | ISO 8601 date of well completion (YYYY-MM-DD). |
| `lat` | number | no | Latitude in decimal degrees (WGS84). |
| `lng` | number | no | Longitude in decimal degrees (WGS84). |
| `elevation` | number | no | Ground elevation above sea level in meters. |
| `obs` | string | no | Free-text observations about the well. |
### `well_type` — Recommended values
| Value | Description |
| ---------------------- | ---------------------------------------------- |
| `tubular` | Conventional rotary or percussion drilled well |
| `artesian` | Free-flowing artesian well |
| `hand_dug` | Manually excavated well or cistern |
| `horizontal` | Horizontal or sub-horizontal well |
| `infiltration_gallery` | Horizontal subsurface infiltration gallery |
The `well_type` field accepts any string. The values above are the recommended vocabulary for interoperability. Tools should treat unrecognized values gracefully.
---
## Object Schemas
### `BoreHole` — `bore_hole[]`
Represents a drilled interval of the borehole. Multiple entries describe a telescoping borehole with different diameters at different depth ranges.
| Field | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `diameter` | number | yes | Borehole diameter in millimeters. |
| `drilling_method` | string | no | Method used (e.g. `rotary`, `percussion`, `cable_tool`). |
```json
{ "from": 0, "to": 80, "diameter": 250, "drilling_method": "rotary" }
```
---
### `WellCase` — `well_case[]`
Steel or plastic casing installed inside the borehole.
| Field | Type | Required | Description |
| ---------- | ------ | -------- | ------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `type` | string | yes | Casing material. Recommended: `steel`, `pvc`, `hdpe`, `fiberglass`. |
| `diameter` | number | yes | Casing outer diameter in millimeters. |
```json
{ "from": 0, "to": 60, "type": "steel", "diameter": 200 }
```
---
### `Reduction` — `reduction[]`
A transition piece connecting two casing or screen sections of different diameters.
| Field | Type | Required | Description |
| ----------- | ------ | -------- | -------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `diam_from` | number | yes | Diameter at the top of the reducer in mm. |
| `diam_to` | number | yes | Diameter at the bottom of the reducer in mm. |
| `type` | string | yes | Reducer type (e.g. `conical`, `stepped`). |
```json
{ "from": 58, "to": 60, "diam_from": 200, "diam_to": 150, "type": "conical" }
```
---
### `WellScreen` — `well_screen[]`
Slotted or wire-wound screen section that allows water to enter the well.
| Field | Type | Required | Description |
| ---------------- | ------ | -------- | --------------------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `type` | string | yes | Screen type. Recommended: `wire_wound`, `bridge_slot`, `louvered`, `pvc_slotted`. |
| `diameter` | number | yes | Screen outer diameter in millimeters. |
| `screen_slot_mm` | number | yes | Slot opening size in millimeters. |
```json
{
"from": 60,
"to": 80,
"type": "wire_wound",
"diameter": 150,
"screen_slot_mm": 0.5
}
```
---
### `SurfaceCase` — `surface_case[]`
Outer protective casing installed near the surface, typically to prevent surface contamination.
| Field | Type | Required | Description |
| ---------- | ------ | -------- | ------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `diameter` | number | yes | Casing outer diameter in millimeters. |
```json
{ "from": 0, "to": 3, "diameter": 300 }
```
---
### `HoleFill` — `hole_fill[]`
Material placed in the annular space between the casing and the borehole wall.
| Field | Type | Required | Description |
| ------------- | ------ | -------- | ------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `type` | string | yes | Either `gravel_pack` or `seal`. |
| `diameter` | number | yes | Outer diameter of the filled annulus in mm. |
| `description` | string | yes | Material description (e.g. grain size, material). |
```json
{
"from": 60,
"to": 80,
"type": "gravel_pack",
"diameter": 250,
"description": "Seixo 2-4mm"
}
```
---
### `CementPad` — `cement_pad`
The concrete pad installed at ground level (depth 0) around the wellhead. Dimensions are in meters.
| Field | Type | Required | Description |
| ----------- | ------ | -------- | ----------------------------------------------------- |
| `type` | string | yes | Pad shape (e.g. `square`, `rectangular`, `circular`). |
| `width` | number | yes | Width in meters. |
| `thickness` | number | yes | Thickness in meters. |
| `length` | number | yes | Length in meters. |
```json
{ "type": "square", "width": 1.0, "thickness": 0.15, "length": 1.0 }
```
---
### `Lithology` — `lithology[]`
Geological description of a depth interval.
| Field | Type | Required | Description |
| --------------- | ------ | -------- | --------------------------------------------------------------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `description` | string | yes | Free-text geological description. |
| `color` | string | yes | Representative color as a CSS hex value. |
| `fgdc_texture` | string | yes | Texture code per the FGDC Digital Cartographic Standard for Geologic Map Symbolization. |
| `geologic_unit` | string | yes | Name of the stratigraphic or geologic unit. |
| `aquifer_unit` | string | yes | Aquifer classification (e.g. `freático`, `confinado`, `fraturado`). |
```json
{
"from": 0,
"to": 20,
"description": "Areia fina amarelada",
"color": "#f5deb3",
"fgdc_texture": "sand",
"geologic_unit": "Quaternário",
"aquifer_unit": "freático"
}
```
---
### `Fracture` — `fractures[]`
A discrete fracture or fracture zone intersected by the borehole.
| Field | Type | Required | Description |
| -------------- | ------- | -------- | -------------------------------------------------------------- |
| `depth` | number | yes | Depth of the fracture in meters. |
| `water_intake` | boolean | yes | Whether the fracture produces water. |
| `description` | string | yes | Free-text description. |
| `swarm` | boolean | yes | Whether this fracture belongs to a swarm (closely spaced set). |
| `azimuth` | number | yes | Azimuth in degrees from geographic north (0–360). |
| `dip` | number | yes | Dip angle in degrees from horizontal (0–90). |
```json
{
"depth": 45.2,
"water_intake": true,
"description": "Fratura aberta",
"swarm": false,
"azimuth": 120,
"dip": 35
}
```
---
### `Cave` — `caves[]`
A cavity or void zone intersected by the borehole.
| Field | Type | Required | Description |
| -------------- | ------- | -------- | -------------------------------- |
| `from` | number | yes | Start depth in meters. |
| `to` | number | yes | End depth in meters. |
| `water_intake` | boolean | yes | Whether the cave produces water. |
| `description` | string | yes | Free-text description. |
```json
{ "from": 50, "to": 52, "water_intake": false, "description": "Caverna seca" }
```
---
## Complete Example
```json
{
"version": 1,
"well_type": "tubular",
"name": "Poço PP-01",
"well_driller": "Perfuradora XYZ",
"construction_date": "2024-03-15",
"lat": -1.4558,
"lng": -48.5039,
"elevation": 12.5,
"obs": "Sem anomalias observadas durante a perfuração.",
"bore_hole": [
{ "from": 0, "to": 80, "diameter": 250, "drilling_method": "rotary" }
],
"well_case": [{ "from": 0, "to": 60, "type": "steel", "diameter": 200 }],
"reduction": [
{
"from": 58,
"to": 60,
"diam_from": 200,
"diam_to": 150,
"type": "conical"
}
],
"well_screen": [
{
"from": 60,
"to": 80,
"type": "wire_wound",
"diameter": 150,
"screen_slot_mm": 0.5
}
],
"surface_case": [{ "from": 0, "to": 3, "diameter": 300 }],
"hole_fill": [
{
"from": 60,
"to": 80,
"type": "gravel_pack",
"diameter": 250,
"description": "Seixo 2-4mm"
},
{
"from": 3,
"to": 60,
"type": "seal",
"diameter": 250,
"description": "Cimento"
}
],
"cement_pad": {
"type": "square",
"width": 1.0,
"thickness": 0.15,
"length": 1.0
},
"lithology": [
{
"from": 0,
"to": 20,
"description": "Areia fina amarelada",
"color": "#f5deb3",
"fgdc_texture": "sand",
"geologic_unit": "Quaternário",
"aquifer_unit": "freático"
},
{
"from": 20,
"to": 80,
"description": "Granito fraturado cinza",
"color": "#a9a9a9",
"fgdc_texture": "granite",
"geologic_unit": "Embasamento Cristalino",
"aquifer_unit": "fraturado"
}
],
"fractures": [
{
"depth": 45.2,
"water_intake": true,
"description": "Fratura aberta",
"swarm": false,
"azimuth": 120,
"dip": 35
},
{
"depth": 62.0,
"water_intake": false,
"description": "Fratura de cisalhamento",
"swarm": true,
"azimuth": 90,
"dip": 70
}
],
"caves": [
{
"from": 50,
"to": 52,
"water_intake": false,
"description": "Caverna seca"
}
]
}
```
---
## Versioning
The `version` field must always be present and is an integer starting at `1`. Parsers must reject files with an unrecognized `version` value and should warn when `version` is absent.
| Version | Description |
| ------- | ----------------------------- |
| `1` | Initial release of the format |
A `hydro` block for dynamic hydrological data (static level, dynamic level, pump tests, water quality) is reserved for v2 and must not appear in v1 files.
---
## MIME Type
```
application/vnd.well+json
```
---
_Specification v1.0 — `.well` format_
# FGDC Texture Patterns Reference
## Overview
This document lists all texture patterns available in `FGDC_TEXTURES_OPTIONS` from `@welldot/core`. These codes follow the **FGDC (Federal Geographic Data Committee)** digital cartographic standard for geologic map symbolization.
Use a `code` value in the `texture` field of a `Lithology` record to assign a pattern to a depth interval.
```typescript
import { FGDC_TEXTURES_OPTIONS } from '@welldot/core';
// Find a texture by code
const limestone = FGDC_TEXTURES_OPTIONS.find(t => t.code === 627);
// { code: 627, label: 'Limestone' }
// Filter to only textures with a rendered image
const available = FGDC_TEXTURES_OPTIONS.filter(t => !t.pending);
```
> **Texture availability:** Some entries carry `pending: true`, meaning the SVG pattern image has not been implemented yet. Textures without this flag are fully rendered and ready to use. The flag is transitional and will be removed once all patterns are complete.
>
> Currently **120 of 282 textures** are available: all of Series 600 (84) and Series 700 (33), plus codes 120, 123, and 132 from Series 100.
The patterns are grouped by FGDC series below. Each table lists the code and label of every pattern, and the summary at the end gives the available and pending counts per series.
## Table of contents
- [Series 100 — Surficial Patterns](#series-100--surficial-patterns)
- [Series 200 — Sedimentary Patterns](#series-200--sedimentary-patterns)
- [Series 300 — Igneous Patterns](#series-300--igneous-patterns)
- [Series 400 — Miscellaneous and Metamorphic Patterns](#series-400--miscellaneous-and-metamorphic-patterns)
- [Series 500 — Glacial and Periglacial Patterns](#series-500--glacial-and-periglacial-patterns)
- [Series 600 — Sedimentary Lithology Patterns](#series-600--sedimentary-lithology-patterns)
- [Series 700 — Metamorphic and Igneous Lithology Patterns](#series-700--metamorphic-and-igneous-lithology-patterns)
- [Summary](#summary)
---
## Series 100 — Surficial Patterns
| Code | Label | Status |
| ---- | -------------------------- | --------- |
| 101 | Unconsolidated material | pending |
| 102 | Alluvium | pending |
| 103 | Terrace deposit | pending |
| 104 | Floodplain deposit | pending |
| 105 | Fan or pediment deposit | pending |
| 106 | Eolian sand | pending |
| 107 | Silt or loess | pending |
| 108 | Clay deposit | pending |
| 109 | Colluvium | pending |
| 110 | Gravel deposit | pending |
| 111 | Residual deposit | pending |
| 112 | Playa deposit | pending |
| 113 | Lacustrine deposit | pending |
| 114 | Marine deposit | pending |
| 115 | Beach or bar deposit | pending |
| 116 | Organic deposit or peat | pending |
| 117 | Swamp or marsh deposit | pending |
| 118 | Soil | pending |
| 119 | Fill or artificial deposit | pending |
| 120 | Surficial pattern 120 | available |
| 121 | Surficial pattern 121 | pending |
| 122 | Surficial pattern 122 | pending |
| 123 | Surficial pattern 123 | available |
| 124 | Surficial pattern 124 | pending |
| 125 | Surficial pattern 125 | pending |
| 126 | Surficial pattern 126 | pending |
| 127 | Surficial pattern 127 | pending |
| 128 | Surficial pattern 128 | pending |
| 129 | Surficial pattern 129 | pending |
| 130 | Surficial pattern 130 | pending |
| 131 | Surficial pattern 131 | pending |
| 132 | Surficial pattern 132 | available |
| 133 | Surficial pattern 133 | pending |
| 134 | Surficial pattern 134 | pending |
| 135 | Surficial pattern 135 | pending |
| 136 | Surficial pattern 136 | pending |
| 137 | Surficial pattern 137 | pending |
---
## Series 200 — Sedimentary Patterns
> All entries in this series are **pending** (no SVG images implemented yet).
| Code | Label |
| ---- | ----------------------- |
| 201 | Sedimentary pattern 201 |
| 202 | Sedimentary pattern 202 |
| 203 | Sedimentary pattern 203 |
| 204 | Sedimentary pattern 204 |
| 205 | Sedimentary pattern 205 |
| 206 | Sedimentary pattern 206 |
| 207 | Sedimentary pattern 207 |
| 208 | Sedimentary pattern 208 |
| 209 | Sedimentary pattern 209 |
| 210 | Sedimentary pattern 210 |
| 211 | Sedimentary pattern 211 |
| 212 | Sedimentary pattern 212 |
| 213 | Sedimentary pattern 213 |
| 214 | Sedimentary pattern 214 |
| 215 | Sedimentary pattern 215 |
| 216 | Sedimentary pattern 216 |
| 217 | Sedimentary pattern 217 |
| 218 | Sedimentary pattern 218 |
| 219 | Sedimentary pattern 219 |
| 220 | Sedimentary pattern 220 |
| 221 | Sedimentary pattern 221 |
| 222 | Sedimentary pattern 222 |
| 223 | Sedimentary pattern 223 |
| 224 | Sedimentary pattern 224 |
| 225 | Sedimentary pattern 225 |
| 226 | Sedimentary pattern 226 |
| 227 | Sedimentary pattern 227 |
| 228 | Sedimentary pattern 228 |
| 229 | Sedimentary pattern 229 |
| 230 | Sedimentary pattern 230 |
| 231 | Sedimentary pattern 231 |
| 232 | Sedimentary pattern 232 |
| 233 | Sedimentary pattern 233 |
---
## Series 300 — Igneous Patterns
> All entries in this series are **pending** (no SVG images implemented yet).
| Code | Label |
| ---- | ------------------- |
| 301 | Igneous pattern 301 |
| 302 | Igneous pattern 302 |
| 303 | Igneous pattern 303 |
| 304 | Igneous pattern 304 |
| 305 | Igneous pattern 305 |
| 306 | Igneous pattern 306 |
| 307 | Igneous pattern 307 |
| 308 | Igneous pattern 308 |
| 309 | Igneous pattern 309 |
| 310 | Igneous pattern 310 |
| 311 | Igneous pattern 311 |
| 312 | Igneous pattern 312 |
| 313 | Igneous pattern 313 |
| 314 | Igneous pattern 314 |
| 315 | Igneous pattern 315 |
| 316 | Igneous pattern 316 |
| 317 | Igneous pattern 317 |
| 318 | Igneous pattern 318 |
| 319 | Igneous pattern 319 |
| 320 | Igneous pattern 320 |
| 321 | Igneous pattern 321 |
| 322 | Igneous pattern 322 |
| 323 | Igneous pattern 323 |
| 324 | Igneous pattern 324 |
| 325 | Igneous pattern 325 |
| 326 | Igneous pattern 326 |
| 327 | Igneous pattern 327 |
| 328 | Igneous pattern 328 |
| 329 | Igneous pattern 329 |
| 330 | Igneous pattern 330 |
| 331 | Igneous pattern 331 |
---
## Series 400 — Miscellaneous and Metamorphic Patterns
> All entries in this series are **pending** (no SVG images implemented yet).
| Code | Label |
| ---- | ------------------------- |
| 401 | Miscellaneous pattern 401 |
| 402 | Miscellaneous pattern 402 |
| 403 | Miscellaneous pattern 403 |
| 404 | Miscellaneous pattern 404 |
| 405 | Miscellaneous pattern 405 |
| 406 | Miscellaneous pattern 406 |
| 407 | Miscellaneous pattern 407 |
| 408 | Miscellaneous pattern 408 |
| 409 | Miscellaneous pattern 409 |
| 410 | Miscellaneous pattern 410 |
| 411 | Miscellaneous pattern 411 |
| 412 | Miscellaneous pattern 412 |
| 413 | Miscellaneous pattern 413 |
| 414 | Miscellaneous pattern 414 |
| 415 | Miscellaneous pattern 415 |
| 416 | Miscellaneous pattern 416 |
| 417 | Miscellaneous pattern 417 |
| 418 | Miscellaneous pattern 418 |
| 419 | Miscellaneous pattern 419 |
| 420 | Miscellaneous pattern 420 |
| 421 | Miscellaneous pattern 421 |
| 422 | Miscellaneous pattern 422 |
| 423 | Miscellaneous pattern 423 |
| 424 | Miscellaneous pattern 424 |
| 425 | Miscellaneous pattern 425 |
| 426 | Miscellaneous pattern 426 |
| 427 | Miscellaneous pattern 427 |
| 428 | Miscellaneous pattern 428 |
| 429 | Miscellaneous pattern 429 |
| 430 | Miscellaneous pattern 430 |
| 431 | Miscellaneous pattern 431 |
| 432 | Miscellaneous pattern 432 |
| 433 | Miscellaneous pattern 433 |
| 434 | Miscellaneous pattern 434 |
| 435 | Miscellaneous pattern 435 |
| 436 | Miscellaneous pattern 436 |
---
## Series 500 — Glacial and Periglacial Patterns
> All entries in this series are **pending** (no SVG images implemented yet).
| Code | Label |
| ---- | ----------------------- |
| 521 | Glacial pattern 521 |
| 522 | Glacial pattern 522 |
| 523 | Glacial pattern 523 |
| 524 | Glacial pattern 524 |
| 530 | Glacial pattern 530 |
| 531 | Glacial pattern 531 |
| 532 | Glacial pattern 532 |
| 533 | Glacial pattern 533 |
| 534 | Glacial pattern 534 |
| 535 | Glacial pattern 535 |
| 536 | Glacial pattern 536 |
| 537 | Glacial pattern 537 |
| 538 | Glacial pattern 538 |
| 580 | Glacial pattern 580 |
| 581 | Glacial pattern 581 |
| 582 | Glacial pattern 582 |
| 583 | Glacial pattern 583 |
| 584 | Glacial pattern 584 |
| 585 | Glacial pattern 585 |
| 586 | Glacial pattern 586 |
| 587 | Glacial pattern 587 |
| 588 | Glacial pattern 588 |
| 589 | Glacial pattern 589 |
| 591 | Periglacial pattern 591 |
| 592 | Periglacial pattern 592 |
| 593 | Periglacial pattern 593 |
| 594 | Periglacial pattern 594 |
| 595 | Periglacial pattern 595 |
---
## Series 600 — Sedimentary Lithology Patterns
These are the most commonly used codes for well lithology logging. They represent specific rock and sediment types encountered in boreholes.
| Code | Label |
| ---- | ---------------------------------------------------------------- |
| 601 | Gravel or conglomerate (1st option) |
| 602 | Gravel or conglomerate (2nd option) |
| 603 | Crossbedded gravel or conglomerate |
| 605 | Breccia (1st option) |
| 606 | Breccia (2nd option) |
| 607 | Massive sand or sandstone |
| 608 | Bedded sand or sandstone |
| 609 | Crossbedded sand or sandstone (1st option) |
| 610 | Crossbedded sand or sandstone (2nd option) |
| 611 | Ripple-bedded sand or sandstone |
| 612 | Argillaceous or shaly sandstone |
| 613 | Calcareous sandstone |
| 614 | Dolomitic sandstone |
| 616 | Silt, siltstone, or shaly silt |
| 617 | Calcareous siltstone |
| 618 | Dolomitic siltstone |
| 619 | Sandy or silty shale |
| 620 | Clay or clay shale |
| 621 | Cherty shale |
| 622 | Dolomitic shale |
| 623 | Calcareous shale or marl |
| 624 | Carbonaceous shale |
| 625 | Oil shale |
| 626 | Chalk |
| 627 | Limestone |
| 628 | Clastic limestone |
| 629 | Fossiliferous clastic limestone |
| 630 | Nodular or irregularly bedded limestone |
| 631 | Limestone, irregular (burrow?) fillings of saccharoidal dolomite |
| 632 | Crossbedded limestone |
| 633 | Cherty crossbedded limestone |
| 634 | Cherty and sandy crossbedded clastic limestone |
| 635 | Oolitic limestone |
| 636 | Sandy limestone |
| 637 | Silty limestone |
| 638 | Argillaceous or shaly limestone |
| 639 | Cherty limestone (1st option) |
| 640 | Cherty limestone (2nd option) |
| 641 | Dolomitic limestone, limy dolostone, or limy dolomite |
| 642 | Dolostone or dolomite |
| 643 | Crossbedded dolostone or dolomite |
| 644 | Oolitic dolostone or dolomite |
| 645 | Sandy dolostone or dolomite |
| 646 | Silty dolostone or dolomite |
| 647 | Argillaceous or shaly dolostone or dolomite |
| 648 | Cherty dolostone or dolomite |
| 649 | Bedded chert (1st option) |
| 650 | Bedded chert (2nd option) |
| 651 | Fossiliferous bedded chert |
| 652 | Fossiliferous rock |
| 653 | Diatomaceous rock |
| 654 | Subgraywacke |
| 655 | Crossbedded subgraywacke |
| 656 | Ripple-bedded subgraywacke |
| 657 | Peat |
| 658 | Coal |
| 659 | Bony coal or impure coal |
| 660 | Underclay |
| 661 | Flint clay |
| 662 | Bentonite |
| 663 | Glauconite |
| 664 | Limonite |
| 665 | Siderite |
| 666 | Phosphatic-nodular rock |
| 667 | Gypsum |
| 668 | Salt |
| 669 | Interbedded sandstone and siltstone |
| 670 | Interbedded sandstone and shale |
| 671 | Interbedded ripple-bedded sandstone and shale |
| 672 | Interbedded shale and silty limestone (shale dominant) |
| 673 | Interbedded shale and limestone, shale dominant (1st option) |
| 674 | Interbedded shale and limestone, shale dominant (2nd option) |
| 675 | Interbedded calcareous shale and limestone (shale dominant) |
| 676 | Interbedded silty limestone and shale |
| 677 | Interbedded limestone and shale (1st option) |
| 678 | Interbedded limestone and shale (2nd option) |
| 679 | Interbedded limestone and shale (limestone dominant) |
| 680 | Interbedded limestone and calcareous shale |
| 681 | Till or diamicton (1st option) |
| 682 | Till or diamicton (2nd option) |
| 683 | Till or diamicton (3rd option) |
| 684 | Loess (1st option) |
| 685 | Loess (2nd option) |
| 686 | Loess (3rd option) |
---
## Series 700 — Metamorphic and Igneous Lithology Patterns
| Code | Label |
| ---- | -------------------------------- |
| 701 | Metamorphism |
| 702 | Quartzite |
| 703 | Slate |
| 704 | Schistose or gneissoid granite |
| 705 | Schist |
| 706 | Contorted schist |
| 707 | Schist and gneiss |
| 708 | Gneiss |
| 709 | Contorted gneiss |
| 710 | Soapstone, talc, or serpentinite |
| 711 | Tuffaceous rock |
| 712 | Crystal tuff |
| 713 | Devitrified tuff |
| 714 | Volcanic breccia and tuff |
| 715 | Volcanic breccia or agglomerate |
| 716 | Zeolitic rock |
| 717 | Basaltic flows |
| 718 | Granite (1st option) |
| 719 | Granite (2nd option) |
| 720 | Banded igneous rock |
| 721 | Igneous rock (1st option) |
| 722 | Igneous rock (2nd option) |
| 723 | Igneous rock (3rd option) |
| 724 | Igneous rock (4th option) |
| 725 | Igneous rock (5th option) |
| 726 | Igneous rock (6th option) |
| 727 | Igneous rock (7th option) |
| 728 | Igneous rock (8th option) |
| 729 | Porphyritic rock (1st option) |
| 730 | Porphyritic rock (2nd option) |
| 731 | Vitrophyre |
| 732 | Quartz |
| 733 | Ore |
---
## Summary
| Series | Category | Total | Available | Pending |
| --------- | ------------------------------- | ------- | --------- | ------- |
| 100 | Surficial | 37 | 3 | 34 |
| 200 | Sedimentary | 33 | 0 | 33 |
| 300 | Igneous | 31 | 0 | 31 |
| 400 | Miscellaneous / Metamorphic | 36 | 0 | 36 |
| 500 | Glacial / Periglacial | 28 | 0 | 28 |
| 600 | Sedimentary Lithology | 84 | 84 | 0 |
| 700 | Metamorphic / Igneous Lithology | 33 | 33 | 0 |
| **Total** | | **282** | **120** | **162** |
---
_Source: FGDC Digital Cartographic Standard for Geologic Map Symbolization (FGDC-STD-013-2006), Chapter 25 — Lithology._
# Contributing
> How to contribute to welldot, from reporting a problem with the docs to proposing a change to the .well format, and where each kind of change lives.
Welldot is open source (Apache 2.0) and developed in the open on [GitHub](https://github.com/welldothq/welldot). Contributions of every size help: a typo fix, a clearer example, a missing vocabulary value, a converter for another registry.
## Where things live
| You want to change | Where | Start with |
| ------------------------------------------------ | ---------------------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
| A rule of the `.well` format | `spec/` (plain Markdown) | An issue describing the need ([Extending the format](/format/guides/extending#propose-a-change-to-the-format)) |
| A package's behavior or API | `packages/core`, `packages/utils`, `packages/render` | An issue or a pull request with tests and a changeset |
| A docs page (guides, concepts, getting started) | `apps/docs/content/en/` | A pull request; use **Edit this page** at the side of any page |
| The site itself (layout, components, MCP server) | `apps/docs/` | An issue first for anything large |
| The conversion skill | `.agents/skills/welldot-converter/` | A pull request |
The repository's [CONTRIBUTING.md](https://github.com/welldothq/welldot/blob/main/CONTRIBUTING.md) covers setup, branches, commits and releases.
## Changes that touch the docs
A change to the spec or to a package's public API usually needs a docs change in the same pull request. [Update rules](/contributing/update-rules) says exactly what, and CI checks the parts that can be checked: examples must validate, links and spec anchors must resolve, and the site must build.
## Writing docs
- [Writing docs](/contributing/writing-docs): the writing rules, how to add a page and how to run the site locally.
- [Content components](/contributing/components): callouts, Do/Don't blocks, code groups, diagrams and live well previews, with their Markdown.
## Reporting a problem
Found a page that is wrong, unclear or out of date? Open an [issue](https://github.com/welldothq/welldot/issues) with the page address and what you expected. For a mistake in the specification itself, say which section and quote the text.
# Content components
This page is the reference for writers. Every block below is shown as it renders, followed by the Markdown (MDC) that produces it. Prefer these over raw HTML.
## Callouts
Four kinds, from the mildest to the strongest. Keep them short: one or two sentences.
::note
A `.well` file is plain JSON. Any text editor can open it.
::
::tip
Start from an example in `packages/core/examples` instead of an empty file.
::
::warning
Depths are in metres from the ground surface. A negative `from` means above ground.
::
::caution
Do not edit files in `spec/` to suit the site. The spec is canonical.
::
```mdc
::note
A `.well` file is plain JSON. Any text editor can open it.
::
```
`::tip`, `::warning` and `::caution` take the same form.
## Do and Don't
Pair a recommended form with the one to avoid. Each side takes text and an optional fenced block.
::do-dont
#do
Give depths as numbers, in metres.
```json
{ "from": 0, "to": 12.5 }
```
#dont
Put units inside the value.
```json
{ "from": "0 m", "to": "12.5 m" }
```
::
````mdc
::do-dont
#do
Give depths as numbers, in metres.
```json
{ "from": 0, "to": 12.5 }
```
#dont
Put units inside the value.
```json
{ "from": "0 m", "to": "12.5 m" }
```
::
````
## Code blocks and code groups
A fence takes a language and an optional `[filename]`. Group alternatives, such as package managers, with `::code-group`. The bracketed name becomes the tab label.
::code-group
```bash [pnpm]
pnpm add @welldot/core
```
```bash [npm]
npm i @welldot/core
```
```bash [yarn]
yarn add @welldot/core
```
::
````mdc
::code-group
```bash [pnpm]
pnpm add @welldot/core
```
```bash [npm]
npm i @welldot/core
```
::
````
### Fence languages
- `well`: a complete `.well` document. CI validates every `well` fence with `parseWell`, so it must be valid as written. It is highlighted as JSON.
- `json`: a fragment, such as a single object or a field. Not validated.
- `ts`, `bash`, `vue`, `yaml`, `md`: as usual.
```ts [read.ts]
import { parseWell } from '@welldot/core';
const well = parseWell(await file.text());
console.log(well.bore_hole.length);
```
## Tabs
Use tabs for content that differs by context but not by language (code alternatives go in a code group).
::tabs
:::tabs-item{label="Browser" icon="i-lucide-globe"}
Open [welldot.org](https://welldot.org) and drop the file on the editor.
:::
:::tabs-item{label="Node" icon="i-lucide-terminal"}
Read the file and call `parseWell` from `@welldot/core`.
:::
::
```mdc
::tabs
:::tabs-item{label="Browser" icon="i-lucide-globe"}
Open welldot.org and drop the file on the editor.
:::
:::tabs-item{label="Node" icon="i-lucide-terminal"}
Read the file and call `parseWell` from `@welldot/core`.
:::
::
```
## Steps
Number a procedure by wrapping its `###` headings in `::steps`.
::steps
### Install the package
Add `@welldot/core` to your project.
### Parse the file
`parseWell` validates the JSON and upgrades a v1 file to v2.
### Use the data
Every field is typed; see the format reference.
::
```mdc
::steps
### Install the package
Add `@welldot/core` to your project.
### Parse the file
`parseWell` validates the JSON and upgrades a v1 file to v2.
::
```
## Diagrams
A `mermaid` fence renders as a diagram that follows the light and dark theme.
```mermaid
flowchart LR
A[.well file] --> B{parseWell}
B -->|valid| C[Well object]
B -->|invalid| D[ZodError with paths]
C --> E[WellRenderer]
C --> F["@welldot/utils"]
```
````mdc
```mermaid
flowchart LR
A[.well file] --> B{parseWell}
B -->|valid| C[Well object]
B -->|invalid| D[ZodError with paths]
```
````
## Live well preview
Draws a profile with `@welldot/render`, next to its source. It runs in the browser only.
### From a bundled example
`example` is the name of a file in `packages/core/examples`, without `.well`.
::well-preview{example="irece-ba-carste-bambui"}
::
```mdc
::well-preview{example="irece-ba-carste-bambui"}
::
```
### From an inline document
Put a complete document in a `well` fence inside the component.
::well-preview
```well
{
"version": "2.4",
"name": "Demo well, sandstone aquifer",
"well_type": "tubular",
"bore_hole": [
{ "from": 0, "to": 12, "diameter": 304.8 },
{ "from": 12, "to": 60, "diameter": 254 }
],
"surface_case": [{ "from": 0, "to": 12, "diameter": 254 }],
"well_case": [{ "from": -0.5, "to": 36, "type": "pvc", "diameter": 152.4 }],
"reduction": [],
"well_screen": [
{ "from": 36, "to": 54, "type": "pvc", "diameter": 152.4, "screen_slot": 0.75 }
],
"hole_fill": [
{ "from": 0, "to": 12, "type": "seal", "diameter": 254, "description": "Cement seal" },
{ "from": 12, "to": 60, "type": "gravel_pack", "diameter": 254, "description": "Gravel pack, 2–4 mm" }
],
"lithology": [
{ "from": 0, "to": 8, "description": "Red clayey soil", "color": "#9c5a3c", "texture": { "code": 620, "vocabulary": "fgdc" }, "geologic_unit": "Cover", "aquifer_unit": "Aquitard" },
{ "from": 8, "to": 30, "description": "Silty sandstone", "color": "#c9a27a", "texture": { "code": 612, "vocabulary": "fgdc" }, "geologic_unit": "Botucatu Formation", "aquifer_unit": "Guarani" },
{ "from": 30, "to": 60, "description": "Medium-grained sandstone", "color": "#e0c48f", "texture": { "code": 607, "vocabulary": "fgdc" }, "geologic_unit": "Botucatu Formation", "aquifer_unit": "Guarani" }
],
"fractures": [],
"caves": []
}
```
::
````mdc
::well-preview
```well
{ "version": "2.4", "name": "…", … }
```
::
````
### Invalid input
When the source does not validate, the preview lists the errors with their paths instead of drawing. This snippet is incomplete on purpose, so it uses a `json` fence, which CI does not validate.
::well-preview
```json
{
"version": "2.4",
"name": "Half-written well",
"bore_hole": [{ "from": 0, "to": "sixty", "diameter": 254 }]
}
```
::
# Update rules
> Which code and spec changes need a docs change in the same pull request, what CI checks automatically, and the checklist to run at the end of every change to the spec or a published package.
The docs stay correct only if they change together with what they describe. These rules apply to everyone: maintainers, outside contributors and AI assistants working in the repository. The same rules are written for Claude in [`apps/docs/CLAUDE.md`](https://github.com/welldothq/welldot/blob/main/apps/docs/CLAUDE.md), and the pull request template links here.
## What triggers a docs change
| Change | Docs work, in the same pull request |
| --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Spec change (new field, enum value, rule, version, deprecation) | Edit the spec file in `spec/` first, then the guides and examples that depend on it. |
| Public API change in a package | The TSDoc in the source (the API reference follows) and every guide that uses the API. |
| Behavior change with no API change | The guide that describes the behavior. |
| Release | Nothing by hand. Changelogs come from changesets. |
| Internal refactor | Nothing. |
| English page edited | Nothing in Portuguese. The CI warning lists stale PT pages; the on-page "behind" notice comes with the PT release. |
When to edit each spec file, and what to update in it, is described in [`packages/core/CLAUDE.md`](https://github.com/welldothq/welldot/blob/main/packages/core/CLAUDE.md) ("Documentation requirements") and [`spec/README.md`](https://github.com/welldothq/welldot/blob/main/spec/README.md). Spec files stay plain GitHub-readable Markdown: no site components or syntax.
## Checks in CI
The `docs — Check` workflow runs on pull requests that touch `apps/docs/`, `spec/`, `packages/` or `.changeset/`.
| Check | Command | Effect |
| ---------------------------------------------------------------------------------------- | --------------------------------------- | --------------------- |
| Every `.well` snippet and example in the docs validates with `@welldot/core` | `pnpm --filter docs check:examples` | Blocking |
| Internal links and anchors resolve, including links into spec headings | `pnpm --filter docs check:links` | Blocking |
| The docs site builds | `pnpm --filter docs generate` | Blocking |
| The core tests that compare the spec with the code (`vocab.test.ts`, `examples.test.ts`) | `pnpm --filter @welldot/core test` | Blocking (CI runs it) |
| A changeset for a published package with no change under `apps/docs/` or `spec/` | `pnpm --filter docs check:touched` | Warning only |
| Portuguese pages behind their English page (once they exist) | `pnpm --filter docs check:translations` | Warning only |
A renamed spec heading therefore fails the build instead of silently breaking the guides that link to it. Many changes need no docs, which is why the changeset check only warns.
## End-of-change checklist
Run through this at the end of any change that touches the spec or a published package:
::steps
### Did a rule change?
Edit the spec file first, as plain Markdown.
### Which docs pages explain or depend on the change?
Update them, linking to the spec instead of restating it. Search `apps/docs/content` for the field, function or rule name.
### Do the examples still validate?
Run `pnpm --filter docs check:examples`, and the `@welldot/core` tests if examples in `packages/core/examples` changed.
### Did an English page change?
Its Portuguese page, when there is one, shows up in the CI warning for stale translations. Nothing else is needed unless you are asked to translate.
### Is there a changeset?
Add one for every published package you changed (`pnpm changeset`). The changelog page follows from it.
::
## In the pull request
The pull request template has three boxes for this:
- [ ] Spec updated first, if a rule changed
- [ ] Docs pages that depend on the change updated (or none needed)
- [ ] Examples validate (`check:examples`) and links resolve (`check:links`)
# Writing docs
> The writing rules for docs.welldot.org, how pages are organized and ordered, how to add a page, which pages are generated, and how to run and check the site locally.
## Who reads these docs
- **Geologists and hydrogeologists**, often reading English as a second language. They need to know what each field means in the field.
- **Developers** building tools, databases and editors on the format. They need code that works against the current packages.
- **AI agents** working for either group, reading the same pages as Markdown.
Write for all three: plain sentences, one idea each, no marketing language, no jargon a field geologist would not use unless it is explained.
## The rules
1. **Link to the spec, don't restate it.** The specification in `spec/` is the only place a rule is stated. A page that depends on a rule explains it in plain words and links to the spec heading, for example `[level sign convention](/format/spec/v2/format-reference#level-sign-convention)`. If a guide needs a rule the spec lacks, the spec gets the rule first.
2. **Full examples use `well` fences.** Every complete `.well` document is a ` ```well ` fence or a `::well-preview{example=""}` block, so CI validates it. Fragments and deliberately invalid snippets use `json` fences.
3. **Two entry points.** Geologist-facing pages (getting started, concepts) explain field meaning without code. Developer pages show code, checked against the current package APIs.
4. **Frontmatter.** Every page has a `title` and a `description`. The description is used in search results, `llms.txt` and SEO, so make it a full sentence that says what the page covers.
5. **No duplicate title.** The page header shows `title`. Omit a leading `# Heading` in the body, or make it identical to `title`.
6. **Sentence case** for titles and headings: "Getting started", not "Getting Started".
7. **Code that runs.** Run new snippets against the packages before publishing. State the output only when you have seen it.
The [content components](/contributing/components) page lists the blocks you can use: callouts (`::note`, `::tip`, `::warning`, `::caution`), `::code-group`, `::tabs`, `::steps`, `::do-dont`, `mermaid` diagrams and `::well-preview`.
::warning
`check:examples` only reads a `::well-preview{example="…"}` attribute written on one line. Keep the whole opening line together.
::
## Links and anchors
- Link site pages by path: `/format/concepts/units-and-precision`.
- Spec pages are at `/format/spec/v2/`, `/format/spec/reference/fgdc-textures` and `/format/spec/v1/well-format`. Heading anchors are the ones GitHub generates, so ``## `bore_hole[].drilling_method` `` becomes `#bore_holedrilling_method--recommended-values-since-v23`. Copy them from the rendered page or GitHub; do not guess.
- Object pages: `/format/reference/objects/