sciagent code + Gitea Actions CI/CD
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,251 @@
|
||||
-- Initiative Recognition System — PostgreSQL schema (architecture_plan.md §4)
|
||||
-- Table order respects FKs (units before users).
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS citext;
|
||||
|
||||
-- ========= ENUMS =========
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE user_role AS ENUM ('applicant','council_member','editor','admin','viewer');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE initiative_class AS ENUM ('technical','research','textbook');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE research_evidence AS ENUM ('international','domestic','poster');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE eval_level AS ENUM ('high','medium','low');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE submission_status AS ENUM ('draft','submitted','under_review','approved','rejected');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE recognition_tier AS ENUM ('excellent','good');
|
||||
EXCEPTION WHEN duplicate_object THEN NULL;
|
||||
END $$;
|
||||
|
||||
-- ========= IDENTITY =========
|
||||
CREATE TABLE IF NOT EXISTS units (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL,
|
||||
parent_id UUID REFERENCES units(id),
|
||||
address TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
email CITEXT UNIQUE NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
full_name TEXT NOT NULL,
|
||||
phone TEXT,
|
||||
unit_id UUID REFERENCES units(id),
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS user_roles (
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
role user_role NOT NULL,
|
||||
PRIMARY KEY (user_id, role)
|
||||
);
|
||||
|
||||
-- System user for anonymous draft saves (no login yet)
|
||||
INSERT INTO users (id, email, password_hash, full_name)
|
||||
VALUES (
|
||||
'00000000-0000-4000-8000-000000000001',
|
||||
'system@draft.local',
|
||||
'-',
|
||||
'System (draft owner)'
|
||||
)
|
||||
ON CONFLICT (email) DO NOTHING;
|
||||
|
||||
-- ========= CASE / INITIATIVE ROOT =========
|
||||
CREATE TABLE IF NOT EXISTS initiatives (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
case_code TEXT UNIQUE NOT NULL,
|
||||
owner_id UUID NOT NULL REFERENCES users(id),
|
||||
status submission_status NOT NULL DEFAULT 'draft',
|
||||
recognition_tier recognition_tier,
|
||||
submitted_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_initiatives_owner_status ON initiatives(owner_id, status);
|
||||
|
||||
-- ========= DRAFT SNAPSHOTS =========
|
||||
CREATE TABLE IF NOT EXISTS drafts (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
draft_code TEXT UNIQUE NOT NULL,
|
||||
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
payload JSONB NOT NULL,
|
||||
version INTEGER NOT NULL DEFAULT 1,
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_drafts_initiative ON drafts(initiative_id);
|
||||
|
||||
-- ========= ĐƠN (APPLICATION) =========
|
||||
CREATE TABLE IF NOT EXISTS applications (
|
||||
initiative_id UUID PRIMARY KEY REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
initiative_name TEXT NOT NULL,
|
||||
investor_name TEXT,
|
||||
application_field TEXT,
|
||||
first_apply_date DATE,
|
||||
initiative_classification initiative_class,
|
||||
research_evidence_kind research_evidence,
|
||||
international_journal_decl TEXT,
|
||||
content_summary TEXT,
|
||||
confidential_info TEXT,
|
||||
conditions TEXT,
|
||||
author_evaluation TEXT,
|
||||
trial_evaluation TEXT,
|
||||
submission_day SMALLINT,
|
||||
submission_month SMALLINT,
|
||||
submission_year SMALLINT,
|
||||
honesty_confirmed BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
CONSTRAINT chk_first_apply_window
|
||||
CHECK (first_apply_date IS NULL
|
||||
OR first_apply_date BETWEEN DATE '2025-04-15' AND DATE '2026-04-15')
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS authors (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
user_id UUID REFERENCES users(id),
|
||||
ordinal SMALLINT NOT NULL,
|
||||
full_name TEXT NOT NULL,
|
||||
dob DATE,
|
||||
workplace TEXT,
|
||||
title TEXT,
|
||||
qualification TEXT,
|
||||
contribution_percent NUMERIC(5,2) NOT NULL,
|
||||
is_representative BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
CHECK (contribution_percent >= 0 AND contribution_percent <= 100)
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_authors_repr ON authors(initiative_id) WHERE is_representative;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS support_staff (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
full_name TEXT,
|
||||
dob DATE,
|
||||
workplace TEXT,
|
||||
title TEXT,
|
||||
qualification TEXT,
|
||||
support_content TEXT
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS evidence_files (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
kind TEXT NOT NULL CHECK (kind IN ('textbook','research','technical')),
|
||||
storage_uri TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
mime_type TEXT NOT NULL DEFAULT 'application/pdf',
|
||||
byte_size BIGINT NOT NULL,
|
||||
sha256 CHAR(64) NOT NULL,
|
||||
uploaded_by UUID NOT NULL REFERENCES users(id),
|
||||
uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS uq_evidence_kind ON evidence_files(initiative_id, kind);
|
||||
|
||||
-- ========= BÁO CÁO (REPORT) =========
|
||||
CREATE TABLE IF NOT EXISTS reports (
|
||||
initiative_id UUID PRIMARY KEY REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
introduction TEXT,
|
||||
representative_phone TEXT,
|
||||
representative_email TEXT,
|
||||
current_status TEXT,
|
||||
purpose TEXT,
|
||||
implementation_steps TEXT,
|
||||
first_applied_unit TEXT,
|
||||
achieved_result TEXT,
|
||||
novelty TEXT,
|
||||
effectiveness JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||
submission_date DATE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS trial_units (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
field TEXT,
|
||||
ordinal SMALLINT
|
||||
);
|
||||
|
||||
-- ========= CONTRIBUTION CONFIRMATION =========
|
||||
CREATE TABLE IF NOT EXISTS contributions (
|
||||
initiative_id UUID PRIMARY KEY REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
main_author TEXT NOT NULL,
|
||||
position TEXT,
|
||||
representative_percent NUMERIC(5,2),
|
||||
submission_date TIMESTAMPTZ,
|
||||
digital_signature_confirmed BOOLEAN NOT NULL DEFAULT FALSE
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS contribution_participants (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
full_name TEXT,
|
||||
work_unit TEXT,
|
||||
contribution_percent NUMERIC(5,2)
|
||||
);
|
||||
|
||||
-- ========= PHIẾU ĐÁNH GIÁ =========
|
||||
CREATE TABLE IF NOT EXISTS evaluations (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
council_member_id UUID NOT NULL REFERENCES users(id),
|
||||
position TEXT,
|
||||
evaluation_date DATE NOT NULL,
|
||||
novelty_level eval_level,
|
||||
novelty_score SMALLINT,
|
||||
novelty_comment TEXT,
|
||||
effectiveness_level eval_level,
|
||||
effectiveness_score SMALLINT,
|
||||
effectiveness_comment TEXT,
|
||||
total_score SMALLINT GENERATED ALWAYS AS
|
||||
(COALESCE(novelty_score,0) + COALESCE(effectiveness_score,0)) STORED,
|
||||
conclusion TEXT,
|
||||
status submission_status NOT NULL DEFAULT 'draft',
|
||||
submitted_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CHECK (novelty_score IS NULL OR (novelty_score BETWEEN 0 AND 40)),
|
||||
CHECK (effectiveness_score IS NULL OR (effectiveness_score BETWEEN 0 AND 60)),
|
||||
UNIQUE (initiative_id, council_member_id)
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_eval_initiative ON evaluations(initiative_id);
|
||||
|
||||
-- ========= ADMIN VERIFY =========
|
||||
CREATE TABLE IF NOT EXISTS verifications (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
initiative_id UUID NOT NULL REFERENCES initiatives(id) ON DELETE CASCADE,
|
||||
field_name TEXT NOT NULL,
|
||||
content_hash CHAR(64) NOT NULL,
|
||||
verified_by UUID NOT NULL REFERENCES users(id),
|
||||
verified_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
result TEXT
|
||||
);
|
||||
|
||||
-- ========= AUDIT TRAIL =========
|
||||
CREATE TABLE IF NOT EXISTS audit_log (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
actor_id UUID REFERENCES users(id),
|
||||
action TEXT NOT NULL,
|
||||
entity TEXT NOT NULL,
|
||||
entity_id UUID NOT NULL,
|
||||
diff JSONB,
|
||||
occurred_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_audit_entity ON audit_log(entity, entity_id);
|
||||
Reference in New Issue
Block a user