For the complete documentation index, see llms.txt. This page is also available as Markdown.

Application Reference

An Application Reference is the human-readable identifier for an intake-form submission. Staff Portal shows it as Application Reference. Operators and applicants use it to find a draft or submitted form. APIs and the database still use submission_id (a UUID) as the primary key.

It is not a Functional ID. A Functional ID (functional_record_id) is assigned later, after the submission is approved and ingested into a Register. An Application Reference exists from the moment the intake submission row is created.

Default format: {DATE:%Y%b%d|upper}-{SECONDS:5}{RAND:1} → example 2026AUG21-519070.

Override it with one environment variable on each API that creates intake submissions. An invalid format prevents that process from starting.

Identifiers compared

Identifier
Assigned
Stored on
Audience

submission_id

On create (UUID)

Intake submission (PK) and section rows

APIs, workers, internal joins

application_reference

On create (format string)

Intake submission (unique) and copied onto section rows

Staff Portal list, breadcrumbs, search/display names

functional_record_id

After ingest into a Register

Live register rows

Real-world registry ID (Farmer ID, Household ID, …)

When it is generated

G2PIntakeFormSubmission.application_reference has a SQLAlchemy default that calls generate_application_reference(). The value is written on insert - typically the first save of a new intake form, while the submission is still DRAFT.

The generator is constructed once during core initialization:

generate_application_reference(now: datetime | None = None) -> str then uses that singleton. If no generator is registered (tests / isolated core), it compiles Settings.application_reference_format on the fly.

The same value is stamped onto each intake section row (G2PIntakeForm.application_reference) when the section is saved, so list/search helpers can read it from the row payload. After ingest, live register rows do not store application_reference.

Configuration

The setting is application_reference_format on registry core Settings (env_prefix="registry_core_"). Each API subclass inherits the field but uses its own env prefix. Intake submissions are created by those APIs, and each process registers its Settings before core initialization.

Process

Env var

Helm envVars key

Staff Portal API

REGISTRY_STAFF_PORTAL_API_APPLICATION_REFERENCE_FORMAT

staffApi.envVars

Partner API

REGISTRY_PARTNER_API_APPLICATION_REFERENCE_FORMAT

partnerApi.envVars

Beneficiary Portal API

REGISTRY_BENE_PORTAL_API_APPLICATION_REFERENCE_FORMAT

beneApi.envVars

Core Settings in isolation (local / tests)

REGISTRY_CORE_APPLICATION_REFERENCE_FORMAT

-

Pydantic Settings treats env names as case-insensitive. Quotes around the value are required in shells when the format contains { } or |.

Helm example (Staff Portal API):

Set the same format on every API that can create submissions. Otherwise Staff Portal drafts, Partner-created submissions, and Beneficiary Portal submissions will look like different numbering schemes.

The format is compiled and test-rendered at startup. A bad format raises ApplicationReferenceFormatError and the process does not come up.

Format syntax

A format string is literals plus tokens.

  1. Literals - any text outside {...} is copied exactly (APP-, -, /).

  2. Tokens - {TOKEN}, {TOKEN:argument}, or {TOKEN:argument|upper}.

Token names are case-insensitive. The only modifier is |upper, and it is applied to DATE and TIME segments (month/weekday names from strftime). Other tokens ignore |upper. {UUID8} is already uppercase hex.

Token reference

Token
Syntax
What it renders
Limits

DATE

{DATE:<strftime>}

datetime.strftime of the generation time

Requires a valid Python strftime pattern

TIME

{TIME:<strftime>}

Same as DATE; use for clock-time segments

Requires a valid Python strftime pattern

SECONDS

{SECONDS:<width>}

Zero-padded seconds since midnight (086399)

Width 1–5

EPOCH

{EPOCH:<width>}

Zero-padded Unix epoch seconds

Width 1–12. Width is minimum padding, not truncation - a 10-digit epoch will still render 10 digits if you pass {EPOCH:5}

RAND

{RAND:<width>}

Zero-padded decimal digits (09), from secrets.randbelow

Width 1–12

RAND_ALNUM

{RAND_ALNUM:<width>}

Random A–Z and 0–9

Width 1–12

UUID8

{UUID8}

First 8 hex characters of a UUID4, uppercased

No argument

Unknown tokens, empty format strings, missing widths, and invalid strftime patterns are rejected at compile time.

DATE / TIME strftime patterns

DATE and TIME accept any valid Python strftime pattern. The compiler checks the pattern against a fixed timestamp (2001-01-01 12:30:45).

Pattern
Example
Notes

%Y%m%d

20260821

Compact ISO-style date

%Y%b%d|upper

2026AUG21

Year + uppercase abbreviated month + day (default date segment)

%d%m%Y

21082026

Day-first numeric date

%d%b%Y

21Aug2026

Day + abbreviated month + year

%d%b%Y|upper

21AUG2026

Uppercase month via |upper

%d%B%Y

21August2026

Full month name (length varies by month)

%Y/%m/%d

2026/08/21

Slashes inside the token; or put them in literals

%H%M%S

143025

24-hour time

%H:%M:%S

14:30:25

Time with colons

Month and weekday names follow the process locale (usually English in container images).

Example formats

Default

The last six characters are five-digit seconds-since-midnight plus one random digit. Fine for low concurrent traffic; see Uniqueness before using this in production.

Day-first date with time

Agency prefix and uppercase month

Slashes in literals

Alphanumeric random segment

UUID fragment

Limits and validation

Rule
Value

Maximum rendered length

64 characters (checked at compile using an estimated max, and again at render)

{RAND} / {RAND_ALNUM} width

1–12

{SECONDS} width

1–5

{EPOCH} width

1–12

Empty format

Rejected

Unknown token

Rejected

Invalid strftime

Rejected

{UUID8} with an argument

Rejected

Modifier other than upper

Rejected

Uniqueness

g2p_intake_form_submissions.application_reference has a unique constraint. There is no retry if two concurrent inserts collide.

Entropy comes from the time tokens plus {RAND}, {RAND_ALNUM}, or {UUID8}. {SECONDS:5} distinguishes submissions in different seconds of the same day. Within the same second you need enough random width:

Random token
Values per second (same date/seconds prefix)

{RAND:1} (default)

10

{RAND:5}

100,000

{RAND_ALNUM:6}

36⁶

{UUID8}

16⁸ (hex)

Display names

The base domain service appends application_reference to the intake record_name when it is not already present:

Override construct_intake_record_name when the intake list should lead with the reference (common on master registers). Include application_reference in construct_search_text if staff will type it into search.

Live register construct_record_name should not depend on application_reference - that column is not on register tables.

Signatures (code)

compile is what validates the implementer-supplied format at startup. generate renders it for the current time (datetime.now() unless now is passed).

Last updated

Was this helpful?