Database Structure
QDash uses MongoDB via the Bunnet ODM with a project-centric multi-tenant model. The data model has two layers:
- datamodel (
src/qdash/datamodel/) — PydanticBaseModelfor business logic - dbmodel (
src/qdash/dbmodel/) — BunnetDocumentfor database persistence
MongoDB Collections
| Collection | Document Class | Description |
|---|---|---|
project | ProjectDocument | Project metadata (owner-centric workspace) |
project_membership | ProjectMembershipDocument | User membership & role per project |
chip | ChipDocument | Quantum chip configuration scoped to a project |
qubit | QubitDocument | Individual qubit data |
coupling | CouplingDocument | Qubit coupling information |
execution_history | ExecutionHistoryDocument | Calibration execution history |
task_result_history | TaskResultHistoryDocument | Task result history |
qubit_history | QubitHistoryDocument | Daily qubit snapshots |
chip_history | ChipHistoryDocument | Daily chip snapshots |
coupling_history | CouplingHistoryDocument | Daily coupling snapshots |
task | TaskDocument | Task definitions |
backend | BackendDocument | Backend configurations (project scoped) |
user | UserDocument | User authentication / default project bootstrap |
tag | TagDocument | Project-level tag management |
execution_lock | ExecutionLockDocument | Exclusive execution lock |
execution_counter | ExecutionCounterDocument | Execution ID counter |
calibration_note | CalibrationNoteDocument | Calibration notes |
flows | FlowDocument | User-defined flows |
Data Models (datamodel)
SystemInfoModel
Base model for common system information.
class SystemInfoModel(BaseModel):
created_at: str # ISO8601 timestamp (Asia/Tokyo)
updated_at: str # ISO8601 timestamp (Asia/Tokyo)ProjectModel
Represents a collaborative workspace. Every tenant-visible entity references a project.
class ProjectModel(BaseModel):
project_id: str # Globally unique slug/UUID
owner_username: str # Creator/owner of the project
name: str # Display name
description: str | None = None
tags: list[str] = [] # Optional labels for search
system_info: SystemInfoModelProjectMembershipModel
Represents user access to a project.
class ProjectRole(str, Enum):
"""Simplified two-role permission model."""
OWNER = "owner" # Full access (read, write, admin)
VIEWER = "viewer" # Read-only access
class ProjectMembershipModel(BaseModel):
project_id: str
username: str
role: ProjectRole
invited_by: str | None = None
status: Literal["pending", "active", "revoked"] = "pending"
system_info: SystemInfoModelChipModel
Model representing quantum chip configuration.
class ChipModel(BaseModel):
chip_id: str # Chip ID (e.g., "chip1")
project_id: str # Owning project ID
username: str # Creator's username
size: int # Chip size
qubits: dict[str, QubitModel] # Qubit map
couplings: dict[str, CouplingModel] # Coupling map
installed_at: str # Installation timestamp
system_info: SystemInfoModelQubitModel
Model representing an individual qubit.
class QubitModel(BaseModel):
project_id: str # Owning project ID
username: str | None # Username
qid: str # Qubit ID (e.g., "0", "1")
status: str # Status ("pending", "completed", etc.)
chip_id: str | None # Parent chip ID
data: dict # Calibration dataExample data field structure:
{
"qubit_frequency": {
"value": 5.0,
"value_type": "float",
"error": 0.001,
"unit": "GHz",
"description": "Qubit resonance frequency",
"calibrated_at": "2024-01-01T00:00:00+09:00",
"execution_id": "20240101-001",
"task_id": "uuid-xxx"
}
}CouplingModel
Model representing coupling between qubits.
class CouplingModel(BaseModel):
project_id: str # Owning project ID
username: str | None # Username
qid: str # Coupling ID (e.g., "0-1")
status: str # Status
chip_id: str | None # Chip ID
data: dict # Calibration dataExecutionModel
Model representing a calibration execution metadata.
Note:
task_resultsandcalib_datawere removed to support 256+ qubit systems (avoiding MongoDB's 16MB document limit). Task results are stored intask_result_historycollection. Calibration data is stored inqubit/couplingcollections.
class ExecutionStatusModel(str, Enum):
SCHEDULED = "scheduled"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
CANCELLED = "cancelled"
class ExecutionModel(BaseModel):
project_id: str # Owning project ID
username: str # Username (request initiator)
name: str # Execution name
execution_id: str # Execution ID (e.g., "20240101-001")
calib_data_path: str # Calibration data path
note: dict # Notes
status: str # Status
tags: list[str] # Tags
chip_id: str # Chip ID
start_at: datetime # Start time
end_at: datetime # End time
elapsed_time: timedelta # Elapsed time
message: str # Message
system_info: SystemInfoModelTaskModel / TaskResultModel
Models for task definitions and execution results.
class TaskStatusModel(str, Enum):
SCHEDULED = "scheduled"
RUNNING = "running"
COMPLETED = "completed"
FAILED = "failed"
PENDING = "pending"
SKIPPED = "skipped"
CANCELLED = "cancelled"
class RunParameterModel(BaseModel):
"""Experiment configuration parameters (shots, ranges, etc.)."""
unit: str # Unit
value_type: str # Value type ("float", "int", "np.linspace", etc.)
value: tuple | int | float | None
description: str # Description
class ParameterModel(BaseModel):
"""Calibration parameter model (for both input and output parameters)."""
value: float | int # Value
value_type: str # Value type
error: float # Error
unit: str # Unit
description: str # Description
calibrated_at: str # Calibration timestamp
execution_id: str # Execution ID
task_id: str # Task ID
class CalibDataModel(BaseModel):
qubit: dict[str, dict[str, ParameterModel]] # Per-qubit data
coupling: dict[str, dict[str, ParameterModel]] # Per-coupling data
class BaseTaskResultModel(BaseModel):
project_id: str
task_id: str # UUID
name: str # Task name
upstream_id: str # Upstream task ID
status: TaskStatusModel
message: str # Message
input_parameters: dict
output_parameters: dict
output_parameter_names: list[str]
note: dict
figure_path: list[str] # Figure paths
json_figure_path: list[str] # JSON figure paths
raw_data_path: list[str] # Raw data paths
start_at: str
end_at: str
elapsed_time: str
task_type: str # "global", "qubit", "coupling", "system"
system_info: SystemInfoModel
# Task type subclasses
class SystemTaskModel(BaseTaskResultModel):
task_type: Literal["system"] = "system"
class GlobalTaskModel(BaseTaskResultModel):
task_type: Literal["global"] = "global"
class QubitTaskModel(BaseTaskResultModel):
task_type: Literal["qubit"] = "qubit"
qid: str # Target qubit ID
class CouplingTaskModel(BaseTaskResultModel):
task_type: Literal["coupling"] = "coupling"
qid: str # Target coupling ID
class TaskResultModel(BaseModel):
system_tasks: list[SystemTaskModel]
global_tasks: list[GlobalTaskModel]
qubit_tasks: dict[str, list[QubitTaskModel]]
coupling_tasks: dict[str, list[CouplingTaskModel]]
class TaskModel(BaseModel):
username: str
name: str # Task name (e.g., "CheckT1", "CheckT2Echo")
backend: str | None # Backend name
description: str
task_type: str # "global", "qubit", "coupling"
input_parameters: dict
output_parameters: dictBackendModel
Model representing backend configuration.
class BackendModel(BaseModel):
name: str # Backend name
username: str # UsernameFridgeModel
Model representing fridge data.
class FridgeModel(BaseModel):
device_id: str # Device ID
timestamp: datetime # Timestamp
data: dict # Data
system_info: SystemInfoModelDatabase Documents (dbmodel)
ProjectDocument
Collection: project
Indexes:
project_id- Unique identifier per project(owner_username, name)- Prevent duplicate names per owner
class ProjectDocument(Document):
project_id: str # UUID/slug
owner_username: str
name: str
description: str | None = None
tags: list[str] = []
system_info: SystemInfoModelProjectMembershipDocument
Collection: project_membership
Indexes:
(project_id, username)- Unique membership per user/project(username, status)- Efficient lookup of invitations
class ProjectMembershipDocument(Document):
project_id: str
username: str
role: ProjectRole
status: Literal["pending", "active", "revoked"] = "pending"
invited_by: str | None = None
last_accessed_at: str | None = None
system_info: SystemInfoModelChipDocument
Collection: chip
Indexes:
(project_id, chip_id)- Unique compound index(username, chip_id)- Backward-compat lookup while migrating
class ChipDocument(Document):
project_id: str
username: str # Creator/owner username
chip_id: str = "SAMPLE"
size: int = 64
qubits: dict[str, QubitModel] = {}
couplings: dict[str, CouplingModel] = {}
installed_at: str # ISO8601
system_info: SystemInfoModelKey Methods:
get_current_chip(project_id)- Get the most recently installed chip per projectget_chip_by_id(project_id, chip_id)- Get a specific chipupdate_qubit(qid, qubit_data)- Update a qubitupdate_coupling(qid, coupling_data)- Update a coupling
QubitDocument
Collection: qubit
Indexes:
(project_id, chip_id, qid)- Unique compound index(project_id, username)- Filter by project/member
class QubitDocument(Document):
project_id: str
username: str
qid: str
status: str = "pending"
chip_id: str
data: dict
system_info: SystemInfoModelKey Methods:
update_calib_data(username, qid, chip_id, output_parameters)- Update calibration dataupdate_status(qid, chip_id, status)- Update status
CouplingDocument
Collection: coupling
Indexes:
(project_id, chip_id, qid)- Unique compound index(project_id, username)- Filter by project/member
class CouplingDocument(Document):
project_id: str
username: str
qid: str # Coupling ID (e.g., "0-1")
status: str = "pending"
chip_id: str
data: dict
system_info: SystemInfoModelExecutionHistoryDocument
Collection: execution_history
Stores execution metadata only. Task results and calibration data are stored in separate collections to support 256+ qubit systems (avoiding MongoDB's 16MB document limit).
Indexes:
(project_id, execution_id)- Unique compound index(project_id, chip_id, start_at)- Supports metrics/best queries(project_id, chip_id)- Chip-based filtering(project_id, username, start_at)- Audit per user
class ExecutionHistoryDocument(Document):
project_id: str
username: str
name: str
execution_id: str
calib_data_path: str
note: dict
status: str
tags: list[str]
chip_id: str
start_at: datetime
end_at: datetime
elapsed_time: float # Stored in seconds
message: str
system_info: SystemInfoModelRelated Collections:
- Task results →
task_result_history(query byexecution_id) - Calibration data →
qubit/couplingcollections
Key Methods:
from_execution_model(execution_model)- Create from ExecutionModelupsert_document(execution_model)- Upsert operation
TaskResultHistoryDocument
Collection: task_result_history
Primary storage for task execution results. Linked to executions via execution_id.
Indexes:
(project_id, task_id)- Unique compound index(project_id, execution_id)- Join with execution_history(project_id, chip_id, start_at)- Time-based queries(project_id, chip_id, name, qid, start_at)- Latest task result queries
class TaskResultHistoryDocument(Document):
project_id: str
username: str
task_id: str
name: str
upstream_id: str
status: str
message: str
input_parameters: dict
output_parameters: dict
output_parameter_names: list[str]
note: dict
figure_path: list[str]
json_figure_path: list[str]
raw_data_path: list[str]
start_at: str
end_at: str
elapsed_time: str
task_type: str
system_info: SystemInfoModel
qid: str = ""
execution_id: str
tags: list[str]
chip_id: strQubitHistoryDocument
Collection: qubit_history
Indexes:
(project_id, chip_id, qid, recorded_date)- Unique compound index
Daily snapshot history table.
class QubitHistoryDocument(Document):
project_id: str
username: str
qid: str
status: str
chip_id: str
data: dict
system_info: SystemInfoModel
recorded_date: str # YYYYMMDD formatChipHistoryDocument
Collection: chip_history
Indexes:
(project_id, chip_id, recorded_date)- Unique compound index
class ChipHistoryDocument(Document):
project_id: str
chip_id: str
username: str
size: int
qubits: dict[str, QubitModel]
couplings: dict[str, CouplingModel]
installed_at: str
system_info: SystemInfoModel
recorded_date: str # YYYYMMDD formatCouplingHistoryDocument
Collection: coupling_history
Indexes:
(project_id, chip_id, qid, recorded_date)- Unique compound index
class CouplingHistoryDocument(Document):
project_id: str
username: str
qid: str
status: str
chip_id: str
data: dict
system_info: SystemInfoModel
recorded_date: str # YYYYMMDD formatTaskDocument
Collection: task
Indexes:
(project_id, name)- Unique compound index(project_id, task_type)- Filter by scope
Stores task definition information.
class TaskDocument(Document):
project_id: str
username: str
name: str # Task name (e.g., "CheckT1")
backend: str | None
description: str
task_type: str # "global", "qubit", "coupling"
input_parameters: dict | None
output_parameters: dict | NoneBackendDocument
Collection: backend
Indexes:
(project_id, name)- Unique compound index
class BackendDocument(Document):
project_id: str
username: str
name: str
system_info: SystemInfoModelUserDocument
Collection: user
Indexes:
username- Unique indexaccess_token- Unique index
class UserDocument(Document):
username: str
hashed_password: str
access_token: str
default_project_id: str | None = None
full_name: str | None = None
disabled: bool = False
system_info: SystemInfoModelTagDocument
Collection: tag
Indexes:
(project_id, name)- Unique compound index
class TagDocument(Document):
project_id: str
username: str
name: strExecutionLockDocument
Collection: execution_lock
Indexes:
project_id- Ensures one lock document per project
Singleton document for exclusive execution control. Ensures only one calibration runs at a time.
class ExecutionLockDocument(Document):
project_id: str
locked: bool = False
system_info: SystemInfoModelKey Methods:
get_lock_status()- Get lock statuslock()- Acquire lockunlock()- Release lock
ExecutionCounterDocument
Collection: execution_counter
Indexes:
(project_id, date, chip_id)- Unique compound index
Manages execution ID sequence numbers per date/user/chip.
class ExecutionCounterDocument(Document):
date: str # YYYYMMDD format
project_id: str
username: str
chip_id: str
index: int # Sequence number (starts from 0)
system_info: SystemInfoModelKey Methods:
get_next_index(project_id, date, chip_id)- Atomically get next index
Generated execution ID format: YYYYMMDD-NNN (e.g., 20240101-001)
CalibrationNoteDocument
Collection: calibration_note
Indexes:
(project_id, execution_id, task_id, chip_id)- Unique compound index(project_id, chip_id, timestamp)- Search index
class CalibrationNoteDocument(Document):
project_id: str
username: str
chip_id: str
execution_id: str
task_id: str
note: dict
timestamp: str # ISO8601
system_info: SystemInfoModelFlowDocument
Collection: flows
Indexes:
(project_id, name)- Unique per project(project_id, created_at)- For sorted listing
Stores metadata for user-defined Python flows (custom calibration workflows).
class FlowDocument(Document):
name: str # Flow name
project_id: str
username: str
chip_id: str # Target chip ID
description: str = ""
flow_function_name: str # Entry point function name
default_parameters: dict = {}
file_path: str # Relative path to .py file
deployment_id: str | None = None # Prefect deployment ID
created_at: datetime
updated_at: datetime
tags: list[str] = []Entity Relationship Diagram (Conceptual)

Other project-scoped collections (tasks, tags, backends, flows, counters, locks, histories) all reference project_id, ensuring a single sharing boundary per project.
Data Flow
During Calibration Execution
- Resolve
(project_id, username)via ProjectMembershipDocument and ensure role isowner - Acquire per-project lock via ExecutionLockDocument(project_id)
- Generate execution ID from ExecutionCounterDocument (YYYYMMDD-NNN scoped by project/chip)
- Execute each task:
- Save task results to TaskResultHistoryDocument (with
execution_idfor linking) - Update calibration data in QubitDocument / CouplingDocument
- Save history to QubitHistoryDocument / CouplingHistoryDocument
- Save task results to TaskResultHistoryDocument (with
- Save execution metadata to ExecutionHistoryDocument (status, timing, notes only)
- Save chip snapshot to ChipHistoryDocument
- Release ExecutionLock
During Cancellation
When a user cancels a running execution via the UI:
- UI sends
POST /executions/{flow_run_id}/cancelwith the Prefect flow run UUID - API sets the Prefect flow run to
Cancellingstate via the Prefect client - Prefect sends SIGTERM to the worker process running the flow
- Prefect triggers the
on_cancellationhook registered on the@flowdecorator - The hook reads
flow_run_idfrom the execution'snotefield to locate the execution - All non-terminal tasks (running/scheduled/pending) are set to
cancelled - The execution status is set to
cancelled - The ExecutionLock is released
Note: The
flow_run_id(Prefect UUID) is stored inExecutionHistoryDocument.note["flow_run_id"]at the start of each flow run. This bridges the QDash execution ID (YYYYMMDD-NNN) with the Prefect flow run UUID, enabling the cancel operation.
Data Architecture (256+ Qubit Support)
To avoid MongoDB's 16MB document limit with large qubit counts:
┌─────────────────────────┐
│ ExecutionHistoryDoc │ ← Metadata only (~2KB)
│ - execution_id │
│ - status, timing │
│ - tags, note │
└───────────┬─────────────┘
│ execution_id
▼
┌─────────────────────────┐
│ TaskResultHistoryDoc │ ← Individual task results
│ - task_id │ (one doc per task)
│ - execution_id (FK) │
│ - output_parameters │
└─────────────────────────┘
┌─────────────────────────┐
│ QubitDocument │ ← Calibration data
│ CouplingDocument │ (persistent storage)
└─────────────────────────┘Timezone
All timestamps are managed in Asia/Tokyo (JST) timezone.
created_at,updated_at: ISO8601 formatrecorded_date: YYYYMMDD formatcalibrated_at: ISO8601 format