Data Model

Vault format

The vault is a single JSON file encrypted with GnuPG. When decrypted, its structure is:

{
  "version": 2,
  "cards": {
    "<uuid>": { ... },
    "<uuid>": { ... }
  }
}

The version field is an integer. The cards field is a dictionary mapping card IDs (UUID4 strings) to card objects.

Card

A card represents one person. It is the primary unit of storage.

{
  "id":            "550e8400-e29b-41d4-a716-446655440000",
  "name":          "John Joseph Bonforte",
  "aliases":       ["Lawrence Smith", "The Minister"],
  "tags":          ["politician", "vip"],
  "facts":         {
    "occupation":  "Minister of Foreign Affairs",
    "organisation":"Expansionist Party",
    "born":        "1918"
  },
  "notes":         [
    "2026-05-03  Never mention the 2115 election."
  ],
  "relationships": {
    "aide":        "Dak Broadbent",
    "secretary":   "Penny Russell"
  },
  "events":        [ ... ],
  "created_at":    "2026-05-01T10:00:00+00:00",
  "updated_at":    "2026-05-04T14:22:11+00:00"
}

Fields

id : UUID4 string. Assigned at creation. Never changes.

name : Canonical display name. Used for exact-match resolution.

aliases : List of alternate names. Included in fuzzy search alongside the canonical name.

tags : List of labels. Used for tag filtering in the list view.

facts : Dictionary of key-value strings. No fixed schema. Standard keys presented in the edit view are occupation, organisation, born, languages, location, nationality. Any key is valid.

notes : List of strings. Notes added through the interface are prefixed with an ISO date: "2026-05-04 text".

relationships : Dictionary mapping role strings to name strings. The name is stored as a plain string, not a card ID. Resolution to a card happens at query time by exact name match.

events : List of event objects. See below.

created_at, updated_at : ISO 8601 timestamps with UTC timezone offset. updated_at is refreshed on every write to the card.

Event

An event is an append-only log entry attached to a card.

{
  "id":        "7c9e6679-7425-40de-944b-e07fc1f90ae7",
  "timestamp": "2026-05-02T09:15:00+00:00",
  "type":      "meeting",
  "content":   "Met at the spaceport."
}

id : UUID4 string. Assigned at creation.

timestamp : ISO 8601 timestamp with UTC timezone offset. Set at creation. Cannot be changed through the interface.

type : Free-form string. Standard values are meeting, call, message, observation, reminder. Any string is accepted.

content : The event note. Editable after creation through the Events tab.

Events are displayed newest first in the interface. The order in the JSON file is chronological (oldest first, as appended).

Relationships

Relationships are stored as a dictionary mapping roles to lists of names:

"relationships": {
  "aide":    ["Dak Broadbent"],
  "child":   ["Alice", "Bob"],
  "sibling": ["Carol"]
}

The role is the key. The value is a list of name strings. A role can hold any number of names — there is no uniqueness constraint on the list beyond deduplication within a single role.

When the interface displays a relationship, it attempts to resolve the name to a card by exact case-insensitive match. If a matching card exists, the relationship display may show context from that card. If no card matches, the name is shown as-is.

This design allows recording relationships before a card exists for the related person. It also means that renaming a card does not automatically update relationship references pointing to it. Update those manually via the Relations tab.

Search uses the RapidFuzz WRatio scorer with a default score cutoff of 60. Queries are matched against card names and aliases. Results are deduplicated by card ID (a card matched by alias and by name appears only once, at the higher score).

Vault version history

Version Change
1 Initial schema. Cards stored in entries key. Named fields: occupation, born, etc. Interactions stored as {date, note}.
2 entries renamed to cards. Named fields replaced by facts dict. Interaction renamed to Event with added id and type fields. sensitive_topics replaced by notes list.
3 relationships values changed from str to list[str]. Allows multiple targets per role.

Inspecting the vault

To inspect the decrypted vault directly:

gpg -d ~/.local/share/filecard/vault.gpg | python3 -m json.tool | less

This decrypts to stdout and pipes through the standard JSON formatter. The decrypted content is not written to disk.