# Data Model Reference > Canonical type definitions and data modeling rules for ArchiTools. --- ## Overview This document defines the shared data types, entity conventions, and relationship patterns used across all ArchiTools modules. Module-specific types extend these base types. All types live in TypeScript; the runtime representation is JSON stored through the storage abstraction layer. --- ## Base Entity Every storable entity in the system extends `BaseEntity`: ```typescript // src/types/base.ts interface BaseEntity { /** Unique identifier. Generated via crypto.randomUUID() or nanoid. */ id: string; /** ISO 8601 timestamp. Set once at creation, never modified. */ createdAt: string; /** ISO 8601 timestamp. Updated on every mutation. */ updatedAt: string; /** User ID of the creator. Omitted until auth is implemented. */ createdBy?: string; /** Controls who can see this entity. */ visibility: Visibility; /** Cross-module tags attached to this entity. Array of tag IDs. */ tags: string[]; /** Which company owns this entity. 'group' for shared/cross-company items. */ company: CompanyId; } ``` ### Rules 1. `id` is always a string. Use `crypto.randomUUID()` for generation. Never use sequential integers (they create conflicts across clients and are not merge-safe). 2. `createdAt` is set once during creation and must never change. 3. `updatedAt` is set on every save operation, including the initial creation. 4. `createdBy` is `undefined` until authentication is implemented. When auth ships, it becomes a required field on new entities. Existing entities with `undefined` are treated as created by the system. 5. `tags` stores tag IDs (not labels). Tags are resolved through the tag service. 6. `company` defaults to `'group'` for entities that are not company-specific. --- ## Company Model ```typescript // src/types/company.ts type CompanyId = 'beletage' | 'urban-switch' | 'studii-de-teren' | 'group'; interface Company { id: CompanyId; name: string; // Full legal name: "Beletage SRL" shortName: string; // Display name: "Beletage" cui: string; // Romanian CUI (tax ID) address?: string; // Registered address email?: string; // Primary contact email phone?: string; // Primary phone number } ``` ### Company Registry Defined as static configuration in `src/config/companies.ts`: ```typescript export const COMPANIES: Record = { beletage: { id: 'beletage', name: 'Beletage SRL', shortName: 'Beletage', cui: '...', // populated in config }, 'urban-switch': { id: 'urban-switch', name: 'Urban Switch SRL', shortName: 'Urban Switch', cui: '...', }, 'studii-de-teren': { id: 'studii-de-teren', name: 'Studii de Teren SRL', shortName: 'Studii de Teren', cui: '...', }, group: { id: 'group', name: 'Grup Beletage', shortName: 'Grup', cui: '', }, }; ``` `'group'` is a virtual company representing the entire organization. Used for shared resources (templates, tags, prompts) that are not company-specific. --- ## Visibility and Roles ### Visibility ```typescript // src/types/visibility.ts type Visibility = 'all' | 'internal' | 'admin' | 'guest'; ``` | Value | Who can see | Use case | |---|---|---| | `all` | Everyone including future external/guest users | Public templates, shared contacts | | `internal` | All authenticated internal users | Most operational data | | `admin` | Admin users only | Passwords, sensitive config | | `guest` | Guest users and above | Curated external-facing views | Until authentication is implemented, all data is treated as `internal`. The visibility field is stored but not enforced at the storage layer. Enforcement will be added at the API/middleware layer when auth ships. ### Roles ```typescript // src/types/roles.ts type Role = 'admin' | 'user' | 'viewer' | 'guest'; ``` | Role | Permissions | |---|---| | `admin` | Full CRUD on all entities. Access to all modules. System configuration. | | `user` | CRUD on own entities. Access to enabled modules. | | `viewer` | Read-only access to entities matching their visibility level. | | `guest` | Read-only access to `guest` and `all` visibility entities. Limited module access. | ### Future Auth Integration When Authentik SSO is integrated: ```typescript interface UserProfile { id: string; email: string; name: string; role: Role; companies: CompanyId[]; // Companies this user belongs to modules: string[]; // Explicitly allowed modules (empty = all for role) } ``` Users may belong to multiple companies. Entity filtering will combine role, visibility, and company membership. --- ## Tag Model Tags are a cross-module resource managed by the Tag Manager module but consumed everywhere. ```typescript // src/types/tag.ts interface Tag { /** Unique tag ID. */ id: string; /** Human-readable label. Romanian. Example: "076 Casa Copernicus" */ label: string; /** * Tag category for grouping. * Examples: 'project', 'client', 'phase', 'type', 'priority', 'domain' */ category: TagCategory; /** Display color. Hex string: "#3B82F6" */ color: string; /** * If set, this tag is only visible/usable within the specified modules. * If undefined or empty, the tag is available globally. */ moduleScope?: string[]; /** Company this tag belongs to. 'group' for cross-company tags. */ company: CompanyId; /** Whether this tag is active. Archived tags are hidden from selectors but preserved on entities. */ archived: boolean; createdAt: string; updatedAt: string; } type TagCategory = | 'project' | 'client' | 'phase' | 'type' | 'priority' | 'domain' | 'custom'; ``` ### Project Reference Pattern Tags with category `'project'` follow the ManicTime naming convention used in the office: ``` "{number} {project name}" ``` Examples: - `"076 Casa Copernicus"` - `"081 PUZ Sector 3"` - `"092 Reabilitare Scoala Nr 5"` The number is a sequential project identifier scoped per company. The tag label is the canonical reference; there is no separate `projectNumber` field. **Parsing utility:** ```typescript // src/lib/tags/utils.ts interface ParsedProjectTag { number: string; // "076" name: string; // "Casa Copernicus" raw: string; // "076 Casa Copernicus" } function parseProjectTag(label: string): ParsedProjectTag | null { const match = label.match(/^(\d{3})\s+(.+)$/); if (!match) return null; return { number: match[1], name: match[2], raw: label }; } ``` ### Tag Relationships Tags create implicit relationships between entities across modules. For example: - A registratura entry tagged `"076 Casa Copernicus"` is linked to the same project as a word template with the same tag. - Searching by tag ID across all namespaces yields every entity related to that project. This is the primary cross-module linking mechanism until a dedicated relational backend is introduced. --- ## Module-Specific Entity Types Each module defines its own entity types that extend `BaseEntity`. Below is an overview of the primary entity per module. ### Registratura ```typescript // src/modules/registratura/types.ts interface RegistryEntry extends BaseEntity { /** Sequential registry number per company per year. "B-2025-0042" */ registryNumber: string; /** Entry or exit. */ direction: 'incoming' | 'outgoing'; /** Date the document was registered. ISO 8601 date (not datetime). */ registryDate: string; /** Document title / description. */ title: string; /** Sender (for incoming) or recipient (for outgoing). */ correspondent: string; /** Number of physical pages, if applicable. */ pageCount?: number; /** Reference to another registry entry (e.g., reply to incoming). */ referenceTo?: string; /** Free-text notes. */ notes?: string; /** Attached file references (future: MinIO object keys). */ attachments: string[]; } ``` ### IT Inventory ```typescript // src/modules/it-inventory/types.ts type DeviceType = 'laptop' | 'desktop' | 'monitor' | 'printer' | 'router' | 'switch' | 'nas' | 'phone' | 'tablet' | 'peripheral' | 'other'; type DeviceStatus = 'active' | 'storage' | 'repair' | 'retired' | 'disposed'; interface InventoryItem extends BaseEntity { name: string; deviceType: DeviceType; manufacturer?: string; model?: string; serialNumber?: string; purchaseDate?: string; warrantyExpiry?: string; assignedTo?: string; location?: string; status: DeviceStatus; specs?: Record; // CPU, RAM, storage, etc. notes?: string; } ``` ### Address Book ```typescript // src/modules/address-book/types.ts type ContactType = 'person' | 'company' | 'institution' | 'supplier'; interface AddressContact extends BaseEntity { contactType: ContactType; name: string; organization?: string; role?: string; email?: string; phone?: string; address?: string; city?: string; county?: string; country?: string; cui?: string; // For companies/institutions iban?: string; bank?: string; website?: string; notes?: string; favorite: boolean; } ``` ### Prompt Generator ```typescript // src/modules/prompt-generator/types.ts type PromptDomain = 'architecture' | 'legal' | 'technical' | 'gis' | 'rendering' | 'urbanism' | 'bim' | 'procurement' | 'administrative'; type PromptTargetAI = 'text' | 'image' | 'code' | 'review' | 'rewrite'; interface PromptTemplate extends BaseEntity { name: string; category: string; domain: PromptDomain; description: string; targetAI: PromptTargetAI; blocks: PromptBlock[]; variables: PromptVariable[]; outputMode: OutputMode; providerProfile?: string; version: number; parentTemplateId?: string; // For cloned/forked templates favorite: boolean; } ``` ### Email Signature ```typescript // src/modules/email-signature/types.ts interface SignatureConfig extends BaseEntity { employeeName: string; jobTitle: string; email: string; phone?: string; mobile?: string; /** Generated HTML output. */ generatedHtml: string; } ``` ### Digital Signatures ```typescript // src/modules/digital-signatures/types.ts type SignatureAssetType = 'signature' | 'stamp' | 'initials'; interface SignatureAsset extends BaseEntity { name: string; assetType: SignatureAssetType; /** Base64-encoded PNG for localStorage. MinIO object key for API backend. */ imageData: string; owner?: string; notes?: string; } ``` ### Password Vault ```typescript // src/modules/password-vault/types.ts interface VaultEntry extends BaseEntity { service: string; url?: string; username: string; /** Stored as plaintext in localStorage (demo-grade security). Encrypted in API backend. */ password: string; notes?: string; category?: string; lastRotated?: string; } ``` > **Security note:** The localStorage adapter stores passwords in plaintext. This is acceptable only for internal demo use. The API backend must encrypt the `password` field at rest. ### Word Templates / Word XML Generators ```typescript // src/modules/word-templates/types.ts type TemplateCategory = 'contract' | 'offer' | 'report' | 'letter' | 'header' | 'certificate' | 'minutes' | 'other'; interface WordTemplate extends BaseEntity { name: string; category: TemplateCategory; description?: string; /** File reference: base64 for localStorage, MinIO key for API backend. */ fileData: string; fileName: string; fileSize: number; version: number; } ``` --- ## Naming Conventions ### Type Names | Convention | Example | Used For | |---|---|---| | PascalCase | `RegistryEntry` | Interfaces, type aliases, classes | | camelCase | `registryNumber` | Properties, variables, functions | | UPPER_SNAKE_CASE | `COMPANIES` | Constants, static config objects | | kebab-case | `'urban-switch'` | String literal union members (IDs, slugs) | ### Entity Type Naming - Main entity per module: descriptive noun (`RegistryEntry`, `InventoryItem`, `AddressContact`). - Do not prefix with module name unless ambiguity exists. - Supporting types (enums, sub-objects): use descriptive names scoped by context (`DeviceType`, `ContactType`). - Union string literal types: use the values themselves, not a wrapper. Prefer `type Direction = 'incoming' | 'outgoing'` over an enum. ### File Naming - Type definition files: `types.ts` in the module root. - Shared types: `src/types/{name}.ts`. - Never put types in a barrel `index.ts` that also exports components (causes circular dependency issues with lazy loading). --- ## Schema Versioning ### Strategy Each module's data schema has a version number tracked in the namespace metadata: ```typescript // Stored at key "_meta" within each namespace interface NamespaceMeta { schemaVersion: number; lastMigration: string; itemCount: number; } ``` ### Migration Registration ```typescript // src/modules/registratura/migrations.ts import { defineMigrations } from '@/lib/storage/migrations'; export const registraturaMigrations = defineMigrations('registratura', [ { version: 2, description: 'Add attachments array to registry entries', up: (data: Record) => { for (const [key, value] of Object.entries(data)) { if (key === '_meta') continue; const entry = value as Record; if (!entry.attachments) { entry.attachments = []; } } return data; }, }, { version: 3, description: 'Rename sender field to correspondent', up: (data: Record) => { for (const [key, value] of Object.entries(data)) { if (key === '_meta') continue; const entry = value as Record; if ('sender' in entry) { entry.correspondent = entry.sender; delete entry.sender; } } return data; }, }, ]); ``` ### Migration Execution On module initialization, the storage layer: 1. Reads `_meta.schemaVersion` for the module's namespace. 2. Compares to the latest registered migration version. 3. If behind, runs each migration sequentially (`version N` to `version N+1`). 4. Updates `_meta.schemaVersion` after each successful migration. 5. If any migration fails, the process halts and logs an error. The module should display a degraded state rather than corrupting data. --- ## Entity Relationships ArchiTools uses a flat document model (not relational). Relationships are expressed through: ### 1. Tags (Primary Cross-Module Link) Entities share project tags. Querying all namespaces for a tag ID returns every entity related to that project. ``` RegistryEntry.tags: ["tag-076-casa-copernicus"] WordTemplate.tags: ["tag-076-casa-copernicus"] PromptTemplate.tags: ["tag-076-casa-copernicus"] ``` ### 2. Direct References Some entities reference others by ID within the same namespace: - `RegistryEntry.referenceTo` points to another registry entry ID. - `PromptTemplate.parentTemplateId` points to the template it was cloned from. ### 3. Company Scoping The `company` field on every entity enables filtering by company. A user at Beletage sees Beletage and group entities; never Urban Switch entities (unless their role grants cross-company access). ### 4. Future: Explicit Project Entity When the platform matures, a `Project` entity may be introduced: ```typescript interface Project extends BaseEntity { number: string; // "076" name: string; // "Casa Copernicus" company: CompanyId; status: 'active' | 'completed' | 'archived'; client?: string; // AddressContact ID startDate?: string; endDate?: string; } ``` At that point, tags with category `'project'` would reference a `Project.id`, and the project number/name would be the single source of truth. Until then, the tag label is the canonical project identifier. --- ## Shared Type Exports All shared types are re-exported from `src/types/index.ts`: ```typescript // src/types/index.ts export type { BaseEntity } from './base'; export type { CompanyId, Company } from './company'; export type { Visibility } from './visibility'; export type { Role, UserProfile } from './roles'; export type { Tag, TagCategory } from './tag'; ``` Module-specific types are not exported from this barrel. They are imported directly from the module: ```typescript import type { RegistryEntry } from '@/modules/registratura/types'; ``` This keeps the shared type surface minimal and avoids pulling module code into unrelated bundles.