CREATE TABLE IF NOT EXISTS business_parser_settings ( organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, route_id text NOT NULL, mode text NOT NULL DEFAULT 'ai', revision bigint NOT NULL DEFAULT 1, updated_by uuid REFERENCES users(id) ON DELETE SET NULL, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), PRIMARY KEY (organization_id, route_id), CONSTRAINT business_parser_settings_route_check CHECK (route_id IN ( 'team_order_create', 'team_order_batch_create', 'shared_plan_create', 'shared_child_order_create', 'passenger_list_import_independent', 'passenger_list_import_shared_child', 'arrangement_guide_create', 'arrangement_vehicle_create', 'arrangement_hotel_create', 'arrangement_transport_create', 'arrangement_other_create', 'order_update_shared_plan', 'order_update_shared_child', 'order_update_independent', 'arrangement_hotel_update', 'order_cancel', 'order_restore', 'confirmation_export' )), CONSTRAINT business_parser_settings_mode_check CHECK (mode IN ('ai', 'shadow', 'auto', 'program')), CONSTRAINT business_parser_settings_revision_check CHECK (revision >= 1) ); CREATE INDEX IF NOT EXISTS business_parser_settings_updated_idx ON business_parser_settings (organization_id, updated_at DESC); ALTER TABLE tasks ADD COLUMN IF NOT EXISTS business_route_id text, ADD COLUMN IF NOT EXISTS input_contract_version text, ADD COLUMN IF NOT EXISTS parser_mode text NOT NULL DEFAULT 'ai' CHECK (parser_mode IN ('ai', 'shadow', 'auto', 'program')), ADD COLUMN IF NOT EXISTS parser_config_revision bigint NOT NULL DEFAULT 0 CHECK (parser_config_revision >= 0), ADD COLUMN IF NOT EXISTS parser_engine_affinity text CHECK (parser_engine_affinity IN ('ai', 'program')), ADD COLUMN IF NOT EXISTS parser_engine text CHECK (parser_engine IN ('ai', 'program')), ADD COLUMN IF NOT EXISTS parser_version text, ADD COLUMN IF NOT EXISTS parser_fallback_reason text, ADD COLUMN IF NOT EXISTS parser_comparison_status text CHECK (parser_comparison_status IN ('equivalent', 'different', 'not_comparable')), ADD COLUMN IF NOT EXISTS parser_reparse_count integer NOT NULL DEFAULT 0 CHECK (parser_reparse_count BETWEEN 0 AND 1); UPDATE tasks SET parser_mode = 'ai', parser_config_revision = 0, parser_engine_affinity = 'ai', parser_engine = COALESCE(parser_engine, CASE WHEN parse_response IS NOT NULL OR parse_response_ciphertext IS NOT NULL THEN 'ai' END), parser_version = COALESCE(parser_version, CASE WHEN parse_response IS NOT NULL OR parse_response_ciphertext IS NOT NULL THEN 'legacy-ai' END); CREATE INDEX IF NOT EXISTS tasks_parser_route_idx ON tasks (organization_id, business_route_id, parser_mode, created_at DESC); CREATE TABLE IF NOT EXISTS parse_decisions ( id uuid PRIMARY KEY DEFAULT gen_random_uuid(), organization_id uuid NOT NULL REFERENCES organizations(id) ON DELETE CASCADE, task_id uuid NOT NULL REFERENCES tasks(id) ON DELETE CASCADE, attempt_no integer NOT NULL, route_id text, configured_mode text NOT NULL, authoritative_engine text NOT NULL, program_version text, ai_prompt_version text, fallback_reason text, comparison_status text, program_status text, ai_status text, program_error_code text, ai_error_code text, program_duration_ms integer, ai_duration_ms integer, program_result_hash text, ai_result_hash text, program_result_ciphertext text, ai_result_ciphertext text, diff_summary jsonb NOT NULL DEFAULT '{}'::jsonb, review_status text, reviewed_by uuid REFERENCES users(id) ON DELETE SET NULL, reviewed_at timestamptz, review_note_ciphertext text, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT parse_decisions_attempt_check CHECK (attempt_no >= 1), CONSTRAINT parse_decisions_mode_check CHECK (configured_mode IN ('ai', 'shadow', 'auto', 'program')), CONSTRAINT parse_decisions_engine_check CHECK (authoritative_engine IN ('ai', 'program')), CONSTRAINT parse_decisions_comparison_check CHECK (comparison_status IS NULL OR comparison_status IN ('equivalent', 'different', 'not_comparable')), CONSTRAINT parse_decisions_review_check CHECK (review_status IS NULL OR review_status IN ('pending', 'equivalent', 'program_correct', 'ai_correct', 'both_wrong')), CONSTRAINT parse_decisions_duration_check CHECK ( (program_duration_ms IS NULL OR program_duration_ms >= 0) AND (ai_duration_ms IS NULL OR ai_duration_ms >= 0) ), UNIQUE (task_id, attempt_no) ); CREATE INDEX IF NOT EXISTS parse_decisions_route_stats_idx ON parse_decisions (organization_id, route_id, created_at DESC); CREATE INDEX IF NOT EXISTS parse_decisions_review_idx ON parse_decisions (organization_id, review_status, created_at DESC) WHERE review_status IS NOT NULL;