Merge remote-tracking branch 'old-github/main' into integrate-old-refactors-of-github

This commit is contained in:
Alireza Vaezi
2026-03-09 13:10:08 -04:00
35 changed files with 2878 additions and 688 deletions

8
supabase/.gitignore vendored Normal file
View File

@@ -0,0 +1,8 @@
# Supabase
.branches
.temp
# dotenvx
.env.keys
.env.local
.env.*.local

View File

@@ -64,9 +64,9 @@ supabase gen types typescript --local > management-dashboard-web-app/src/types/s
## Seed Data
Seed files are run automatically after migrations when using docker-compose. They populate the database with initial data:
- `seed_01_users.sql`: Creates admin user and initial user profiles
- `seed_02_phase2_experiments.sql`: Creates initial experiment data
Seed files are run automatically after migrations when using `supabase db reset` (see `config.toml` → `[db.seed]` → `sql_paths`). Currently only user seed is enabled:
- `seed_01_users.sql`: Creates admin user and initial user profiles (enabled)
- `seed_02_phase2_experiments.sql`: Initial experiment data (temporarily disabled; add back to `sql_paths` in `config.toml` to re-enable)
## Configuration

View File

@@ -0,0 +1,14 @@
-- Test migration to create experiment_phases table
CREATE TABLE IF NOT EXISTS public.experiment_phases (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL UNIQUE,
description TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_by UUID NOT NULL
);
-- Insert test data
INSERT INTO public.experiment_phases (name, description, created_by)
VALUES ('Phase 2 of JC Experiments', 'Second phase of JC Cracker experiments', '00000000-0000-0000-0000-000000000000')
ON CONFLICT (name) DO NOTHING;

View File

@@ -57,7 +57,9 @@ schema_paths = []
enabled = true
# Specifies an ordered list of seed files to load during db reset.
# Supports glob patterns relative to supabase directory: "./seeds/*.sql"
sql_paths = ["./seed_01_users.sql", "./seed_02_phase2_experiments.sql"]
# Temporarily only user seed; other seeds suppressed.
sql_paths = ["./seed_01_users.sql"]
# sql_paths = ["./seed_01_users.sql", "./seed_02_phase2_experiments.sql"]
# , "./seed_04_phase2_jc_experiments.sql", "./seed_05_meyer_experiments.sql"]
[db.network_restrictions]

View File

@@ -70,6 +70,10 @@ CREATE TABLE IF NOT EXISTS public.shelling (
scheduled_start_time TIMESTAMP WITH TIME ZONE NOT NULL,
actual_start_time TIMESTAMP WITH TIME ZONE,
actual_end_time TIMESTAMP WITH TIME ZONE,
-- The space (in inches) between the sheller's rings
ring_gap_inches NUMERIC(6,2) CHECK (ring_gap_inches > 0),
-- The revolutions per minute for the sheller drum
drum_rpm INTEGER CHECK (drum_rpm > 0),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_by UUID NOT NULL REFERENCES public.user_profiles(id),

View File

@@ -56,6 +56,80 @@ CREATE INDEX IF NOT EXISTS idx_phase_executions_machine_type_id
CREATE INDEX IF NOT EXISTS idx_phase_executions_created_by
ON public.experiment_phase_executions(created_by);
-- =============================================
-- 2.5. CREATE CONDUCTOR ASSIGNMENTS TABLE
-- =============================================
-- Table to store conductor assignments to phase executions
-- This allows multiple conductors to be assigned to each phase execution
CREATE TABLE IF NOT EXISTS public.experiment_phase_assignments (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
phase_execution_id UUID NOT NULL REFERENCES public.experiment_phase_executions(id) ON DELETE CASCADE,
conductor_id UUID NOT NULL REFERENCES public.user_profiles(id) ON DELETE CASCADE,
-- Scheduled times for this assignment (should match phase_execution times, but stored for clarity)
scheduled_start_time TIMESTAMP WITH TIME ZONE NOT NULL,
scheduled_end_time TIMESTAMP WITH TIME ZONE,
-- Status tracking
status TEXT NOT NULL DEFAULT 'scheduled'
CHECK (status IN ('scheduled', 'in_progress', 'completed', 'cancelled')),
-- Optional notes about the assignment
notes TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_by UUID NOT NULL REFERENCES public.user_profiles(id),
-- Ensure scheduled_end_time is after scheduled_start_time
CONSTRAINT valid_scheduled_time_range CHECK (scheduled_end_time IS NULL OR scheduled_end_time > scheduled_start_time),
-- Ensure unique assignment per conductor per phase execution
CONSTRAINT unique_conductor_phase_execution UNIQUE (phase_execution_id, conductor_id)
);
-- Indexes for conductor assignments
CREATE INDEX IF NOT EXISTS idx_phase_assignments_phase_execution_id
ON public.experiment_phase_assignments(phase_execution_id);
CREATE INDEX IF NOT EXISTS idx_phase_assignments_conductor_id
ON public.experiment_phase_assignments(conductor_id);
CREATE INDEX IF NOT EXISTS idx_phase_assignments_status
ON public.experiment_phase_assignments(status);
CREATE INDEX IF NOT EXISTS idx_phase_assignments_scheduled_start_time
ON public.experiment_phase_assignments(scheduled_start_time);
CREATE INDEX IF NOT EXISTS idx_phase_assignments_created_by
ON public.experiment_phase_assignments(created_by);
-- Trigger for updated_at on conductor assignments
CREATE TRIGGER set_updated_at_phase_assignments
BEFORE UPDATE ON public.experiment_phase_assignments
FOR EACH ROW
EXECUTE FUNCTION public.handle_updated_at();
-- Grant permissions
GRANT ALL ON public.experiment_phase_assignments TO authenticated;
-- Enable Row Level Security
ALTER TABLE public.experiment_phase_assignments ENABLE ROW LEVEL SECURITY;
-- RLS Policies for conductor assignments
CREATE POLICY "Phase assignments are viewable by authenticated users"
ON public.experiment_phase_assignments
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "Phase assignments are insertable by authenticated users"
ON public.experiment_phase_assignments
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Phase assignments are updatable by authenticated users"
ON public.experiment_phase_assignments
FOR UPDATE USING (auth.role() = 'authenticated');
CREATE POLICY "Phase assignments are deletable by authenticated users"
ON public.experiment_phase_assignments
FOR DELETE USING (auth.role() = 'authenticated');
-- =============================================
-- 3. FUNCTION: Calculate Sequential Phase Start Times
-- =============================================

View File

@@ -0,0 +1,9 @@
-- Add experiment-level shelling parameters (defaults for repetitions)
-- These match the shelling table attributes: ring_gap_inches, drum_rpm
ALTER TABLE public.experiments
ADD COLUMN IF NOT EXISTS ring_gap_inches NUMERIC(6,2) CHECK (ring_gap_inches IS NULL OR ring_gap_inches > 0),
ADD COLUMN IF NOT EXISTS drum_rpm INTEGER CHECK (drum_rpm IS NULL OR drum_rpm > 0);
COMMENT ON COLUMN public.experiments.ring_gap_inches IS 'Default space (inches) between sheller rings for this experiment';
COMMENT ON COLUMN public.experiments.drum_rpm IS 'Default sheller drum revolutions per minute for this experiment';

View File

@@ -0,0 +1,399 @@
-- Rename table experiment_phases to experiment_books
-- This migration renames the table and updates all dependent objects (views, functions, triggers, indexes, RLS).
-- =============================================
-- 1. RENAME TABLE
-- =============================================
ALTER TABLE public.experiment_phases RENAME TO experiment_books;
-- =============================================
-- 2. RENAME TRIGGER
-- =============================================
DROP TRIGGER IF EXISTS set_updated_at_experiment_phases ON public.experiment_books;
CREATE TRIGGER set_updated_at_experiment_books
BEFORE UPDATE ON public.experiment_books
FOR EACH ROW
EXECUTE FUNCTION public.handle_updated_at();
-- =============================================
-- 3. RENAME CONSTRAINT
-- =============================================
ALTER TABLE public.experiment_books
RENAME CONSTRAINT ck_experiment_phases_machine_required_when_cracking
TO ck_experiment_books_machine_required_when_cracking;
-- =============================================
-- 4. RENAME INDEXES
-- =============================================
ALTER INDEX IF EXISTS public.idx_experiment_phases_name RENAME TO idx_experiment_books_name;
ALTER INDEX IF EXISTS public.idx_experiment_phases_cracking_machine_type_id RENAME TO idx_experiment_books_cracking_machine_type_id;
-- =============================================
-- 5. RLS POLICIES (drop old, create new with updated names)
-- =============================================
DROP POLICY IF EXISTS "Experiment phases are viewable by authenticated users" ON public.experiment_books;
DROP POLICY IF EXISTS "Experiment phases are insertable by authenticated users" ON public.experiment_books;
DROP POLICY IF EXISTS "Experiment phases are updatable by authenticated users" ON public.experiment_books;
DROP POLICY IF EXISTS "Experiment phases are deletable by authenticated users" ON public.experiment_books;
CREATE POLICY "Experiment books are viewable by authenticated users" ON public.experiment_books
FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment books are insertable by authenticated users" ON public.experiment_books
FOR INSERT WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Experiment books are updatable by authenticated users" ON public.experiment_books
FOR UPDATE USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment books are deletable by authenticated users" ON public.experiment_books
FOR DELETE USING (auth.role() = 'authenticated');
-- =============================================
-- 6. UPDATE FUNCTION: create_phase_executions_for_repetition (references experiment_phases)
-- =============================================
CREATE OR REPLACE FUNCTION create_phase_executions_for_repetition()
RETURNS TRIGGER AS $$
DECLARE
exp_phase_config RECORD;
phase_type_list TEXT[] := ARRAY[]::TEXT[];
phase_name TEXT;
BEGIN
SELECT
ep.has_soaking,
ep.has_airdrying,
ep.has_cracking,
ep.has_shelling,
ep.cracking_machine_type_id
INTO exp_phase_config
FROM public.experiments e
JOIN public.experiment_books ep ON e.phase_id = ep.id
WHERE e.id = NEW.experiment_id;
IF exp_phase_config.has_soaking THEN
phase_type_list := array_append(phase_type_list, 'soaking');
END IF;
IF exp_phase_config.has_airdrying THEN
phase_type_list := array_append(phase_type_list, 'airdrying');
END IF;
IF exp_phase_config.has_cracking THEN
phase_type_list := array_append(phase_type_list, 'cracking');
END IF;
IF exp_phase_config.has_shelling THEN
phase_type_list := array_append(phase_type_list, 'shelling');
END IF;
FOREACH phase_name IN ARRAY phase_type_list
LOOP
INSERT INTO public.experiment_phase_executions (
repetition_id,
phase_type,
scheduled_start_time,
status,
created_by,
soaking_duration_minutes,
duration_minutes,
machine_type_id
)
VALUES (
NEW.id,
phase_name,
NOW(),
'pending',
NEW.created_by,
NULL,
NULL,
CASE WHEN phase_name = 'cracking'
THEN exp_phase_config.cracking_machine_type_id
ELSE NULL END
);
END LOOP;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
-- =============================================
-- 7. UPDATE FUNCTION: create_sample_experiment_phases (INSERT into experiment_books)
-- =============================================
CREATE OR REPLACE FUNCTION public.create_sample_experiment_phases()
RETURNS VOID AS $$
DECLARE
jc_cracker_id UUID;
meyer_cracker_id UUID;
BEGIN
SELECT id INTO jc_cracker_id FROM public.machine_types WHERE name = 'JC Cracker';
SELECT id INTO meyer_cracker_id FROM public.machine_types WHERE name = 'Meyer Cracker';
INSERT INTO public.experiment_books (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by) VALUES
('Full Process - JC Cracker', 'Complete pecan processing with JC Cracker', true, true, true, true, jc_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)),
('Full Process - Meyer Cracker', 'Complete pecan processing with Meyer Cracker', true, true, true, true, meyer_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)),
('Cracking Only - JC Cracker', 'JC Cracker cracking process only', false, false, true, false, jc_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1)),
('Cracking Only - Meyer Cracker', 'Meyer Cracker cracking process only', false, false, true, false, meyer_cracker_id, (SELECT id FROM public.user_profiles LIMIT 1))
ON CONFLICT (name) DO NOTHING;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
-- =============================================
-- 8. UPDATE VIEWS (from 00014 - experiments_with_phases, repetitions_with_phases, experiments_with_all_reps_and_phases, get_experiment_with_reps_and_phases)
-- =============================================
CREATE OR REPLACE VIEW public.experiments_with_phases AS
SELECT
e.id,
e.experiment_number,
e.reps_required,
e.weight_per_repetition_lbs,
e.results_status,
e.completion_status,
e.phase_id,
e.created_at,
e.updated_at,
e.created_by,
ep.name as phase_name,
ep.description as phase_description,
ep.has_soaking,
ep.has_airdrying,
ep.has_cracking,
ep.has_shelling,
er.id as first_repetition_id,
er.repetition_number as first_repetition_number,
soaking_e.id as soaking_id,
soaking_e.scheduled_start_time as soaking_scheduled_start,
soaking_e.actual_start_time as soaking_actual_start,
soaking_e.soaking_duration_minutes,
soaking_e.scheduled_end_time as soaking_scheduled_end,
soaking_e.actual_end_time as soaking_actual_end,
airdrying_e.id as airdrying_id,
airdrying_e.scheduled_start_time as airdrying_scheduled_start,
airdrying_e.actual_start_time as airdrying_actual_start,
airdrying_e.duration_minutes as airdrying_duration,
airdrying_e.scheduled_end_time as airdrying_scheduled_end,
airdrying_e.actual_end_time as airdrying_actual_end,
cracking_e.id as cracking_id,
cracking_e.scheduled_start_time as cracking_scheduled_start,
cracking_e.actual_start_time as cracking_actual_start,
cracking_e.actual_end_time as cracking_actual_end,
mt.name as machine_type_name,
shelling_e.id as shelling_id,
shelling_e.scheduled_start_time as shelling_scheduled_start,
shelling_e.actual_start_time as shelling_actual_start,
shelling_e.actual_end_time as shelling_actual_end
FROM public.experiments e
LEFT JOIN public.experiment_books ep ON e.phase_id = ep.id
LEFT JOIN LATERAL (
SELECT id, repetition_number
FROM public.experiment_repetitions
WHERE experiment_id = e.id
ORDER BY repetition_number
LIMIT 1
) er ON true
LEFT JOIN public.experiment_phase_executions soaking_e
ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking'
LEFT JOIN public.experiment_phase_executions airdrying_e
ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying'
LEFT JOIN public.experiment_phase_executions cracking_e
ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking'
LEFT JOIN public.experiment_phase_executions shelling_e
ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling'
LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id;
CREATE OR REPLACE VIEW public.repetitions_with_phases AS
SELECT
er.id,
er.experiment_id,
er.repetition_number,
er.status,
er.created_at,
er.updated_at,
er.created_by,
e.experiment_number,
e.phase_id,
e.weight_per_repetition_lbs,
ep.name as phase_name,
ep.has_soaking,
ep.has_airdrying,
ep.has_cracking,
ep.has_shelling,
soaking_e.scheduled_start_time as soaking_scheduled_start,
soaking_e.actual_start_time as soaking_actual_start,
soaking_e.soaking_duration_minutes,
soaking_e.scheduled_end_time as soaking_scheduled_end,
soaking_e.actual_end_time as soaking_actual_end,
airdrying_e.scheduled_start_time as airdrying_scheduled_start,
airdrying_e.actual_start_time as airdrying_actual_start,
airdrying_e.duration_minutes as airdrying_duration,
airdrying_e.scheduled_end_time as airdrying_scheduled_end,
airdrying_e.actual_end_time as airdrying_actual_end,
cracking_e.scheduled_start_time as cracking_scheduled_start,
cracking_e.actual_start_time as cracking_actual_start,
cracking_e.actual_end_time as cracking_actual_end,
mt.name as machine_type_name,
shelling_e.scheduled_start_time as shelling_scheduled_start,
shelling_e.actual_start_time as shelling_actual_start,
shelling_e.actual_end_time as shelling_actual_end
FROM public.experiment_repetitions er
JOIN public.experiments e ON er.experiment_id = e.id
LEFT JOIN public.experiment_books ep ON e.phase_id = ep.id
LEFT JOIN public.experiment_phase_executions soaking_e
ON er.id = soaking_e.repetition_id AND soaking_e.phase_type = 'soaking'
LEFT JOIN public.experiment_phase_executions airdrying_e
ON er.id = airdrying_e.repetition_id AND airdrying_e.phase_type = 'airdrying'
LEFT JOIN public.experiment_phase_executions cracking_e
ON er.id = cracking_e.repetition_id AND cracking_e.phase_type = 'cracking'
LEFT JOIN public.experiment_phase_executions shelling_e
ON er.id = shelling_e.repetition_id AND shelling_e.phase_type = 'shelling'
LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id;
-- experiments_with_all_reps_and_phases
CREATE OR REPLACE VIEW public.experiments_with_all_reps_and_phases AS
SELECT
e.id as experiment_id,
e.experiment_number,
e.reps_required,
e.weight_per_repetition_lbs,
e.results_status,
e.completion_status,
e.phase_id,
e.created_at as experiment_created_at,
e.updated_at as experiment_updated_at,
e.created_by as experiment_created_by,
ep.name as phase_name,
ep.description as phase_description,
ep.has_soaking,
ep.has_airdrying,
ep.has_cracking,
ep.has_shelling,
ep.cracking_machine_type_id as phase_cracking_machine_type_id,
er.id as repetition_id,
er.repetition_number,
er.status as repetition_status,
er.scheduled_date,
er.created_at as repetition_created_at,
er.updated_at as repetition_updated_at,
er.created_by as repetition_created_by,
soaking_e.id as soaking_execution_id,
soaking_e.scheduled_start_time as soaking_scheduled_start,
soaking_e.actual_start_time as soaking_actual_start,
soaking_e.soaking_duration_minutes,
soaking_e.scheduled_end_time as soaking_scheduled_end,
soaking_e.actual_end_time as soaking_actual_end,
soaking_e.status as soaking_status,
airdrying_e.id as airdrying_execution_id,
airdrying_e.scheduled_start_time as airdrying_scheduled_start,
airdrying_e.actual_start_time as airdrying_actual_start,
airdrying_e.duration_minutes as airdrying_duration_minutes,
airdrying_e.scheduled_end_time as airdrying_scheduled_end,
airdrying_e.actual_end_time as airdrying_actual_end,
airdrying_e.status as airdrying_status,
cracking_e.id as cracking_execution_id,
cracking_e.scheduled_start_time as cracking_scheduled_start,
cracking_e.actual_start_time as cracking_actual_start,
cracking_e.scheduled_end_time as cracking_scheduled_end,
cracking_e.actual_end_time as cracking_actual_end,
cracking_e.machine_type_id as cracking_machine_type_id,
cracking_e.status as cracking_status,
mt.name as machine_type_name,
shelling_e.id as shelling_execution_id,
shelling_e.scheduled_start_time as shelling_scheduled_start,
shelling_e.actual_start_time as shelling_actual_start,
shelling_e.scheduled_end_time as shelling_scheduled_end,
shelling_e.actual_end_time as shelling_actual_end,
shelling_e.status as shelling_status
FROM public.experiments e
LEFT JOIN public.experiment_books ep ON e.phase_id = ep.id
LEFT JOIN public.experiment_repetitions er ON er.experiment_id = e.id
LEFT JOIN public.experiment_phase_executions soaking_e
ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking'
LEFT JOIN public.experiment_phase_executions airdrying_e
ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying'
LEFT JOIN public.experiment_phase_executions cracking_e
ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking'
LEFT JOIN public.experiment_phase_executions shelling_e
ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling'
LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id
ORDER BY e.experiment_number, er.repetition_number;
-- get_experiment_with_reps_and_phases function
CREATE OR REPLACE FUNCTION public.get_experiment_with_reps_and_phases(p_experiment_id UUID)
RETURNS TABLE (
experiment_id UUID,
experiment_number INTEGER,
phase_name TEXT,
repetitions JSONB
) AS $$
BEGIN
RETURN QUERY
SELECT
e.id,
e.experiment_number,
ep.name,
COALESCE(
jsonb_agg(
jsonb_build_object(
'repetition_id', er.id,
'repetition_number', er.repetition_number,
'status', er.status,
'scheduled_date', er.scheduled_date,
'soaking', jsonb_build_object(
'scheduled_start', soaking_e.scheduled_start_time,
'actual_start', soaking_e.actual_start_time,
'duration_minutes', soaking_e.soaking_duration_minutes,
'scheduled_end', soaking_e.scheduled_end_time,
'actual_end', soaking_e.actual_end_time,
'status', soaking_e.status
),
'airdrying', jsonb_build_object(
'scheduled_start', airdrying_e.scheduled_start_time,
'actual_start', airdrying_e.actual_start_time,
'duration_minutes', airdrying_e.duration_minutes,
'scheduled_end', airdrying_e.scheduled_end_time,
'actual_end', airdrying_e.actual_end_time,
'status', airdrying_e.status
),
'cracking', jsonb_build_object(
'scheduled_start', cracking_e.scheduled_start_time,
'actual_start', cracking_e.actual_start_time,
'scheduled_end', cracking_e.scheduled_end_time,
'actual_end', cracking_e.actual_end_time,
'machine_type_id', cracking_e.machine_type_id,
'machine_type_name', mt.name,
'status', cracking_e.status
),
'shelling', jsonb_build_object(
'scheduled_start', shelling_e.scheduled_start_time,
'actual_start', shelling_e.actual_start_time,
'scheduled_end', shelling_e.scheduled_end_time,
'actual_end', shelling_e.actual_end_time,
'status', shelling_e.status
)
)
ORDER BY er.repetition_number
),
'[]'::jsonb
) as repetitions
FROM public.experiments e
LEFT JOIN public.experiment_books ep ON e.phase_id = ep.id
LEFT JOIN public.experiment_repetitions er ON er.experiment_id = e.id
LEFT JOIN public.experiment_phase_executions soaking_e
ON soaking_e.repetition_id = er.id AND soaking_e.phase_type = 'soaking'
LEFT JOIN public.experiment_phase_executions airdrying_e
ON airdrying_e.repetition_id = er.id AND airdrying_e.phase_type = 'airdrying'
LEFT JOIN public.experiment_phase_executions cracking_e
ON cracking_e.repetition_id = er.id AND cracking_e.phase_type = 'cracking'
LEFT JOIN public.experiment_phase_executions shelling_e
ON shelling_e.repetition_id = er.id AND shelling_e.phase_type = 'shelling'
LEFT JOIN public.machine_types mt ON cracking_e.machine_type_id = mt.id
WHERE e.id = p_experiment_id
GROUP BY e.id, e.experiment_number, ep.name;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
GRANT SELECT ON public.experiments_with_all_reps_and_phases TO authenticated;
GRANT EXECUTE ON FUNCTION public.get_experiment_with_reps_and_phases(UUID) TO authenticated;

View File

@@ -0,0 +1,118 @@
-- Experiment-level phase config tables
-- One row per experiment per phase; linked by experiment_id. Used when creating an experiment
-- so soaking, airdrying, cracking, and shelling parameters are stored and can be applied to repetitions.
-- =============================================
-- 1. EXPERIMENT_SOAKING (template for soaking phase)
-- =============================================
CREATE TABLE IF NOT EXISTS public.experiment_soaking (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
experiment_id UUID NOT NULL REFERENCES public.experiments(id) ON DELETE CASCADE,
soaking_duration_hr DOUBLE PRECISION NOT NULL CHECK (soaking_duration_hr >= 0),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_by UUID NOT NULL REFERENCES public.user_profiles(id),
CONSTRAINT unique_experiment_soaking_per_experiment UNIQUE (experiment_id)
);
CREATE INDEX IF NOT EXISTS idx_experiment_soaking_experiment_id ON public.experiment_soaking(experiment_id);
GRANT ALL ON public.experiment_soaking TO authenticated;
ALTER TABLE public.experiment_soaking ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Experiment soaking config is viewable by authenticated" ON public.experiment_soaking FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment soaking config is insertable by authenticated" ON public.experiment_soaking FOR INSERT WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Experiment soaking config is updatable by authenticated" ON public.experiment_soaking FOR UPDATE USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment soaking config is deletable by authenticated" ON public.experiment_soaking FOR DELETE USING (auth.role() = 'authenticated');
CREATE TRIGGER set_updated_at_experiment_soaking
BEFORE UPDATE ON public.experiment_soaking
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
-- =============================================
-- 2. EXPERIMENT_AIRDRYING (template for airdrying phase)
-- =============================================
CREATE TABLE IF NOT EXISTS public.experiment_airdrying (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
experiment_id UUID NOT NULL REFERENCES public.experiments(id) ON DELETE CASCADE,
duration_minutes INTEGER NOT NULL CHECK (duration_minutes >= 0),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_by UUID NOT NULL REFERENCES public.user_profiles(id),
CONSTRAINT unique_experiment_airdrying_per_experiment UNIQUE (experiment_id)
);
CREATE INDEX IF NOT EXISTS idx_experiment_airdrying_experiment_id ON public.experiment_airdrying(experiment_id);
GRANT ALL ON public.experiment_airdrying TO authenticated;
ALTER TABLE public.experiment_airdrying ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Experiment airdrying config is viewable by authenticated" ON public.experiment_airdrying FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment airdrying config is insertable by authenticated" ON public.experiment_airdrying FOR INSERT WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Experiment airdrying config is updatable by authenticated" ON public.experiment_airdrying FOR UPDATE USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment airdrying config is deletable by authenticated" ON public.experiment_airdrying FOR DELETE USING (auth.role() = 'authenticated');
CREATE TRIGGER set_updated_at_experiment_airdrying
BEFORE UPDATE ON public.experiment_airdrying
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
-- =============================================
-- 3. EXPERIMENT_CRACKING (template for cracking; supports JC and Meyer params)
-- =============================================
CREATE TABLE IF NOT EXISTS public.experiment_cracking (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
experiment_id UUID NOT NULL REFERENCES public.experiments(id) ON DELETE CASCADE,
machine_type_id UUID NOT NULL REFERENCES public.machine_types(id) ON DELETE RESTRICT,
-- JC Cracker parameters (nullable; used when machine is JC)
plate_contact_frequency_hz DOUBLE PRECISION CHECK (plate_contact_frequency_hz IS NULL OR plate_contact_frequency_hz > 0),
throughput_rate_pecans_sec DOUBLE PRECISION CHECK (throughput_rate_pecans_sec IS NULL OR throughput_rate_pecans_sec > 0),
crush_amount_in DOUBLE PRECISION CHECK (crush_amount_in IS NULL OR crush_amount_in >= 0),
entry_exit_height_diff_in DOUBLE PRECISION,
-- Meyer Cracker parameters (nullable; used when machine is Meyer)
motor_speed_hz DOUBLE PRECISION CHECK (motor_speed_hz IS NULL OR motor_speed_hz > 0),
jig_displacement_inches DOUBLE PRECISION CHECK (jig_displacement_inches IS NULL OR jig_displacement_inches >= 0),
spring_stiffness_nm DOUBLE PRECISION CHECK (spring_stiffness_nm IS NULL OR spring_stiffness_nm > 0),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_by UUID NOT NULL REFERENCES public.user_profiles(id),
CONSTRAINT unique_experiment_cracking_per_experiment UNIQUE (experiment_id)
);
CREATE INDEX IF NOT EXISTS idx_experiment_cracking_experiment_id ON public.experiment_cracking(experiment_id);
CREATE INDEX IF NOT EXISTS idx_experiment_cracking_machine_type_id ON public.experiment_cracking(machine_type_id);
GRANT ALL ON public.experiment_cracking TO authenticated;
ALTER TABLE public.experiment_cracking ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Experiment cracking config is viewable by authenticated" ON public.experiment_cracking FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment cracking config is insertable by authenticated" ON public.experiment_cracking FOR INSERT WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Experiment cracking config is updatable by authenticated" ON public.experiment_cracking FOR UPDATE USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment cracking config is deletable by authenticated" ON public.experiment_cracking FOR DELETE USING (auth.role() = 'authenticated');
CREATE TRIGGER set_updated_at_experiment_cracking
BEFORE UPDATE ON public.experiment_cracking
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();
-- =============================================
-- 4. EXPERIMENT_SHELLING (template for shelling phase)
-- =============================================
CREATE TABLE IF NOT EXISTS public.experiment_shelling (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
experiment_id UUID NOT NULL REFERENCES public.experiments(id) ON DELETE CASCADE,
ring_gap_inches NUMERIC(6,2) CHECK (ring_gap_inches IS NULL OR ring_gap_inches > 0),
drum_rpm INTEGER CHECK (drum_rpm IS NULL OR drum_rpm > 0),
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
created_by UUID NOT NULL REFERENCES public.user_profiles(id),
CONSTRAINT unique_experiment_shelling_per_experiment UNIQUE (experiment_id)
);
CREATE INDEX IF NOT EXISTS idx_experiment_shelling_experiment_id ON public.experiment_shelling(experiment_id);
GRANT ALL ON public.experiment_shelling TO authenticated;
ALTER TABLE public.experiment_shelling ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Experiment shelling config is viewable by authenticated" ON public.experiment_shelling FOR SELECT USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment shelling config is insertable by authenticated" ON public.experiment_shelling FOR INSERT WITH CHECK (auth.role() = 'authenticated');
CREATE POLICY "Experiment shelling config is updatable by authenticated" ON public.experiment_shelling FOR UPDATE USING (auth.role() = 'authenticated');
CREATE POLICY "Experiment shelling config is deletable by authenticated" ON public.experiment_shelling FOR DELETE USING (auth.role() = 'authenticated');
CREATE TRIGGER set_updated_at_experiment_shelling
BEFORE UPDATE ON public.experiment_shelling
FOR EACH ROW EXECUTE FUNCTION public.handle_updated_at();

View File

@@ -566,11 +566,11 @@ INSERT INTO public.machine_types (name, description, created_by) VALUES
ON CONFLICT (name) DO NOTHING;
-- =============================================
-- 5. CREATE EXPERIMENT PHASES
-- 5. CREATE EXPERIMENT BOOKS (table renamed from experiment_phases in migration 00016)
-- =============================================
-- Create "Phase 2 of JC Experiments" phase
INSERT INTO public.experiment_phases (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by)
-- Create "Phase 2 of JC Experiments" book
INSERT INTO public.experiment_books (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by)
SELECT
'Phase 2 of JC Experiments',
'Second phase of JC Cracker experiments for pecan processing optimization',
@@ -584,8 +584,8 @@ FROM public.user_profiles up
WHERE up.email = 's.alireza.v@gmail.com'
;
-- Create "Post Workshop Meyer Experiments" phase
INSERT INTO public.experiment_phases (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by)
-- Create "Post Workshop Meyer Experiments" book
INSERT INTO public.experiment_books (name, description, has_soaking, has_airdrying, has_cracking, has_shelling, cracking_machine_type_id, created_by)
SELECT
'Post Workshop Meyer Experiments',
'Post workshop Meyer Cracker experiments for pecan processing optimization',

View File

@@ -1,7 +1,9 @@
-- ==============================================
-- 6. CREATE EXPERIMENTS FOR PHASE 2
-- ==============================================
-- TEMPORARILY DISABLED (see config.toml sql_paths). When re-enabling, replace
-- all "experiment_phases" with "experiment_books" (table renamed in migration 00016).
--
-- This seed file creates experiments from phase_2_JC_experimental_run_sheet.csv
-- Each experiment has 3 repetitions with specific parameters
-- Experiment numbers are incremented by 1 (CSV 0-19 becomes DB 1-20)