Source

models/investigation/schema-definition.js

import { InvestigationStatus } from "@typez/investigation/enums";
import { Schema } from "mongoose";
// ============================================================================
// EDITABLE FIELD SCHEMAS
// ============================================================================
/**
 * Field History Entry Schema - Schema for a single history entry
 * @category Models
 */
const FieldHistoryEntrySchema = new Schema({
    value: { type: Schema.Types.Mixed, required: true },
    updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
    updatedAt: { type: Date, default: Date.now },
}, { _id: false });
/**
 * General EditableField Schema - Base schema for all editable fields
 * This schema provides the common structure for AI-generated and human-editable content
 * @category Models
 */
const EditableFieldSchema = new Schema({
    value: { type: Schema.Types.Mixed, default: null },
    aiGeneratedValue: { type: Schema.Types.Mixed, default: null },
    humanEdited: { type: Boolean, default: false },
    aiEditable: { type: Boolean, default: true },
    isContradicting: { type: Boolean, default: false },
    contradictionReason: { type: String, default: null },
    updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
    updatedAt: { type: Date, default: Date.now },
    createdAt: { type: Date, default: Date.now },
    targetFieldName: { type: String, default: null },
    history: {
        type: [FieldHistoryEntrySchema],
        default: [],
    },
}, { _id: false });
// const EditableFieldStringSchema = new Schema(
//   {
//     value: { type: String, default: null },
//     aiGeneratedValue: { type: Schema.Types.Mixed, default: null },
//     humanEdited: { type: Boolean, default: false },
//     aiEditable: { type: Boolean, default: true },
//     isContradicting: { type: Boolean, default: false },
//     contradictionReason: { type: String, default: null },
//     updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
//     updatedAt: { type: Date, default: Date.now },
//     createdAt: { type: Date, default: Date.now },
//     targetFieldName: { type: String, default: null },
//   },
//   { _id: false },
// );
// ============================================================================
// COMPONENT SCHEMAS
// ============================================================================
/**
 * Step Schema - Individual step in an investigation
 * Each field is now wrapped with EditableFieldSchema for AI/human editing capabilities
 * @category Models
 */
const StepSchema = new Schema({
    title: {
        type: EditableFieldSchema,
        default: null,
    },
    descriptionEn: {
        type: EditableFieldSchema,
        default: null,
    },
    desiredOutcome: {
        type: EditableFieldSchema,
        default: null,
    },
    alternativeOutcome: {
        type: EditableFieldSchema,
        default: null,
    },
    skippable: {
        type: EditableFieldSchema,
        default: null,
    },
    skippable_after_marking: {
        type: EditableFieldSchema,
        default: null,
    },
}, { _id: false });
/**
 * Vector3 Schema - 3D vector with x, y, z coordinates
 * @category Models
 */
const EditableVector3Schema = new Schema({
    x: { type: EditableFieldSchema, default: null },
    y: { type: EditableFieldSchema, default: null },
    z: { type: EditableFieldSchema, default: null },
}, { _id: false });
/**
 * Interaction Schema - Represents an interaction available for a component
 * @category Models
 */
// const InteractionSchema = new Schema<IInteraction>(
//   {
//     name: {
//       type: String,
//       trim: true,
//       maxlength: [100, "Interaction name cannot exceed 100 characters"],
//     },
//   },
//   { _id: false },
// );
/**
 * View Component Schema - Mesh, Material, and Texture
 * @category Models
 */
// const EditableViewComponentSchema = new Schema(
//   {
//     mesh: { type: EditableFieldSchema, default: null },
//     material: { type: EditableFieldSchema, default: null },
//     texture: { type: EditableFieldSchema, default: null },
//   },
//   { _id: true },
// );
// const ViewComponentSchema2 = new Schema<IViewComponent2>(
//   {
//     mesh: { type: String },
//     material: { type: String },
//     texture: { type: String },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * RigidBody Component Schema - Physics body that can be affected by forces
 * @category Models
 */
// const RigidBodyComponentSchema = new Schema<IRigidBodyComponent>(
//   {
//     mass: { type: Number, min: 0 },
//     velocity: { type: Vector3Schema },
//     angularVelocity: { type: Vector3Schema },
//     drag: { type: Number, min: 0 },
//     angularDrag: { type: Number, min: 0 },
//     useGravity: { type: Boolean },
//     isKinematic: { type: Boolean },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * Fluid RigidBody Component Schema - Special physics for fluid objects
 * @category Models
 */
// const FluidRigidBodyComponentSchema = new Schema<IFluidRigidBodyComponent>(
//   {
//     density: { type: Number, min: 0 },
//     viscosity: { type: Number, min: 0 },
//     surfaceTension: { type: Number, min: 0 },
//     particleSize: { type: Number, min: 0 },
//     simulationMethod: { type: String, enum: ["SPH", "Grid"] },
//     maxParticles: { type: Number, min: 1 },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * Collision Mesh Component Schema - Defines collision boundaries
 * @category Models
 */
// const CollisionMeshComponentSchema = new Schema<ICollisionMeshComponent>(
//   {
//     type: { type: String, enum: ["Box", "Sphere", "Capsule", "Mesh", "Convex"] },
//     size: { type: Vector3Schema },
//     isTrigger: { type: Boolean },
//     material: { type: String },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * Temperature Component Schema - Temperature with value and unit
 * @category Models
 */
// const TemperatureComponentSchema = new Schema<ITemperatureComponent>(
//   {
//     value: { type: Number },
//     unit: { type: String, enum: ["C", "F", "K"] },
//     conductivity: { type: Number, min: 0 },
//     specificHeat: { type: Number, min: 0 },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * State Of Matter Component Schema - Material state based on temperature
 * @category Models
 */
// const StateOfMatterComponentSchema = new Schema<IStateOfMatterComponent>(
//   {
//     currentState: { type: String, enum: ["Solid", "Liquid", "Gas", "Plasma"] },
//     meltingPoint: { type: Number },
//     boilingPoint: { type: Number },
//     sublimationPoint: { type: Number },
//     stateChangeObjects: {
//       solid: { type: String },
//       liquid: { type: String },
//       gas: { type: String },
//       plasma: { type: String },
//     },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * Shatter Component Schema - Damage and destruction behavior
 * @category Models
 */
// const ShatterComponentSchema = new Schema<IShatterComponent>(
//   {
//     health: { type: Number, min: 0 },
//     maxHealth: { type: Number, min: 0 },
//     shatterThreshold: { type: Number, min: 0 },
//     shatterEffect: { type: String, enum: ["Visual", "Vaporize", "Fragment"] },
//     fragmentCount: { type: Number, min: 0 },
//     fragmentObject: { type: String },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * Hard-Attach Component Schema - Permanent attachment behavior
 * @category Models
 */
// const HardAttachComponentSchema = new Schema<IHardAttachComponent>(
//   {
//     attachedObjects: [{ type: String }],
//     attachmentStrength: { type: Number, min: 0 },
//     canDetach: { type: Boolean },
//     detachForce: { type: Number, min: 0 },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * Fill Component Schema - Container that can be filled with liquids
 * @category Models
 */
// const FillComponentSchema = new Schema<IFillComponent>(
//   {
//     maxVolume: { type: Number, min: 0 },
//     currentVolume: { type: Number, min: 0 },
//     currentLiquid: { type: String },
//     fillRate: { type: Number, min: 0 },
//     drainRate: { type: Number, min: 0 },
//     hasOverflow: { type: Boolean },
//     interactions: [{ type: InteractionSchema }],
//   },
//   { _id: false },
// );
/**
 * Object Schema - 3D object with position, rotation, size, view and components
 * @category Models
 */
const ObjectSchema = new Schema({
    name: { type: EditableFieldSchema, default: null },
    objectId: { type: EditableFieldSchema, default: null },
    position: { type: EditableVector3Schema, default: null },
    rotation: { type: EditableVector3Schema, default: null },
    size: { type: EditableFieldSchema, default: null },
}, { _id: false });
/**
 * Object Schema Pre-save hook - Generate object ID from name
 * @category Models
 */
ObjectSchema.pre("save", function generateObjectIdFromName(next) {
    if (this.name.value) {
        this.objectId.value = this.name.value.trim().toLocaleLowerCase().replace(/ /g, "_");
    }
    if (this.name.aiGeneratedValue) {
        this.objectId.aiGeneratedValue = this.name.aiGeneratedValue
            .trim()
            .toLocaleLowerCase()
            .replace(/ /g, "_");
    }
    next();
});
// const ObjectSchema2 = new Schema<IObject2>(
//   {
//     id: { type: String },
//     name: {
//       type: String,
//       trim: true,
//       maxlength: [200, "Object name cannot exceed 200 characters"],
//     },
//     position: { type: Vector3Schema },
//     rotation: { type: Vector3Schema },
//     size: { type: Number, min: 0 },
//     view: { type: ViewComponentSchema2 },
//
//     // Optional components
//     rigidBody: { type: RigidBodyComponentSchema },
//     fluidRigidBody: { type: FluidRigidBodyComponentSchema },
//     collisionMesh: { type: CollisionMeshComponentSchema },
//     temperature: { type: TemperatureComponentSchema },
//     stateOfMatter: { type: StateOfMatterComponentSchema },
//     shatter: { type: ShatterComponentSchema },
//     hardAttach: { type: HardAttachComponentSchema },
//     fill: { type: FillComponentSchema },
//   },
//   { _id: false },
// );
/**
 * Investigation Metadata Schema - Metadata for an investigation
 * @category Models
 */
const InvestigationMetadataSchema = new Schema({
    dateOfDevelopment: { type: Date, default: null },
    dateOfDevelopmentDelivery: { type: Date, default: null },
    dateOfPublishing: { type: Date, default: null },
    author: { type: Schema.Types.ObjectId, ref: "User" }, //todo required later !
    editors: [{ type: Schema.Types.ObjectId, ref: "User" }],
    views: { type: Number, default: 0, min: 0 },
    sourceInvestigation: { type: Schema.Types.ObjectId, ref: "Investigation", default: null },
    dateOfCreation: { type: Date, default: Date.now() },
    dateModified: { type: Date, default: Date.now() },
}, { _id: false });
const InvestigationVersionSchema = new Schema({
    versionNumber: { type: Number, required: true },
    snapshot: { type: Schema.Types.Mixed, required: true },
    updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
    updatedAt: { type: Date, default: Date.now },
    isAiGenerated: { type: Boolean, required: true, default: false },
}, { _id: false });
// ============================================================================
// EDITABLE FIELD SCHEMAS
// ============================================================================
/**
 * Objects EditableField Schema - For array of 3D objects with full validation
 * @category Models
 */
// const ObjectsEditableFieldSchema = new Schema(
//   {
//     value: { type: [ObjectSchema], default: null },
//     aiGeneratedValue: { type: [ObjectSchema], default: null },
//     humanEdited: { type: Boolean, default: false },
//     aiEditable: { type: Boolean, default: true },
//     isContradicting: { type: Boolean, default: false },
//     contradictionReason: { type: String, default: null },
//     updatedBy: { type: Schema.Types.ObjectId, ref: "User", default: null },
//     updatedAt: { type: Date, default: null },
//   },
//   { _id: false },
// );
// ============================================================================
// MAIN INVESTIGATION SCHEMA
// ============================================================================
/**
 * Investigation Schema Definition
 * This file contains the main mongoose schema definition for investigations
 * @category Models
 */
export const InvestigationSchema = new Schema({
    // Lifecycle & locking
    status: {
        type: String,
        enum: Object.values(InvestigationStatus),
        default: InvestigationStatus.DRAFT_INCOMPLETE,
    },
    isLocked: {
        type: Boolean,
        default: false,
    },
    lockedBy: {
        type: Schema.Types.ObjectId,
        ref: "User",
        default: null,
    },
    lockedAt: {
        type: Date,
        default: null,
    },
    // Editable content (all wrapped with typed EditableField<T>)
    title: {
        type: EditableFieldSchema,
    },
    curriculum: {
        type: EditableFieldSchema,
    },
    unitNumberAndTitle: {
        type: EditableFieldSchema,
    },
    grade: {
        type: EditableFieldSchema,
    },
    lessonNumberAndTitle: {
        type: EditableFieldSchema,
    },
    objectives: {
        type: EditableFieldSchema,
    },
    ngss: {
        type: EditableFieldSchema,
    },
    analyticalFacts: {
        type: EditableFieldSchema,
    },
    goals: {
        type: EditableFieldSchema,
    },
    day: {
        type: EditableFieldSchema,
    },
    steps: {
        type: [StepSchema],
        default: [],
    },
    objects: {
        type: [ObjectSchema],
    },
    // Metadata (system/relations)
    metadata: {
        type: InvestigationMetadataSchema,
    },
    versions: {
        type: [InvestigationVersionSchema],
        default: [],
    },
    currentVersionIndex: {
        type: Number,
        default: 0,
    },
    lastChangeWithAI: {
        type: Boolean,
        default: true,
    },
}, {
    timestamps: true,
    collection: "investigations",
});