# Data Model

All domain entities live in `src/OtisEd.Nimble.Domain/`. Most use `Guid` as their primary key. `NimbleDbContext` in `src/OtisEd.Nimble.EntityFrameworkCore/` holds 60+ `DbSet` properties and serves as the single EF Core context, replacing both `IIdentityProDbContext` and `ISaasDbContext` to enable cross-module JOINs.

## Table of Contents

* [Base Classes and Conventions](#base-classes-and-conventions)
* [Multi-Tenancy Approach](#multi-tenancy-approach)
* [Entity Inventory by Domain](#entity-inventory-by-domain)
  * [Reporting Hierarchy](#reporting-hierarchy)
  * [Container and Card System](#container-and-card-system)
  * [Navigation and Security](#navigation-and-security)
  * [Data Collection](#data-collection)
  * [Organizational Data Import](#organizational-data-import)
  * [Content and Reference Data](#content-and-reference-data)
  * [Approval and Flagging](#approval-and-flagging)
  * [Messaging](#messaging)
  * [User Filters and Background Job Tracking](#user-filters-and-background-job-tracking)
  * [ABP Framework Entities](#abp-framework-entities)
  * [Reporting Data Views](#reporting-data-views)
* [Key Enums](#key-enums)
* [JSON Configuration Pattern](#json-configuration-pattern)
* [Data Seeding](#data-seeding)
* [DbContext Structure](#dbcontext-structure)

***

## Base Classes and Conventions

### Audit Base Classes

| Base Class                 | Tracks                     | Soft Delete                                    |
| -------------------------- | -------------------------- | ---------------------------------------------- |
| `FullAuditedEntity<T>`     | Created, modified, deleted | Yes (`IsDeleted`, `DeletionTime`, `DeleterId`) |
| `AuditedEntity<T>`         | Created, modified          | No                                             |
| `CreationAuditedEntity<T>` | Created only               | No                                             |
| `Entity<T>`                | None                       | No                                             |

The majority of domain entities use `FullAuditedEntity<Guid>`. Exceptions are documented per entity below.

### Multi-Tenant Marker

Entities that implement `IMultiTenant` carry a nullable `TenantId` (`Guid?`) property. ABP's EF Core interceptors automatically filter queries by the current tenant's `TenantId`. A `null` TenantId indicates host-level (shared across all tenants).

### Constructor Validation

Entities validate constructor arguments using `Volo.Abp.Check`:

* `Check.NotNull()` for required reference fields
* `Check.Length()` with max/min lengths from the corresponding `*Consts` class in `Domain.Shared`
* Mutable properties with side-effects are exposed as `SetXxx()` methods (rather than public setters) and re-validate on assignment
* Entity IDs use protected setters; tests must use reflection or `RepositoryMockHelper.SetEntityId()`

### WithNavigationProperties Projections

Many aggregates have a companion class for eager-loaded queries: `XxxWithNavigationProperties`. These are projection types, not entities, and are used to return related lookup data in a single query. Corresponding DTOs in `Application.Contracts` are named `XxxWithNavigationPropertiesDto`.

***

## Multi-Tenancy Approach

* Row-level isolation: all tenants share the same SQL Server schema; ABP applies `TenantId` filters via EF Core query interceptors
* `NimbleDbContext` uses `[ConnectionStringName("Default")]`; per-tenant connection strings are supported via the `TenantConnectionString` entity
* `NimbleTenantDatabaseMigrationHandler` runs migrations for each tenant on startup
* Known tenants: KDE (Kentucky), KYSRC (Kentucky SRC), NDDPI (North Dakota), VIDE (US Virgin Islands)

Entities **not** marked `IMultiTenant` are global/host-level data shared across all tenants. These include: `DcAgency`, `DcFileRequest`, `Glossary`, `Card`, `Page`, `CardData`, `StoreMessage`, `ReferenceItem`.

***

## Entity Inventory by Domain

### Reporting Hierarchy

```
ReportPage (Guid, IMultiTenant, FullAudited)
  Properties: Name, Description, Configuration (JSON), Filters (JSON), PageType
  |
  +-- ReportGroup (Guid, IMultiTenant, FullAudited)
  |   Properties: Title, Description, SortOrder, IsActive, Configuration (JSON),
  |               Filters (JSON), GroupType, ReportCount, About
  |   FK: ReportPageId -> ReportPage
  |   Self-ref FK: ReportGroupId -> ReportGroup (nesting)
  |   |
  |   +-- Report (Guid, IMultiTenant, FullAudited)
  |   |   Properties: Title, Description, ExternalId, Parameters (JSON),
  |   |               Width, Height, StoredProcedure, FilterProcedure, IsActive,
  |   |               SortOrder, Configuration (JSON), Filters (JSON), URLExtensions
  |   |   FK: ReportTypeId -> ReportType
  |   |   |
  |   |   +-- ReportFilter (Guid, IMultiTenant)
  |   |   |   Properties: Label, Name, DefaultValue, SortOrder, Cascading, ExternalId
  |   |   |   FK: ReportId -> Report, ReportFilterTypeId -> ReportFilterType
  |   |   |
  |   |   +-- ReportColumn (Guid) — column definitions for tabular reports
  |   |   +-- ReportLayout (Guid) — layout configuration per report
  |   |
  |   +-- ReportGroupContainer — junction: links ReportGroups to Containers
  |
  +-- ReportList (Guid) — ordered list of reports

SiteReport extends Report
  Additional: List<ReportColumn> Columns (navigation property)
  Used for MicroStrategy-backed reports

ReportType (Guid) — classification lookup; data-seeded
ReportFilterType (Guid) — reusable filter type definitions; data-seeded
ReportColumnType (Guid) — reusable column type definitions; data-seeded
ReportServer (Guid) — report server connection configuration; data-seeded
```

#### Related

| Entity                     | Base                                    | Purpose                                             |
| -------------------------- | --------------------------------------- | --------------------------------------------------- |
| `ReportAccessLog`          | `CreationAuditedEntity<Guid>`           | Tracks user report views                            |
| `ReportFlag`               | `AuditedEntity<Guid>`                   | User flags on reports per org unit/term with status |
| `ReportCardDomain`         | `Entity<Guid>`                          | Domain categorization for report cards              |
| `UserReportPageFilter`     | `FullAuditedEntity<Guid>`, IMultiTenant | Per-user saved filter state for a report page       |
| `ReportFilterJob`          | `CreationAuditedEntity<Guid>`           | Background filter generation job tracking           |
| `ReportFilterResult`       | —                                       | Individual filter generation results                |
| `UserPageFilterRebuildJob` | —                                       | Tracks rebuild jobs for user page filters           |

***

### Container and Card System

```
Container (Guid, IMultiTenant, FullAudited)
  Properties: Name, Description, Title, IsActive, RowHeight (default from ContainerConsts.DefaultRowHeight)
  Navigation: ICollection<ContainerCard> ContainerCards
  |
  +-- ContainerCard (Guid, IMultiTenant, FullAudited)
  |   Properties: ContainerId (FK), ContainerCardId (string), CardTypeId (string)
  |               Order, Configuration (JSON), DataSource (JSON), FilterSource (JSON)
  |               RefreshStatus, RefreshComments, RefreshStartTime, RefreshEndTime
  |   Domain method: SetConfiguration() — validates max length before assignment
  |   |
  |   +-- ContainerCardData (Guid, IMultiTenant, FullAudited)
  |       Properties: ContainerCardId (FK), AppTermId (int?), OrganizationUnitId (Guid?)
  |                   Data (JSON), SecuredData (JSON), Filters (JSON), Included (bool)
  |       Domain methods: SetData(), SetSecuredData(), SetFilters() — validate max length

Card (Guid, FullAudited) — NOT multi-tenant
  Properties: Type, Name, Description, Configuration (JSON)

Page (Guid, FullAudited) — NOT multi-tenant
  Properties: PageType, Title, Description

PageCard — junction linking Pages to Cards
  FK: PageId -> Page, CardId -> Card

CardData (Guid, AuditedEntity) — NOT multi-tenant, NOT full-audited
  Properties: SchoolYear (int), EntityId (Guid?), JsonData (JSON)
  FK: CardId -> Card
```

***

### Navigation and Security

```
Route (int PK, IMultiTenant)
  Properties: Name, Path, Component (React component name), IsPublic, IsActive
  |
  +-- SecurityRoute — links routes to roles
  +-- UserRoute — links routes to individual users

Menu (int PK, IMultiTenant, FullAudited)
  Properties: Name, Icon, ExternalUrl, SortOrder, Parameters, Target, IsActive
  Self-ref FK: ParentId -> Menu (hierarchy)
  FK: RouteId -> Route
  |
  +-- SecurityMenu — links menus to roles
  +-- UserMenu — links menus to individual users

Module (Guid, IMultiTenant)
  Properties: Name, Component, SortOrder
  FK: RouteId -> Route
  |
  +-- SecurityModule — links modules to roles
  +-- UserModule — links modules to individual users

SecurityUser (int PK, IMultiTenant)
  Properties: UserId (FK -> IdentityUser), OrganizationUnitId, RoleId, IsDefault, CreatorId
```

***

### Data Collection

```
DcAgency (Guid, FullAudited) — NOT multi-tenant
  Properties: Code, Name, Description, AgencyStatus (int)
              AzureSasUrl, AzureFolder, AzureDataSource
  FK: AgencyTypeId -> DcAgencyType, OrganizationUnitId

DcAgencyType (Guid) — Agency classification

DcFileRequest (Guid, FullAudited) — NOT multi-tenant
  Properties: Term, Name, Description, RowCount, UploadedOn, UploadedBy, AzureFileUrl
              LastStatus (DcFileRequestStatus enum), LastStage (FileProcessStage enum), LastMessage
  FK: AgencyId -> DcAgency, FileTypeId -> DcFileType

DcFileType (Guid) — file type definitions
  FK: DcFileTypeCategoryId -> DcFileTypeCategory, DcFileTypeGroupId -> DcFileTypeGroup

DcFileTypeCategory (Guid) — category taxonomy
DcFileTypeGroup (Guid) — group taxonomy

DcForm (Guid) — links to Volo.Forms; event bus subscriber on form changes
  Subscriber: FormBusSubscribeAppService

FileProcessHistory (Guid) — processing pipeline stage tracking
```

***

### Organizational Data Import

```
Import (int PK, IMultiTenant)
  Properties: StartDate, EndDate, RecordsProcessed, ImportTypeId

ImportType (Guid) — import type definitions; data-seeded
ImportUpdateType (Guid) — update type classifications; data-seeded

ImportDistrict (Guid, IMultiTenant) — district import records
ImportSchool (Guid, IMultiTenant) — school import records
ImportUnit (Guid, IMultiTenant) — unit import records
ImportMember (Guid, IMultiTenant) — member import records
```

***

### Content and Reference Data

| Entity          | Key                                 | Multi-Tenant | Notes                                                                   |
| --------------- | ----------------------------------- | ------------ | ----------------------------------------------------------------------- |
| `Glossary`      | `Guid`, `FullAudited`               | No           | `SetGroupName()` auto-derives `Group` (first letter; "#" for non-alpha) |
| `ReferenceItem` | `Guid`                              | No           | Reference data items for display                                        |
| `Widget`        | `Guid`, IMultiTenant, `FullAudited` | Yes          | Content, Name, Code; self-ref via `ParentWidgetId`                      |
| `WidgetType`    | `int`                               | —            | Lookup; data-seeded                                                     |
| `WidgetGroup`   | `int`                               | —            | Lookup; data-seeded                                                     |
| `AppTerm`       | `Guid`                              | No           | Customizable application terminology/labels                             |
| `SocialMedia`   | `Guid`                              | No           | Org social media links with `SocialMediaType` enum                      |

***

### Approval and Flagging

| Entity             | Base                      | Notes                                                                                                                                     |
| ------------------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `ApprovalStatus`   | `Entity<Guid>` (no audit) | Per-org approval linked to `ReportCardDomain`; fields: `OrganizationID`, `Approved`, `ReportCardDomainId`, `LastUpdatedBy`, `LastUpdated` |
| `ReportFlag`       | `AuditedEntity<Guid>`     | User flag on a report per `OrgUnitId`/`DistrictId`/`SchoolId`/`Term`; `FlagStatus` enum                                                   |
| `ReportCardDomain` | `Entity<Guid>`            | Domain categorization                                                                                                                     |

***

### Messaging

| Entity         | Base                  | Notes                                                        |
| -------------- | --------------------- | ------------------------------------------------------------ |
| `StoreMessage` | `AuditedEntity<Guid>` | NOT multi-tenant; org-scoped message by Year and MessageType |
| `MessageType`  | —                     | Message type definitions; data-seeded                        |

***

### User Filters and Background Job Tracking

| Entity                     | Base                                    | Notes                                                            |
| -------------------------- | --------------------------------------- | ---------------------------------------------------------------- |
| `UserReportPageFilter`     | `FullAuditedEntity<Guid>`, IMultiTenant | Saved filter state per user per report page; `FilterData` (JSON) |
| `ReportFilterJob`          | `CreationAuditedEntity<Guid>`           | Daily filter job run tracking; `Status` enum (Started, …)        |
| `ReportFilterResult`       | —                                       | Per-report result within a filter job                            |
| `UserPageFilterRebuildJob` | —                                       | Tracks user page filter rebuild jobs                             |
| `SyncFileBackgroundJob`    | —                                       | Background file sync tracking                                    |
| `ReportAccessLog`          | `CreationAuditedEntity<Guid>`           | Report access tracking                                           |

***

### ABP Framework Entities

These entities are managed by ABP modules and are included in `NimbleDbContext` to support cross-module JOINs:

| Entity                                                                                        | Module              | Notes                                                 |
| --------------------------------------------------------------------------------------------- | ------------------- | ----------------------------------------------------- |
| `IdentityUser`, `IdentityRole`, `IdentityClaimType`, `IdentitySecurityLog`, `IdentitySession` | `Volo.Abp.Identity` | Core user/role management                             |
| `OrganizationUnit`                                                                            | `Volo.Abp.Identity` | Hierarchical org structure; central to security model |
| `OrganizationType`                                                                            | Custom extension    | State, District, School, etc.; data-seeded            |
| `Tenant`, `Edition`, `TenantConnectionString`                                                 | `Volo.Saas`         | Multi-tenancy management                              |

***

### Reporting Data Views

Read-only entities backed by stored procedures or database views. These are not full aggregate roots and are used exclusively for query projections:

| View Entity            | Purpose                          |
| ---------------------- | -------------------------------- |
| `SiteReportData`       | MicroStrategy report result rows |
| `SiteReportFilterData` | MicroStrategy filter result data |
| `ReportPageData`       | Report page aggregated data      |
| `ReportData`           | Generic report data              |
| `ReportDropdownData`   | Dropdown filter data             |
| `OrganizationalData`   | Organizational hierarchy data    |
| `SchoolFinderData`     | School search data               |

***

## Key Enums

All enums are defined in `src/OtisEd.Nimble.Domain.Shared/`.

| Enum                    | Values                     | Used By                    |
| ----------------------- | -------------------------- | -------------------------- |
| `DcFileRequestStatus`   | `NotFlagged`, `Flagged`, … | `DcFileRequest.LastStatus` |
| `FileProcessStage`      | Pipeline stage values      | `DcFileRequest.LastStage`  |
| `FlagStatus`            | `NotFlagged`, `Flagged`, … | `ReportFlag.FlagStatus`    |
| `ReportFilterJobStatus` | `Started`, …               | `ReportFilterJob.Status`   |
| `SocialMediaType`       | Social platform types      | `SocialMedia`              |
| `PageType`              | Page classification values | `Page.PageType`            |

***

## JSON Configuration Pattern

Several entities store configuration as serialized JSON strings rather than normalized columns. This trades referential integrity for schema flexibility — adding configuration fields requires no migration.

| Entity              | JSON Property/Properties                      |
| ------------------- | --------------------------------------------- |
| `Container`         | —                                             |
| `ContainerCard`     | `Configuration`, `DataSource`, `FilterSource` |
| `ContainerCardData` | `Data`, `SecuredData`, `Filters`              |
| `Report`            | `Configuration`, `Filters`, `Parameters`      |
| `ReportGroup`       | `Configuration`, `Filters`                    |
| `ReportPage`        | `Configuration`, `Filters`                    |
| `Card`              | `Configuration`                               |
| `CardData`          | `JsonData`                                    |
| `Widget`            | `Content`                                     |

`SetXxx()` domain methods on `ContainerCard` and `ContainerCardData` validate maximum string length (sourced from `*Consts` classes) before persisting JSON.

***

## Data Seeding

Reference and lookup data is inserted deterministically via `IDataSeedContributor` implementations. These run during `DbMigrator` execution and on first application startup.

| Seed Contributor                       | Seeds                                  |
| -------------------------------------- | -------------------------------------- |
| `MenuDataSeedContributor`              | Default menus                          |
| `RouteDataSeedContributor`             | Default routes                         |
| `ReportTypeDataSeedContributor`        | Report types                           |
| `ReportFilterTypeDataSeedContributor`  | Report filter types                    |
| `ReportColumnTypeDataSeedContributor`  | Report column types                    |
| `ReportServerDataSeedContributor`      | Report servers                         |
| `WidgetTypeDataSeedContributor`        | Widget types                           |
| `WidgetGroupDataSeedContributor`       | Widget groups                          |
| `WidgetDataSeedContributor`            | Default widgets                        |
| `ImportTypesDataSeeder`                | Import types                           |
| `ImportUpdateTypesDataSeedContributor` | Import update types                    |
| `OrganizationTypesDataSeedContributor` | Org types (State, District, School, …) |
| `SiteReportsDataSeedContributor`       | Default site reports                   |
| `MessageTypeDataSeedContributor`       | Message types                          |
| `OpenIddictDataSeedContributor`        | OAuth clients and scopes               |
| `SaasDataSeedContributor`              | Default tenants                        |

***

## DbContext Structure

`NimbleDbContext` in `src/OtisEd.Nimble.EntityFrameworkCore/`:

* Inherits from ABP's `AbpDbContext<NimbleDbContext>` and replaces `IIdentityProDbContext` and `ISaasDbContext` to consolidate all entities into one context for cross-module JOIN support
* Annotated `[ConnectionStringName("Default")]`
* Exposes 60+ `DbSet<T>` properties covering all domain entities listed above
* EF Core configurations are defined in `EntityFrameworkCore/` via `IEntityTypeConfiguration<T>` classes
* Migrations live in `EntityFrameworkCore/Migrations/`
* SQL scripts for complex stored procedures and views are in `EntityFrameworkCore/SqlScripts/`

### Repository Implementations

Each aggregate root has a corresponding `EfCoreXxxRepository` in `EntityFrameworkCore/` that extends ABP's `EfCoreRepository<NimbleDbContext, TEntity, TKey>`. Domain-layer repository interfaces (`IXxxRepository`) are defined in `Domain/` and implemented here. Custom methods on repositories handle complex queries such as navigation property includes and multi-join filters.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://nimble.docs.otised.com/technical/data-model.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
