sciagent code + Gitea Actions CI/CD
CI/CD / backend (push) Failing after 2m8s
CI/CD / frontend (push) Failing after 1m40s
CI/CD / deploy (push) Has been skipped

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Thinh Lam
2026-06-30 09:38:30 +07:00
commit 688fac73e9
1167 changed files with 158244 additions and 0 deletions
+251
View File
@@ -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);