# Permits

> Derive a permit's status, list condition deadlines and whether each was met, follow renewals, and collect permit warnings with @welldot/utils.

A `permits` entry records an abstraction permit (outorga), an exemption or a registration: who issued it, its validity, the granted flow and volumes, and its conditions (condicionantes) with their fulfillments ([`permits`](/format/spec/v2/object-schemas#permits-since-v23)). The file stores the facts as written in the documents. Whether a permit is in force today, and which deadlines are due, is derived on the local calendar date at the well, never stored ([permit status](/format/spec/v2/object-schemas#permit-status-derived)).

The outputs on this page come from the `colatina-es-gnaisse-rural` [example well](/format/reference/examples): an insignificant-use exemption from 2017, superseded in 2023 by an irrigation permit with a meter-installation condition and a yearly volume report.

## Status

```ts
import { getPermitStatus } from '@welldot/utils';

for (const permit of well.permits ?? []) {
  console.log(permit.id, getPermitStatus(well, permit));
}
// insig-2017    superseded
// outorga-2023  active
```

`getPermitStatus(well, permit, today?)` accepts a permit or its id and returns:

| Status                                                     | When                                                                                                                        |
| ---------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `requested`, `suspended`, `revoked`, `denied`, `withdrawn` | The stored administrative `status` says so ([administrative status](/format/spec/v2/object-schemas#administrative-status)). |
| `superseded`                                               | Another permit names this one in `supersedes` and has started.                                                              |
| `not_yet_valid`                                            | Granted, but `valid_from` is in the future.                                                                                 |
| `active`                                                   | In force today.                                                                                                             |
| `active_pending_renewal`                                   | Past `valid_until`, with a renewal requested in time.                                                                       |
| `expired`                                                  | Past `valid_until`, with no renewal.                                                                                        |

`today` is a calendar date (`YYYY-MM-DD`) and defaults to today. Pass it explicitly in tests and reports, so the result does not change with the clock. `PERMIT_STATUSES` has English and Portuguese labels for each status.

Related helpers:

- `getActivePermit(well, today?)`: the active permit with the latest start, the one production is checked against.
- `getPermitEffectiveEnd(well, permit, today?)`: the day before the successor starts, else `valid_until`.
- `isPermitGranted(permit)`, `getPermitIdentifier(permit)` (the `identifier`, else the `request_identifier`), `permitLabel(permit)`.

## Condition deadlines

A condition gives its deadline as a date, a duration after issue (`due_after: "P90D"`) or a recurrence (`first_due` plus `recurrence: "P1Y"`), in ISO 8601 durations ([deadline generation](/format/spec/v2/object-schemas#deadline-generation-normative)). The deadlines themselves are derived:

```ts
import {
  getConditionDeadlineStates,
  getConditionDeadlines,
} from '@welldot/utils';

const permit = well.permits!.find(p => p.id === 'outorga-2023')!;
const reporting = permit.conditions!.find(c => c.id === 'c2')!; // yearly volume report

getConditionDeadlines(well, permit, reporting);
// ['2024-01-31', '2025-01-31', '2026-01-31', …]

getConditionDeadlineStates(well, permit, reporting, { today: '2026-10-24' });
// [{ due_date: '2024-01-31', status: 'fulfilled', fulfillment_id: 'f-2024' },
//  { due_date: '2025-01-31', status: 'fulfilled', fulfillment_id: 'f-2025' },
//  { due_date: '2026-01-31', status: 'fulfilled', fulfillment_id: 'f-2026' },
//  { due_date: '2027-01-31', status: 'upcoming' }, …]
```

Each state is `fulfilled`, `fulfilled_late`, `upcoming` or `overdue`, matched against the condition's `fulfillments` by `due_date` ([condition fulfillment](/format/spec/v2/object-schemas#condition-fulfillment)). Deadlines stop at the condition's `occurrences` or `last_due`, at the permit's effective end or at a horizon; a permit that was never granted has none.

Month-end dates are clamped (a monthly deadline from 31 January falls on 28 or 29 February). The duration helpers are exported too: `parseDateDuration`, `addDateDuration`, `todayCalendarDate`.

## History and timeline

```ts
import {
  getOverduePermitHistory,
  getPermitHistory,
  getPermitTimeline,
} from '@welldot/utils';

getPermitHistory(permit); // administrative steps sorted by date
getOverduePermitHistory(permit); // steps past due_date and not done
getPermitTimeline(permit, 'America/Sao_Paulo'); // steps and fulfillments, newest first
```

## Warnings

```ts
import { getPermitWarnings } from '@welldot/utils';

getPermitWarnings(well); // [] for a consistent file
// [{ code: 'missing_timezone', ids: ['outorga-2023'] }] once location.properties.timezone is removed
```

Permit warnings point at data that is valid but inconsistent: validity in the wrong order, a broken `supersedes` chain, overlapping permits, a daily operating time above 24 h (read as 24), monthly maxima above the annual limit, duplicates, missing identifiers, fulfillments that match no deadline, and, since 2.4, a missing site time zone when conditions have fulfillments. None of them makes a file invalid ([permit validation](/format/spec/v2/object-schemas#validation-warnings-never-rejection-1)). The [docs MCP server](/ai/mcp) returns them alongside the validation result.

## Time zone

Local dates are computed in `location.properties.timezone` when the file has one, otherwise from the offset written in each instant ([datetime conventions](/format/spec/v2/format-reference#datetime-conventions)). `getWellTimeZone(well)` returns the zone and `instantLocalDate(instant, timeZone?)` converts an instant to its local date.
