mirror of
https://github.com/soconnor0919/hristudio.git
synced 2026-03-23 19:27:51 -04:00
feat(analytics): refine timeline visualization and add print support
This commit is contained in:
46
scripts/archive/check-db-actions.ts
Normal file
46
scripts/archive/check-db-actions.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
async function main() {
|
||||
console.log("🔍 Checking seeded actions...");
|
||||
|
||||
const actions = await db.query.actions.findMany({
|
||||
where: (actions, { or, eq, like }) => or(
|
||||
eq(actions.type, "sequence"),
|
||||
eq(actions.type, "parallel"),
|
||||
eq(actions.type, "loop"),
|
||||
eq(actions.type, "branch"),
|
||||
like(actions.type, "hristudio-core%")
|
||||
),
|
||||
limit: 10
|
||||
});
|
||||
|
||||
console.log(`Found ${actions.length} control actions.`);
|
||||
|
||||
for (const action of actions) {
|
||||
console.log(`\nAction: ${action.name} (${action.type})`);
|
||||
console.log(`ID: ${action.id}`);
|
||||
// Explicitly log parameters to check structure
|
||||
console.log("Parameters:", JSON.stringify(action.parameters, null, 2));
|
||||
|
||||
const params = action.parameters as any;
|
||||
if (params.children) {
|
||||
console.log(`✅ Has ${params.children.length} children in parameters.`);
|
||||
} else if (params.trueBranch || params.falseBranch) {
|
||||
console.log(`✅ Has branches in parameters.`);
|
||||
} else {
|
||||
console.log(`❌ No children/branches found in parameters.`);
|
||||
}
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../src/server/db/schema";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import { db } from "../src/server/db";
|
||||
import { experiments, steps } from "../src/server/db/schema";
|
||||
import { db } from "../../src/server/db";
|
||||
import { experiments, steps } from "../../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
async function inspectAllSteps() {
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import { db } from "../src/server/db";
|
||||
import { steps } from "../src/server/db/schema";
|
||||
import { db } from "../../src/server/db";
|
||||
import { steps } from "../../src/server/db/schema";
|
||||
import { eq, like } from "drizzle-orm";
|
||||
|
||||
async function checkSteps() {
|
||||
@@ -1,6 +1,6 @@
|
||||
|
||||
import { db } from "../src/server/db";
|
||||
import { experiments } from "../src/server/db/schema";
|
||||
import { db } from "../../src/server/db";
|
||||
import { experiments } from "../../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
async function inspectVisualDesign() {
|
||||
76
scripts/archive/reproduce-hydration.ts
Normal file
76
scripts/archive/reproduce-hydration.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
|
||||
import { convertDatabaseToSteps } from "../../src/lib/experiment-designer/block-converter";
|
||||
import { type ExperimentStep } from "../../src/lib/experiment-designer/types";
|
||||
|
||||
// Mock DB Steps (simulating what experimentsRouter returns before conversion)
|
||||
const mockDbSteps = [
|
||||
{
|
||||
id: "step-1",
|
||||
name: "Step 1",
|
||||
type: "wizard",
|
||||
orderIndex: 0,
|
||||
actions: [
|
||||
{
|
||||
id: "seq-1",
|
||||
name: "Test Sequence",
|
||||
type: "sequence",
|
||||
parameters: {
|
||||
children: [
|
||||
{ id: "child-1", name: "Child 1", type: "wait", parameters: { duration: 1 } },
|
||||
{ id: "child-2", name: "Child 2", type: "wait", parameters: { duration: 2 } }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
// Mock Store Logic (simulating store.ts)
|
||||
function cloneActions(actions: any[]): any[] {
|
||||
return actions.map((a) => ({
|
||||
...a,
|
||||
children: a.children ? cloneActions(a.children) : undefined,
|
||||
}));
|
||||
}
|
||||
|
||||
function cloneSteps(steps: any[]): any[] {
|
||||
return steps.map((s) => ({
|
||||
...s,
|
||||
actions: cloneActions(s.actions),
|
||||
}));
|
||||
}
|
||||
|
||||
console.log("🔹 Testing Hydration & Cloning...");
|
||||
|
||||
// 1. Convert DB -> Runtime
|
||||
const runtimeSteps = convertDatabaseToSteps(mockDbSteps);
|
||||
const seq = runtimeSteps[0]?.actions[0];
|
||||
|
||||
if (!seq) {
|
||||
console.error("❌ Conversion Failed: Sequence action not found.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Runtime Children Count: ${seq.children?.length ?? "undefined"}`);
|
||||
|
||||
if (!seq.children || seq.children.length === 0) {
|
||||
console.error("❌ Conversion Failed: Children not hydrated from parameters.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 2. Store Cloning
|
||||
const clonedSteps = cloneSteps(runtimeSteps);
|
||||
const clonedSeq = clonedSteps[0]?.actions[0];
|
||||
|
||||
if (!clonedSeq) {
|
||||
console.error("❌ Cloning Failed: Sequence action lost.");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(`Cloned Children Count: ${clonedSeq.children?.length ?? "undefined"}`);
|
||||
|
||||
if (clonedSeq.children?.length === 2) {
|
||||
console.log("✅ SUCCESS: Data hydrated and cloned correctly.");
|
||||
} else {
|
||||
console.error("❌ CLONING FAILED: Children lost during clone.");
|
||||
}
|
||||
121
scripts/archive/seed-control-demo-draft.ts
Normal file
121
scripts/archive/seed-control-demo-draft.ts
Normal file
@@ -0,0 +1,121 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { sql } from "drizzle-orm";
|
||||
|
||||
// Database connection
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
async function main() {
|
||||
console.log("🌱 Seeding 'Control Flow Demo' experiment...");
|
||||
|
||||
try {
|
||||
// 1. Find Admin User & Study
|
||||
const user = await db.query.users.findFirst({
|
||||
where: (users, { eq }) => eq(users.email, "sean@soconnor.dev")
|
||||
});
|
||||
if (!user) throw new Error("Admin user 'sean@soconnor.dev' not found. Run seed-dev.ts first.");
|
||||
|
||||
const study = await db.query.studies.findFirst({
|
||||
where: (studies, { eq }) => eq(studies.name, "Comparative WoZ Study")
|
||||
});
|
||||
if (!study) throw new Error("Study 'Comparative WoZ Study' not found. Run seed-dev.ts first.");
|
||||
|
||||
// Find Robot
|
||||
const robot = await db.query.robots.findFirst({
|
||||
where: (robots, { eq }) => eq(robots.name, "NAO6")
|
||||
});
|
||||
if (!robot) throw new Error("Robot 'NAO6' not found. Run seed-dev.ts first.");
|
||||
|
||||
|
||||
// 2. Create Experiment
|
||||
const [experiment] = await db.insert(schema.experiments).values({
|
||||
studyId: study.id,
|
||||
name: "Control Flow Demo",
|
||||
description: "Demonstration of enhanced control flow actions: Sequence, Parallel, Wait, Loop, Branch.",
|
||||
version: 1,
|
||||
status: "draft",
|
||||
robotId: robot.id,
|
||||
createdBy: user.id,
|
||||
}).returning();
|
||||
|
||||
if (!experiment) throw new Error("Failed to create experiment");
|
||||
console.log(`✅ Created Experiment: ${experiment.id}`);
|
||||
|
||||
// 3. Create Steps
|
||||
|
||||
// Step 1: Sequence & Parallel
|
||||
const [step1] = await db.insert(schema.steps).values({
|
||||
experimentId: experiment.id,
|
||||
name: "Complex Action Structures",
|
||||
description: "Demonstrating Sequence and Parallel groups",
|
||||
type: "robot",
|
||||
orderIndex: 0,
|
||||
required: true,
|
||||
durationEstimate: 30
|
||||
}).returning();
|
||||
|
||||
// Step 2: Loops & Waits
|
||||
const [step2] = await db.insert(schema.steps).values({
|
||||
experimentId: experiment.id,
|
||||
name: "Repetition & Delays",
|
||||
description: "Demonstrating Loop and Wait actions",
|
||||
type: "robot",
|
||||
orderIndex: 1,
|
||||
required: true,
|
||||
durationEstimate: 45
|
||||
}).returning();
|
||||
|
||||
// 4. Create Actions
|
||||
|
||||
// --- Step 1 Actions ---
|
||||
|
||||
// Top-level Sequence
|
||||
const seqId = `seq-${Date.now()}`;
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step1!.id,
|
||||
name: "Introduction Sequence",
|
||||
type: "sequence", // New type
|
||||
orderIndex: 0,
|
||||
parameters: {},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
// No explicit children column in schema?
|
||||
// Wait, schema.actions has "children" as jsonb or it's a recursive relationship?
|
||||
// Let's check schema/types.
|
||||
// Looking at ActionChip, it expects `action.children`.
|
||||
// In DB, it's likely stored in `children` jsonb column if it exists, OR we need to perform recursive inserts if schema supports parentId.
|
||||
// Checking `types.ts` or schema...
|
||||
// Assuming flat list references for now or JSONB.
|
||||
// Wait, `ExperimentAction` in types has `children?: ExperimentAction[]`.
|
||||
// If the DB schema `actions` table handles nesting via `parameters` or specific column, I need to know.
|
||||
// Defaulting to "children" property in JSON parameter if DB doesn't have parentId.
|
||||
// Checking `schema.ts`: "children" is likely NOT a column if I haven't seen it in seed-dev.
|
||||
// However, `ActionChip` uses `action.children`. Steps map to `actions`.
|
||||
// If `actions` table has `parentId` or `children` JSONB.
|
||||
// I will assume `children` is part of the `parameters` or a simplified representation for now,
|
||||
// BUT `FlowWorkspace` treats `action.children` as real actions.
|
||||
// Let's check `schema.ts` quickly.
|
||||
});
|
||||
|
||||
// I need to check schema.actions definition effectively.
|
||||
// For this pass, I will insert them as flat actions since I can't confirm nesting storage without checking schema.
|
||||
// But the user WANTS to see the nesting (Sequence, Parallel).
|
||||
// The `SortableActionChip` renders `action.children`.
|
||||
// The `TrialExecutionEngine` executes `action.children`.
|
||||
// So the data MUST include children.
|
||||
// Most likely `actions` table has a `children` JSONB column.
|
||||
|
||||
// I will insert a Parallel action with embedded children in the `children` column (if it exists) or `parameters`.
|
||||
// Re-reading `scripts/seed-dev.ts`: It doesn't show any nested actions.
|
||||
// I will read `src/server/db/schema.ts` to be sure.
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// I'll write the file AFTER checking schema to ensure I structure the nested actions correctly.
|
||||
241
scripts/archive/seed-control-demo.ts
Normal file
241
scripts/archive/seed-control-demo.ts
Normal file
@@ -0,0 +1,241 @@
|
||||
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
// Database connection
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
async function main() {
|
||||
console.log("🌱 Seeding 'Control Flow Demo' experiment...");
|
||||
|
||||
try {
|
||||
// 1. Find Admin User & Study
|
||||
const user = await db.query.users.findFirst({
|
||||
where: (users, { eq }) => eq(users.email, "sean@soconnor.dev")
|
||||
});
|
||||
if (!user) throw new Error("Admin user 'sean@soconnor.dev' not found. Run seed-dev.ts first.");
|
||||
|
||||
const study = await db.query.studies.findFirst({
|
||||
where: (studies, { eq }) => eq(studies.name, "Comparative WoZ Study")
|
||||
});
|
||||
if (!study) throw new Error("Study 'Comparative WoZ Study' not found. Run seed-dev.ts first.");
|
||||
|
||||
// Find Robot
|
||||
const robot = await db.query.robots.findFirst({
|
||||
where: (robots, { eq }) => eq(robots.name, "NAO6")
|
||||
});
|
||||
if (!robot) throw new Error("Robot 'NAO6' not found. Run seed-dev.ts first.");
|
||||
|
||||
|
||||
// 2. Create Experiment
|
||||
const [experiment] = await db.insert(schema.experiments).values({
|
||||
studyId: study.id,
|
||||
name: "Control Flow Demo",
|
||||
description: "Demonstration of enhanced control flow actions: Sequence, Parallel, Wait, Loop, Branch.",
|
||||
version: 1,
|
||||
status: "draft",
|
||||
robotId: robot.id,
|
||||
createdBy: user.id,
|
||||
}).returning();
|
||||
|
||||
if (!experiment) throw new Error("Failed to create experiment");
|
||||
console.log(`✅ Created Experiment: ${experiment.id}`);
|
||||
|
||||
// 3. Create Steps
|
||||
|
||||
// Step 1: Sequence & Parallel
|
||||
const [step1] = await db.insert(schema.steps).values({
|
||||
experimentId: experiment.id,
|
||||
name: "Complex Action Structures",
|
||||
description: "Demonstrating Sequence and Parallel groups",
|
||||
type: "robot",
|
||||
orderIndex: 0,
|
||||
required: true,
|
||||
durationEstimate: 30
|
||||
}).returning();
|
||||
if (!step1) throw new Error("Failed to create step1");
|
||||
|
||||
// Step 2: Loops & Waits
|
||||
const [step2] = await db.insert(schema.steps).values({
|
||||
experimentId: experiment.id,
|
||||
name: "Repetition & Delays",
|
||||
description: "Demonstrating Loop and Wait actions",
|
||||
type: "robot",
|
||||
orderIndex: 1,
|
||||
required: true,
|
||||
durationEstimate: 45
|
||||
}).returning();
|
||||
if (!step2) throw new Error("Failed to create step2");
|
||||
|
||||
// 4. Create Actions
|
||||
|
||||
// --- Step 1 Actions ---
|
||||
|
||||
// Action 1: Sequence
|
||||
// Note: Nested children are stored in 'children' property of the action object in frontend,
|
||||
// but in DB 'parameters' is the JSONB field.
|
||||
// However, looking at ActionChip, it expects `action.children`.
|
||||
// The `ExperimentAction` type usually has `children` at top level.
|
||||
// If the DB doesn't have it, the API must be hydrating it.
|
||||
// BUT, for the purpose of this seed which writes to DB directly, I will put it in `parameters.children`
|
||||
// and assume the frontend/API handles it or I'm missing a column.
|
||||
// Actually, looking at schema again, `actions` table DOES NOT have children.
|
||||
// So it MUST be in `parameters` or it's not persisted in this table structure yet (which would be a bug, but I'm seeding what exists).
|
||||
// Wait, if I put it in parameters, does the UI read it?
|
||||
// `ActionChip` reads `action.children`.
|
||||
// I will try to put it in `parameters` and distinct `children` property in the JSON passed to `parameters`?
|
||||
// No, `parameters` is jsonb.
|
||||
// I will assume for now that the system expects it in parameters if it's not a column, OR it's not fully supported in DB yet.
|
||||
// I will stick to what the UI likely consumes. `parameters: { children: [...] }`
|
||||
|
||||
// Sequence
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step1.id,
|
||||
name: "Introduction Sequence",
|
||||
type: "sequence",
|
||||
orderIndex: 0,
|
||||
// Embedding children here to demonstrate.
|
||||
// Real implementation might vary if keys are strictly checked.
|
||||
parameters: {
|
||||
children: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Say Hello",
|
||||
type: "nao6-ros2.say_text",
|
||||
parameters: { text: "Hello there!" },
|
||||
category: "interaction"
|
||||
},
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Wave Hand",
|
||||
type: "nao6-ros2.move_arm",
|
||||
parameters: { arm: "right", action: "wave" },
|
||||
category: "movement"
|
||||
}
|
||||
]
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
});
|
||||
|
||||
// Parallel
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step1.id,
|
||||
name: "Parallel Actions",
|
||||
type: "parallel",
|
||||
orderIndex: 1,
|
||||
parameters: {
|
||||
children: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Say 'Moving'",
|
||||
type: "nao6-ros2.say_text",
|
||||
parameters: { text: "I am moving and talking." },
|
||||
category: "interaction"
|
||||
},
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Walk Forward",
|
||||
type: "nao6-ros2.move_to",
|
||||
parameters: { x: 0.5, y: 0 },
|
||||
category: "movement"
|
||||
}
|
||||
]
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
});
|
||||
|
||||
|
||||
// --- Step 2 Actions ---
|
||||
|
||||
// Loop
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step2.id,
|
||||
name: "Repeat Message",
|
||||
type: "loop",
|
||||
orderIndex: 0,
|
||||
parameters: {
|
||||
iterations: 3,
|
||||
children: [
|
||||
{
|
||||
id: uuidv4(),
|
||||
name: "Say 'Echo'",
|
||||
type: "nao6-ros2.say_text",
|
||||
parameters: { text: "Echo" },
|
||||
category: "interaction"
|
||||
}
|
||||
]
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
});
|
||||
|
||||
// Wait
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step2.id,
|
||||
name: "Wait 5 Seconds",
|
||||
type: "wait",
|
||||
orderIndex: 1,
|
||||
parameters: { duration: 5 },
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
});
|
||||
|
||||
// Branch (Controls step routing, not nested actions)
|
||||
// Note: Branch configuration is stored in step.trigger.conditions, not action.parameters
|
||||
// The branch action itself is just a marker that this step has conditional routing
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: step2.id,
|
||||
name: "Conditional Routing",
|
||||
type: "branch",
|
||||
orderIndex: 2,
|
||||
parameters: {
|
||||
// Branch actions don't have nested children
|
||||
// Routing is configured at the step level via trigger.conditions
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
});
|
||||
|
||||
// Update step2 to have conditional routing
|
||||
await db.update(schema.steps)
|
||||
.set({
|
||||
type: "conditional",
|
||||
conditions: {
|
||||
options: [
|
||||
{
|
||||
label: "High Score Path",
|
||||
nextStepIndex: 2, // Would go to a hypothetical step 3
|
||||
variant: "default"
|
||||
},
|
||||
{
|
||||
label: "Low Score Path",
|
||||
nextStepIndex: 0, // Loop back to step 1
|
||||
variant: "outline"
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
.where(sql`id = ${step2.id}`);
|
||||
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
} finally {
|
||||
await connection.end();
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
60
scripts/archive/test-converter.ts
Normal file
60
scripts/archive/test-converter.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
|
||||
import { convertDatabaseToAction } from "../../src/lib/experiment-designer/block-converter";
|
||||
|
||||
const mockDbAction = {
|
||||
id: "eaf8f85b-75cf-4973-b436-092516b4e0e4",
|
||||
name: "Introduction Sequence",
|
||||
description: null,
|
||||
type: "sequence",
|
||||
orderIndex: 0,
|
||||
parameters: {
|
||||
"children": [
|
||||
{
|
||||
"id": "75018b01-a964-41fb-8612-940a29020d4a",
|
||||
"name": "Say Hello",
|
||||
"type": "nao6-ros2.say_text",
|
||||
"category": "interaction",
|
||||
"parameters": {
|
||||
"text": "Hello there!"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "d7020530-6477-41f3-84a4-5141778c93da",
|
||||
"name": "Wave Hand",
|
||||
"type": "nao6-ros2.move_arm",
|
||||
"category": "movement",
|
||||
"parameters": {
|
||||
"arm": "right",
|
||||
"action": "wave"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
timeout: null,
|
||||
retryCount: 0,
|
||||
sourceKind: "core",
|
||||
pluginId: "hristudio-core",
|
||||
pluginVersion: null,
|
||||
robotId: null,
|
||||
baseActionId: null,
|
||||
category: "control",
|
||||
transport: null,
|
||||
ros2: null,
|
||||
rest: null,
|
||||
retryable: null,
|
||||
parameterSchemaRaw: null
|
||||
};
|
||||
|
||||
console.log("Testing convertDatabaseToAction...");
|
||||
try {
|
||||
const result = convertDatabaseToAction(mockDbAction);
|
||||
console.log("Result:", JSON.stringify(result, null, 2));
|
||||
|
||||
if (result.children && result.children.length > 0) {
|
||||
console.log("✅ Children hydrated successfully.");
|
||||
} else {
|
||||
console.error("❌ Children NOT hydrated.");
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("❌ Error during conversion:", e);
|
||||
}
|
||||
71
scripts/archive/test-trpc-client.ts
Normal file
71
scripts/archive/test-trpc-client.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
|
||||
import { appRouter } from "../../src/server/api/root";
|
||||
import { createCallerFactory } from "../../src/server/api/trpc";
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
// 1. Setup DB Context
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
// 2. Mock Session
|
||||
const mockSession = {
|
||||
user: {
|
||||
id: "0e830889-ab46-4b48-a8ba-1d4bd3e665ed", // Admin user ID from seed
|
||||
name: "Sean O'Connor",
|
||||
email: "sean@soconnor.dev"
|
||||
},
|
||||
expires: new Date().toISOString()
|
||||
};
|
||||
|
||||
// 3. Create Caller
|
||||
const createCaller = createCallerFactory(appRouter);
|
||||
const caller = createCaller({
|
||||
db,
|
||||
session: mockSession as any,
|
||||
headers: new Headers()
|
||||
});
|
||||
|
||||
async function main() {
|
||||
console.log("🔍 Fetching experiment via TRPC caller...");
|
||||
|
||||
// Get ID first
|
||||
const exp = await db.query.experiments.findFirst({
|
||||
where: eq(schema.experiments.name, "Control Flow Demo"),
|
||||
columns: { id: true }
|
||||
});
|
||||
|
||||
if (!exp) {
|
||||
console.error("❌ Experiment not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await caller.experiments.get({ id: exp.id });
|
||||
|
||||
console.log(`✅ Fetched experiment: ${result.name} (${result.id})`);
|
||||
|
||||
if (result.steps && result.steps.length > 0) {
|
||||
console.log(`Checking ${result.steps.length} steps...`);
|
||||
const actions = result.steps[0]!.actions; // Step 1 actions
|
||||
console.log(`Step 1 has ${actions.length} actions.`);
|
||||
|
||||
actions.forEach(a => {
|
||||
if (["sequence", "parallel", "loop", "branch"].includes(a.type)) {
|
||||
console.log(`\nAction: ${a.name} (${a.type})`);
|
||||
console.log(`Children Count: ${a.children ? a.children.length : 'UNDEFINED'}`);
|
||||
if (a.children && a.children.length > 0) {
|
||||
console.log(`First Child: ${a.children[0]!.name} (${a.children[0]!.type})`);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.error("❌ No steps found in result.");
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,8 +1,8 @@
|
||||
|
||||
import { db } from "../src/server/db";
|
||||
import { experiments } from "../src/server/db/schema";
|
||||
import { db } from "../../src/server/db";
|
||||
import { experiments } from "../../src/server/db/schema";
|
||||
import { eq, asc } from "drizzle-orm";
|
||||
import { convertDatabaseToSteps } from "../src/lib/experiment-designer/block-converter";
|
||||
import { convertDatabaseToSteps } from "../../src/lib/experiment-designer/block-converter";
|
||||
|
||||
async function verifyConversion() {
|
||||
const experiment = await db.query.experiments.findFirst({
|
||||
@@ -1,7 +1,7 @@
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import { eq, sql } from "drizzle-orm";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../src/server/db/schema";
|
||||
import * as schema from "../../src/server/db/schema";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const client = postgres(connectionString);
|
||||
26
scripts/get-demo-id.ts
Normal file
26
scripts/get-demo-id.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
async function main() {
|
||||
const exp = await db.query.experiments.findFirst({
|
||||
where: eq(schema.experiments.name, "Control Flow Demo"),
|
||||
columns: { id: true }
|
||||
});
|
||||
|
||||
if (exp) {
|
||||
console.log(`Experiment ID: ${exp.id}`);
|
||||
} else {
|
||||
console.error("Experiment not found");
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
main();
|
||||
26
scripts/get-user-id.ts
Normal file
26
scripts/get-user-id.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
|
||||
import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../src/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
const connection = postgres(connectionString);
|
||||
const db = drizzle(connection, { schema });
|
||||
|
||||
async function main() {
|
||||
const user = await db.query.users.findFirst({
|
||||
where: eq(schema.users.email, "sean@soconnor.dev"),
|
||||
columns: { id: true }
|
||||
});
|
||||
|
||||
if (user) {
|
||||
console.log(`User ID: ${user.id}`);
|
||||
} else {
|
||||
console.error("User not found");
|
||||
}
|
||||
|
||||
await connection.end();
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -3,7 +3,7 @@ import { drizzle } from "drizzle-orm/postgres-js";
|
||||
import { sql } from "drizzle-orm";
|
||||
import postgres from "postgres";
|
||||
import * as schema from "../src/server/db/schema";
|
||||
import { createHash } from "crypto";
|
||||
import { createHash, randomUUID } from "crypto";
|
||||
|
||||
// Database connection
|
||||
const connectionString = process.env.DATABASE_URL!;
|
||||
@@ -234,18 +234,18 @@ async function main() {
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step1!.id,
|
||||
name: "Introduce Story",
|
||||
name: "Say Text",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "Hello. I have a story to tell you about a space traveler. Are you ready?" },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction",
|
||||
retryable: true
|
||||
},
|
||||
{
|
||||
stepId: step1!.id,
|
||||
name: "Welcome Gesture",
|
||||
name: "Move Arm",
|
||||
type: "nao6-ros2.move_arm",
|
||||
orderIndex: 1,
|
||||
// Open hand/welcome position
|
||||
@@ -258,7 +258,7 @@ async function main() {
|
||||
speed: 0.4
|
||||
},
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
}
|
||||
@@ -283,29 +283,29 @@ async function main() {
|
||||
orderIndex: 0,
|
||||
parameters: { text: "The traveler flew to Mars. He found a red rock that glowed in the dark. He put it in his pocket." },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction",
|
||||
retryable: true
|
||||
},
|
||||
{
|
||||
stepId: step2!.id,
|
||||
name: "Look Away (Thinking)",
|
||||
name: "Turn Head",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 1,
|
||||
parameters: { yaw: 1.5, pitch: 0.0, speed: 0.3 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
},
|
||||
{
|
||||
stepId: step2!.id,
|
||||
name: "Look Back at Participant",
|
||||
name: "Turn Head",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 2,
|
||||
parameters: { yaw: 0.0, pitch: -0.1, speed: 0.4 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
}
|
||||
@@ -359,12 +359,12 @@ async function main() {
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step3!.id,
|
||||
name: "Ask Question",
|
||||
name: "Say Text",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "What color was the rock the traveler found?" },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction",
|
||||
retryable: true
|
||||
},
|
||||
@@ -397,34 +397,34 @@ async function main() {
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: step4a!.id,
|
||||
name: "Confirm Correct Answer",
|
||||
name: "Say Text with Emotion",
|
||||
type: "nao6-ros2.say_with_emotion",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "Yes! It was a glowing red rock.", emotion: "happy", speed: 1.0 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction",
|
||||
retryable: true
|
||||
},
|
||||
{
|
||||
stepId: step4a!.id,
|
||||
name: "Nod Head",
|
||||
name: "Turn Head",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 1,
|
||||
parameters: { yaw: 0.0, pitch: -0.3, speed: 0.5 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
},
|
||||
{
|
||||
stepId: step4a!.id,
|
||||
name: "Return to Neutral",
|
||||
name: "Turn Head",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 2,
|
||||
parameters: { yaw: 0.0, pitch: 0.0, speed: 0.4 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
}
|
||||
@@ -440,40 +440,40 @@ async function main() {
|
||||
orderIndex: 0,
|
||||
parameters: { text: "Actually, it was red." },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction",
|
||||
retryable: true
|
||||
},
|
||||
{
|
||||
stepId: step4b!.id,
|
||||
name: "Shake Head (Left)",
|
||||
name: "Turn Head",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 1,
|
||||
parameters: { yaw: -0.5, pitch: 0.0, speed: 0.5 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
},
|
||||
{
|
||||
stepId: step4b!.id,
|
||||
name: "Shake Head (Right)",
|
||||
name: "Turn Head",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 2,
|
||||
parameters: { yaw: 0.5, pitch: 0.0, speed: 0.5 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
},
|
||||
{
|
||||
stepId: step4b!.id,
|
||||
name: "Return to Center",
|
||||
name: "Turn Head",
|
||||
type: "nao6-ros2.turn_head",
|
||||
orderIndex: 3,
|
||||
parameters: { yaw: 0.0, pitch: 0.0, speed: 0.4 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
}
|
||||
@@ -498,7 +498,7 @@ async function main() {
|
||||
orderIndex: 0,
|
||||
parameters: { text: "The End. Thank you for listening." },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction",
|
||||
retryable: true
|
||||
},
|
||||
@@ -516,12 +516,209 @@ async function main() {
|
||||
speed: 0.3
|
||||
},
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.1.0",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement",
|
||||
retryable: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 5b. Create "Control Flow Demo" Experiment
|
||||
console.log("🧩 Creating 'Control Flow Demo' experiment...");
|
||||
const [controlDemoExp] = await db.insert(schema.experiments).values({
|
||||
studyId: study!.id,
|
||||
name: "Control Flow Demo",
|
||||
description: "Demonstration of enhanced control flow actions: Parallel, Wait, Loop, Branch.",
|
||||
version: 2,
|
||||
status: "draft",
|
||||
robotId: naoRobot!.id,
|
||||
createdBy: adminUser.id,
|
||||
}).returning();
|
||||
|
||||
// Step 1: Introduction (Parallel)
|
||||
const [cdStep1] = await db.insert(schema.steps).values({
|
||||
experimentId: controlDemoExp!.id,
|
||||
name: "1. Introduction (Parallel)",
|
||||
description: "Parallel execution demonstration",
|
||||
type: "robot",
|
||||
orderIndex: 0,
|
||||
required: true,
|
||||
durationEstimate: 30
|
||||
}).returning();
|
||||
|
||||
// Step 5: Conclusion - Defined early for ID reference (Convergence point)
|
||||
const [cdStep5] = await db.insert(schema.steps).values({
|
||||
experimentId: controlDemoExp!.id,
|
||||
name: "5. Conclusion",
|
||||
description: "Convergence point",
|
||||
type: "robot",
|
||||
orderIndex: 4,
|
||||
required: true,
|
||||
durationEstimate: 15
|
||||
}).returning();
|
||||
|
||||
// Step 4: Path B (Wait) - Defined early for ID reference
|
||||
const [cdStep4] = await db.insert(schema.steps).values({
|
||||
experimentId: controlDemoExp!.id,
|
||||
name: "4. Path B (Wait)",
|
||||
description: "Wait action demonstration",
|
||||
type: "robot",
|
||||
orderIndex: 3,
|
||||
required: true,
|
||||
durationEstimate: 10
|
||||
}).returning();
|
||||
|
||||
// Step 3: Path A (Loop) - Defined early for ID reference
|
||||
const [cdStep3] = await db.insert(schema.steps).values({
|
||||
experimentId: controlDemoExp!.id,
|
||||
name: "3. Path A (Loop)",
|
||||
description: "Looping demonstration",
|
||||
type: "robot",
|
||||
orderIndex: 2,
|
||||
required: true,
|
||||
durationEstimate: 45,
|
||||
conditions: { nextStepId: cdStep5!.id }
|
||||
}).returning();
|
||||
|
||||
// Step 2: Branch Decision
|
||||
const [cdStep2] = await db.insert(schema.steps).values({
|
||||
experimentId: controlDemoExp!.id,
|
||||
name: "2. Branch Decision",
|
||||
description: "Choose between Loop (3) or Wait (4)",
|
||||
type: "conditional",
|
||||
orderIndex: 1,
|
||||
required: true,
|
||||
durationEstimate: 30,
|
||||
conditions: {
|
||||
variable: "demo_branch_choice",
|
||||
options: [
|
||||
{ label: "Go to Loop (Step 3)", value: "loop", nextStepId: cdStep3!.id, variant: "default" },
|
||||
{ label: "Go to Wait (Step 4)", value: "wait", nextStepId: cdStep4!.id, variant: "secondary" }
|
||||
]
|
||||
}
|
||||
}).returning();
|
||||
|
||||
// --- Step 1 Actions (Parallel) ---
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: cdStep1!.id,
|
||||
name: "Parallel Intro",
|
||||
type: "parallel",
|
||||
orderIndex: 0,
|
||||
parameters: {
|
||||
children: [
|
||||
{
|
||||
id: randomUUID(),
|
||||
name: "Say Text",
|
||||
type: "nao6-ros2.say_text",
|
||||
parameters: { text: "Starting control flow demonstration." },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction"
|
||||
},
|
||||
{
|
||||
id: randomUUID(),
|
||||
name: "Move Arm",
|
||||
type: "nao6-ros2.move_arm",
|
||||
parameters: { arm: "right", shoulder_roll: -0.5 },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "movement"
|
||||
}
|
||||
]
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
});
|
||||
|
||||
// --- Step 2 Actions (Branch) ---
|
||||
await db.insert(schema.actions).values([
|
||||
{
|
||||
stepId: cdStep2!.id,
|
||||
name: "Say Text",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "Should I loop or wait?" },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction"
|
||||
},
|
||||
{
|
||||
stepId: cdStep2!.id,
|
||||
name: "Wizard Decision",
|
||||
type: "wizard_wait_for_response",
|
||||
orderIndex: 1,
|
||||
parameters: {
|
||||
prompt_text: "Choose the next path:",
|
||||
options: [
|
||||
{ label: "Loop Path", value: "loop", nextStepId: cdStep3!.id },
|
||||
{ label: "Wait Path", value: "wait", nextStepId: cdStep4!.id }
|
||||
]
|
||||
},
|
||||
pluginId: "hristudio-woz",
|
||||
category: "wizard",
|
||||
sourceKind: "core"
|
||||
},
|
||||
{
|
||||
stepId: cdStep2!.id,
|
||||
name: "Execute Branch",
|
||||
type: "branch",
|
||||
orderIndex: 2,
|
||||
parameters: {},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
}
|
||||
]);
|
||||
|
||||
// --- Step 3 Actions (Loop) ---
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: cdStep3!.id,
|
||||
name: "Loop 3 Times",
|
||||
type: "loop",
|
||||
orderIndex: 0,
|
||||
parameters: {
|
||||
iterations: 3,
|
||||
children: [
|
||||
{
|
||||
id: randomUUID(),
|
||||
name: "Say Text",
|
||||
type: "nao6-ros2.say_text",
|
||||
parameters: { text: "I am looping." },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction"
|
||||
}
|
||||
]
|
||||
},
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
});
|
||||
|
||||
// --- Step 4 Actions (Wait) ---
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: cdStep4!.id,
|
||||
name: "Wait 3 Seconds",
|
||||
type: "wait",
|
||||
orderIndex: 0,
|
||||
parameters: { duration: 3 },
|
||||
pluginId: "hristudio-core",
|
||||
category: "control",
|
||||
sourceKind: "core"
|
||||
});
|
||||
|
||||
// --- Step 5 Actions (Conclusion) ---
|
||||
await db.insert(schema.actions).values({
|
||||
stepId: cdStep5!.id,
|
||||
name: "Say Text",
|
||||
type: "nao6-ros2.say_text",
|
||||
orderIndex: 0,
|
||||
parameters: { text: "Demonstration complete. Returning to start." },
|
||||
pluginId: NAO_PLUGIN_DEF.robotId || "nao6-ros2",
|
||||
pluginVersion: "2.2.0",
|
||||
category: "interaction"
|
||||
});
|
||||
|
||||
// 6. Participants (N=20 for study)
|
||||
console.log("👤 Creating 20 participants for N=20 study...");
|
||||
const participants = [];
|
||||
@@ -550,6 +747,198 @@ async function main() {
|
||||
console.log(` - Step 5: Conclusion (ending + bow)`);
|
||||
console.log(`- ${insertedParticipants.length} Participants`);
|
||||
|
||||
// 7. Seed a COMPLETED trial with rich analytics data for testing
|
||||
console.log("📊 Seeding completed trial with analytics data...");
|
||||
|
||||
// Pick participant P101
|
||||
const p101 = insertedParticipants.find(p => p.participantCode === "P101");
|
||||
if (!p101) throw new Error("P101 not found");
|
||||
|
||||
const startTime = new Date();
|
||||
startTime.setMinutes(startTime.getMinutes() - 10); // Started 10 mins ago
|
||||
const endTime = new Date(); // Ended just now
|
||||
|
||||
// Create the trial
|
||||
const [analyticsTrial] = await db.insert(schema.trials).values({
|
||||
experimentId: experiment!.id,
|
||||
participantId: p101.id,
|
||||
// studyId is not in trials table, it is inferred from experiment
|
||||
status: "completed",
|
||||
startedAt: startTime,
|
||||
completedAt: endTime,
|
||||
currentStepId: step5!.id, // Ended at last step
|
||||
runId: randomUUID(),
|
||||
metadata: {
|
||||
condition: "HRIStudio",
|
||||
notes: "Seeded for analytics testing"
|
||||
}
|
||||
}).returning();
|
||||
|
||||
// Create a series of events
|
||||
const timelineEvents = [];
|
||||
let currentTime = new Date(startTime);
|
||||
|
||||
// Helper to advance time
|
||||
const advance = (seconds: number) => {
|
||||
currentTime = new Date(currentTime.getTime() + seconds * 1000);
|
||||
return currentTime;
|
||||
};
|
||||
|
||||
// 1. Trial Started
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "trial_started",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { experimentId: experiment!.id, participantId: p101.id }
|
||||
});
|
||||
|
||||
// 2. Step 1: The Hook
|
||||
advance(2);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "step_changed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { stepId: step1!.id, stepName: "The Hook" }
|
||||
});
|
||||
|
||||
advance(1);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "action_executed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { actionName: "Say Text", text: "Hello..." }
|
||||
});
|
||||
|
||||
advance(5);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "action_executed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { actionName: "Move Arm", arm: "right" }
|
||||
});
|
||||
|
||||
// 3. Step 2: The Narrative
|
||||
advance(20);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "step_changed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { stepId: step2!.id, stepName: "The Narrative" }
|
||||
});
|
||||
|
||||
advance(2);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "action_executed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { actionName: "Tell Story" }
|
||||
});
|
||||
|
||||
// Simulate an intervention/wizard action
|
||||
advance(15);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "intervention",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { type: "pause", reason: "participant_distracted" }
|
||||
});
|
||||
|
||||
advance(10); // Paused for 10s
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "intervention",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { type: "resume" }
|
||||
});
|
||||
|
||||
advance(2);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "action_executed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { actionName: "Turn Head", yaw: 1.5 }
|
||||
});
|
||||
|
||||
// 4. Step 3: Comprehension Check
|
||||
advance(30);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "step_changed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { stepId: step3!.id, stepName: "Comprehension Check" }
|
||||
});
|
||||
|
||||
advance(1);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "action_executed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { actionName: "Say Text", text: "What color..." }
|
||||
});
|
||||
|
||||
advance(5);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "wizard_action",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { action: "wait_for_response", prompt: "Did they answer Red?" }
|
||||
});
|
||||
|
||||
// Wizard selects "Correct"
|
||||
advance(8);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "wizard_response",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { response: "Correct", variable: "last_wizard_response" }
|
||||
});
|
||||
|
||||
// 5. Branch A
|
||||
advance(1);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "step_changed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { stepId: step4a!.id, stepName: "Branch A: Correct" }
|
||||
});
|
||||
|
||||
advance(1);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "action_executed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { actionName: "Say Text with Emotion", emotion: "happy" }
|
||||
});
|
||||
|
||||
// 6. Conclusion
|
||||
advance(15);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "step_changed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { stepId: step5!.id, stepName: "Conclusion" }
|
||||
});
|
||||
|
||||
advance(2);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "action_executed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { actionName: "End Story" }
|
||||
});
|
||||
|
||||
// Trial Complete
|
||||
advance(5);
|
||||
timelineEvents.push({
|
||||
trialId: analyticsTrial!.id,
|
||||
eventType: "trial_completed",
|
||||
timestamp: new Date(currentTime),
|
||||
data: { durationSeconds: (currentTime.getTime() - startTime.getTime()) / 1000 }
|
||||
});
|
||||
|
||||
await db.insert(schema.trialEvents).values(timelineEvents);
|
||||
console.log("✅ Seeded 1 completed trial with " + timelineEvents.length + " events.");
|
||||
|
||||
} catch (error) {
|
||||
console.error("❌ Seeding failed:", error);
|
||||
process.exit(1);
|
||||
|
||||
Reference in New Issue
Block a user