Guides

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). 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).

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

Status

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:

StatusWhen
requested, suspended, revoked, denied, withdrawnThe stored administrative status says so (administrative status).
supersededAnother permit names this one in supersedes and has started.
not_yet_validGranted, but valid_from is in the future.
activeIn force today.
active_pending_renewalPast valid_until, with a renewal requested in time.
expiredPast 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). The deadlines themselves are derived:

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). 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

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

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). The docs MCP server 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). getWellTimeZone(well) returns the zone and instantLocalDate(instant, timeZone?) converts an instant to its local date.