# 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<CompanyId, Company> = {
  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<string, string>;  // 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;
}
```

### Email Notifications (platform service)

```typescript
// src/core/notifications/types.ts

type NotificationType = "deadline-urgent" | "deadline-overdue" | "document-expiry";

interface NotificationPreference {
  userId: string;
  email: string;
  name: string;
  company: CompanyId;
  enabledTypes: NotificationType[];
  globalOptOut: boolean;
}

interface DigestItem {
  entryNumber: string;
  subject: string;
  label: string;
  dueDate: string;        // YYYY-MM-DD
  daysRemaining: number;  // negative = overdue
  color: "red" | "yellow" | "blue";
}

interface DigestSection {
  type: NotificationType;
  title: string;
  items: DigestItem[];
}
```

> **Storage:** Preferences stored in `KeyValueStore` (namespace `notifications`, key `pref:<userId>`). No separate Prisma model needed.

---

## 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<string, unknown>) => {
      for (const [key, value] of Object.entries(data)) {
        if (key === '_meta') continue;
        const entry = value as Record<string, unknown>;
        if (!entry.attachments) {
          entry.attachments = [];
        }
      }
      return data;
    },
  },
  {
    version: 3,
    description: 'Rename sender field to correspondent',
    up: (data: Record<string, unknown>) => {
      for (const [key, value] of Object.entries(data)) {
        if (key === '_meta') continue;
        const entry = value as Record<string, unknown>;
        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.
