first commit
CI / lint-and-test (push) Canceled after 0s

This commit is contained in:
Alberto-Audrix committed 2026-07-28 08:55:05 +07:00
commit 32a36cceff
444 files changed
+67186

No files matched your search

@@ -0,0 +1,15 @@
-- Migration: Add chicken_count and population columns to mortality_records table
-- Date: 2025-12-30
-- Description: Adds chicken_count (from AI counting) and population (manual override) columns
-- Add chicken_count column (nullable)
ALTER TABLE mortality_records
ADD COLUMN IF NOT EXISTS chicken_count INTEGER;
-- Add population column (nullable)
ALTER TABLE mortality_records
ADD COLUMN IF NOT EXISTS population INTEGER;
-- Add comments for documentation
COMMENT ON COLUMN mortality_records.chicken_count IS 'Chicken count from AI camera counting system';
COMMENT ON COLUMN mortality_records.population IS 'Manual population override value';
@@ -0,0 +1,10 @@
-- Migration tracking table
-- This table keeps track of which migrations have been applied
CREATE TABLE IF NOT EXISTS schema_migrations (
id SERIAL PRIMARY KEY,
filename VARCHAR(255) UNIQUE NOT NULL,
applied_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Create index for faster lookups
CREATE INDEX IF NOT EXISTS idx_schema_migrations_filename ON schema_migrations(filename);
@@ -0,0 +1,74 @@
-- Migration: Add kandang management
-- Date: 2026-02-26
-- Description: Adds kandangs table, kandang_cycles junction table, and kandang_id to mortality_records
-- Also auto-assigns existing mortality records to a default "Kandang 1"
-- 1. Create kandangs table
CREATE TABLE IF NOT EXISTS kandangs (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);
-- 2. Create kandang_cycles junction table (doc_in_count per kandang per cycle)
CREATE TABLE IF NOT EXISTS kandang_cycles (
id SERIAL PRIMARY KEY,
kandang_id INTEGER NOT NULL,
cycle_id VARCHAR(255) NOT NULL,
doc_in_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (kandang_id) REFERENCES kandangs(id) ON DELETE CASCADE,
FOREIGN KEY (cycle_id) REFERENCES cycles(id) ON DELETE CASCADE,
UNIQUE(kandang_id, cycle_id)
);
-- 3. Add kandang_id column to mortality_records (nullable for backward compat)
ALTER TABLE mortality_records
ADD COLUMN IF NOT EXISTS kandang_id INTEGER REFERENCES kandangs(id) ON DELETE SET NULL;
-- 4. Add indexes
CREATE INDEX IF NOT EXISTS idx_mortality_kandang_id ON mortality_records(kandang_id);
CREATE INDEX IF NOT EXISTS idx_kandang_cycles_kandang ON kandang_cycles(kandang_id);
CREATE INDEX IF NOT EXISTS idx_kandang_cycles_cycle ON kandang_cycles(cycle_id);
-- 5. Add updated_at triggers
DROP TRIGGER IF EXISTS update_kandangs_updated_at ON kandangs;
CREATE TRIGGER update_kandangs_updated_at BEFORE UPDATE ON kandangs
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
DROP TRIGGER IF EXISTS update_kandang_cycles_updated_at ON kandang_cycles;
CREATE TRIGGER update_kandang_cycles_updated_at BEFORE UPDATE ON kandang_cycles
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
-- 6. Auto-assign existing data: Create default "Kandang 1" if not exists and assign all unassigned mortality records
-- Insert default kandang (only if no kandangs exist yet)
INSERT INTO kandangs (name)
SELECT 'Kandang 1'
WHERE NOT EXISTS (SELECT 1 FROM kandangs LIMIT 1);
-- Assign existing mortality records to the first kandang (only records with NULL kandang_id)
UPDATE mortality_records
SET kandang_id = (SELECT id FROM kandangs ORDER BY id LIMIT 1)
WHERE kandang_id IS NULL
AND EXISTS (SELECT 1 FROM kandangs LIMIT 1);
-- Create kandang_cycles entries for the default kandang for all cycles that have doc_in_count
INSERT INTO kandang_cycles (kandang_id, cycle_id, doc_in_count)
SELECT k.id, c.id, COALESCE(c.doc_in_count, 0)
FROM kandangs k, cycles c
WHERE k.id = (SELECT id FROM kandangs ORDER BY id LIMIT 1)
AND NOT EXISTS (
SELECT 1 FROM kandang_cycles kc
WHERE kc.kandang_id = k.id AND kc.cycle_id = c.id
);
-- 7. Drop old unique constraint and add new one that includes kandang_id
-- Note: The constraint name follows PostgreSQL naming convention
ALTER TABLE mortality_records DROP CONSTRAINT IF EXISTS mortality_records_cycle_id_day_key;
-- Create new unique index that handles kandang_id (using COALESCE for NULL safety)
DROP INDEX IF EXISTS idx_mortality_cycle_day_kandang;
CREATE UNIQUE INDEX idx_mortality_cycle_day_kandang
ON mortality_records(cycle_id, day, COALESCE(kandang_id, -1));
@@ -0,0 +1,22 @@
-- Migration: Simplify chicken counting to a single upsert table
-- Date: 2026-03-05
CREATE TABLE IF NOT EXISTS chicken_counting (
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
kandang_id INTEGER NOT NULL REFERENCES kandangs(id) ON DELETE CASCADE,
filename TEXT,
total_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
UNIQUE(date, kandang_id)
);
CREATE INDEX IF NOT EXISTS idx_chicken_counting_date ON chicken_counting(date);
CREATE INDEX IF NOT EXISTS idx_chicken_counting_kandang_id ON chicken_counting(kandang_id);
-- Auto-update updated_at trigger
CREATE TRIGGER update_chicken_counting_updated_at
BEFORE UPDATE ON chicken_counting
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
@@ -0,0 +1,12 @@
-- Insert API key for chicken counting endpoint
-- Key: cpa_e8cfeeabaa6997a1ecd4239cee6a9cc59cf43f96cfd58463
INSERT INTO api_keys (id, key_hash, name, description, is_active, rate_limit)
VALUES (
'apk_chicken_counting',
'7a98758e7e6e4dea9f69a06b98d1f56f9f9838dabb2e56c23214676d17a34cad',
'Chicken Counting Service',
'API key for chicken counting program to submit count data',
true,
1000
)
ON CONFLICT (id) DO NOTHING;
@@ -0,0 +1,12 @@
-- Migration: Add deplesi tracking columns
-- Date: 2026-04-19
-- Description: Adds panen and keterangan columns to mortality_records for comprehensive daily tracking
-- Add new columns (safe: idempotent, non-destructive)
ALTER TABLE mortality_records
ADD COLUMN IF NOT EXISTS panen INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS keterangan TEXT;
-- Optional: Add comment for documentation
COMMENT ON COLUMN mortality_records.panen IS 'Daily harvest count (chickens sold/removed)';
COMMENT ON COLUMN mortality_records.keterangan IS 'Daily notes or remarks about significant events';
@@ -0,0 +1,10 @@
-- Migration: Add afkir column
-- Date: 2026-04-19
-- Description: Adds afkir (culled chickens) column to mortality_records
-- Add new column (safe: idempotent, non-destructive)
ALTER TABLE mortality_records
ADD COLUMN IF NOT EXISTS afkir INTEGER DEFAULT 0;
-- Optional: Add comment for documentation
COMMENT ON COLUMN mortality_records.afkir IS 'Daily culled chickens count (removed due to poor quality)';
@@ -0,0 +1,29 @@
-- Migration: Add manual_feed_sack_entries table
-- Description: Store manual feed sack count entries separate from IOT camera data
-- Date: 2026-04-19
CREATE TABLE IF NOT EXISTS manual_feed_sack_entries (
id SERIAL PRIMARY KEY,
cycle_id VARCHAR(255) NOT NULL,
day INTEGER NOT NULL,
date DATE NOT NULL,
camera_name VARCHAR(255) NOT NULL,
count INTEGER NOT NULL CHECK(count >= 0),
created_by VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (cycle_id) REFERENCES cycles(id) ON DELETE CASCADE,
UNIQUE(cycle_id, day, camera_name)
);
-- Create indexes for faster lookups
CREATE INDEX IF NOT EXISTS idx_manual_feed_sack_cycle ON manual_feed_sack_entries(cycle_id);
CREATE INDEX IF NOT EXISTS idx_manual_feed_sack_day ON manual_feed_sack_entries(day);
CREATE INDEX IF NOT EXISTS idx_manual_feed_sack_camera ON manual_feed_sack_entries(camera_name);
-- Trigger for automatic updated_at timestamp
DROP TRIGGER IF EXISTS update_manual_feed_sack_entries_updated_at ON manual_feed_sack_entries;
CREATE TRIGGER update_manual_feed_sack_entries_updated_at
BEFORE UPDATE ON manual_feed_sack_entries
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
@@ -0,0 +1,32 @@
-- Migration: Update manual_feed_sack_entries to use date-based keys instead of cycle-based
-- Description: Remove cycle_id and day dependencies, use date + camera_name as unique key
-- Date: 2026-04-19
-- Drop old unique constraint
ALTER TABLE manual_feed_sack_entries DROP CONSTRAINT IF EXISTS manual_feed_sack_entries_cycle_id_day_camera_name_key;
-- Drop foreign key constraint
ALTER TABLE manual_feed_sack_entries DROP CONSTRAINT IF EXISTS manual_feed_sack_entries_cycle_id_fkey;
-- Drop cycle_id and day columns
ALTER TABLE manual_feed_sack_entries DROP COLUMN IF EXISTS cycle_id;
ALTER TABLE manual_feed_sack_entries DROP COLUMN IF EXISTS day;
-- Add new unique constraint on date + camera_name (only if it doesn't exist)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'manual_feed_sack_entries_date_camera_key'
) THEN
ALTER TABLE manual_feed_sack_entries
ADD CONSTRAINT manual_feed_sack_entries_date_camera_key UNIQUE (date, camera_name);
END IF;
END $$;
-- Drop old index on cycle_id
DROP INDEX IF EXISTS idx_manual_feed_sack_cycle;
DROP INDEX IF EXISTS idx_manual_feed_sack_day;
-- Add index on date for faster lookups
CREATE INDEX IF NOT EXISTS idx_manual_feed_sack_date ON manual_feed_sack_entries(date);
@@ -0,0 +1,23 @@
-- Migration: Add type field to feed_sack_column_config
-- Description: Add 'type' field to distinguish inbound vs usage columns
-- Date: 2026-05-08
-- Author: Claude Code
-- Update existing configs to add type field
-- This uses PostgreSQL's JSONB operators to modify each array element
UPDATE feed_sack_column_config
SET config = (
SELECT jsonb_agg(
CASE
-- Infer type from cameraName for backward compatibility
WHEN elem->>'cameraName' LIKE '%karung_masuk%'
THEN elem || '{"type": "inbound"}'
ELSE elem || '{"type": "usage"}'
END
)
FROM jsonb_array_elements(config) AS elem
)
WHERE config IS NOT NULL AND config != '[]'::jsonb;
-- Note: This migration is safe because it adds a new field with defaults
-- All existing column configs will automatically get 'type' based on camera name pattern
@@ -0,0 +1,20 @@
-- Migration: Add keterangan column to manual_feed_sack_entries
-- Description: Support per-camera notes/remarks for feed sack counting
-- Date: 2026-05-14
-- Add keterangan column (nullable for backward compatibility)
ALTER TABLE manual_feed_sack_entries
ADD COLUMN IF NOT EXISTS keterangan TEXT;
-- Add CHECK constraint for max 200 characters (only if not exists)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'keterangan_max_length'
AND conrelid = 'manual_feed_sack_entries'::regclass
) THEN
ALTER TABLE manual_feed_sack_entries
ADD CONSTRAINT keterangan_max_length CHECK (char_length(keterangan) <= 200);
END IF;
END $$;
@@ -0,0 +1,23 @@
-- Migration: Add cycle_feed_initial_balance table
-- Description: Store initial feed sack balance (saldo karung awal) per cycle
-- Date: 2026-05-26
CREATE TABLE IF NOT EXISTS cycle_feed_initial_balance (
id SERIAL PRIMARY KEY,
cycle_id VARCHAR(255) NOT NULL UNIQUE,
saldo_awal INTEGER NOT NULL DEFAULT 0 CHECK(saldo_awal >= 0),
created_by VARCHAR(255),
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (cycle_id) REFERENCES cycles(id) ON DELETE CASCADE
);
-- Create index for faster lookups by cycle
CREATE INDEX IF NOT EXISTS idx_cycle_feed_initial_balance_cycle ON cycle_feed_initial_balance(cycle_id);
-- Trigger for automatic updated_at timestamp
DROP TRIGGER IF EXISTS update_cycle_feed_initial_balance_updated_at ON cycle_feed_initial_balance;
CREATE TRIGGER update_cycle_feed_initial_balance_updated_at
BEFORE UPDATE ON cycle_feed_initial_balance
FOR EACH ROW
EXECUTE FUNCTION update_updated_at_column();
@@ -0,0 +1,19 @@
-- Create AI Insights table for caching AI-generated insights
CREATE TABLE IF NOT EXISTS ai_insights (
id SERIAL PRIMARY KEY,
cycle_id VARCHAR(255) NOT NULL,
kandang_id VARCHAR(255), -- NULL means "all kandangs"
insight_text TEXT NOT NULL,
version VARCHAR(50) NOT NULL DEFAULT 'v1',
generated_at TIMESTAMP NOT NULL DEFAULT NOW(),
expires_at TIMESTAMP NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
);
-- Create indexes for faster lookups
CREATE INDEX IF NOT EXISTS idx_ai_insights_cycle_kandang ON ai_insights (cycle_id, kandang_id);
CREATE INDEX IF NOT EXISTS idx_ai_insights_expires_at ON ai_insights (expires_at);
-- Add comment to table
COMMENT ON TABLE ai_insights IS 'Stores AI-generated insights with 6-hour TTL for cross-device caching';
@@ -0,0 +1,18 @@
-- Migration 012: Add cage_uuid to kandangs table
-- Add cage_uuid field for external scales API integration
-- Add cage_uuid column (nullable, unique)
ALTER TABLE kandangs
ADD COLUMN IF NOT EXISTS cage_uuid VARCHAR(36) UNIQUE;
-- Add comment explaining the field
COMMENT ON COLUMN kandangs.cage_uuid IS 'External cage UUID for scales API (dashboard.cpsp.id/api/scales/cage/)';
-- Seed initial data for existing kandangs
-- Kandang Atas gets the existing hardcoded UUID
UPDATE kandangs
SET cage_uuid = '9f556d2a-af5c-41b0-bcd0-6cea0dfa0fed'
WHERE LOWER(name) LIKE '%atas%';
-- Kandang Bawah and others remain null (to be configured via settings UI)
-- This is intentional - admins will configure via IoT Panel settings
@@ -0,0 +1,8 @@
-- Migration: Add harvest weight tracking to mortality records
-- Date: 2026-06-19
-- Description: Adds berat_panen to mortality_records so harvest weight persists across reloads
ALTER TABLE mortality_records
ADD COLUMN IF NOT EXISTS berat_panen NUMERIC(10,2) DEFAULT 0;
COMMENT ON COLUMN mortality_records.berat_panen IS 'Daily harvest weight in kilograms';