운영 지표와 지원 요청 저장소 추가

This commit is contained in:
Yun Chan 2026-06-28 20:13:06 +09:00
parent 50fa4ad432
commit e7ebb38177
20 changed files with 3038 additions and 39 deletions

View file

@ -92,6 +92,28 @@ CREATE TABLE IF NOT EXISTS app.admin_health_event (
CREATE INDEX IF NOT EXISTS idx_admin_health_event_observed
ON app.admin_health_event(observed_at DESC, service_key);
CREATE TABLE IF NOT EXISTS app.admin_health_daily_rollup (
rollup_date DATE NOT NULL,
environment TEXT NOT NULL,
engine_mode TEXT NOT NULL,
service_key TEXT NOT NULL,
service_name TEXT NOT NULL,
sample_count INTEGER NOT NULL DEFAULT 0 CHECK (sample_count >= 0),
ok_samples INTEGER NOT NULL DEFAULT 0 CHECK (ok_samples >= 0),
degraded_samples INTEGER NOT NULL DEFAULT 0 CHECK (degraded_samples >= 0),
down_samples INTEGER NOT NULL DEFAULT 0 CHECK (down_samples >= 0),
first_observed_at TIMESTAMPTZ NOT NULL,
last_observed_at TIMESTAMPTZ NOT NULL,
latest_status TEXT NOT NULL CHECK (latest_status IN ('ok','degraded','down')),
last_down_at TIMESTAMPTZ,
max_load REAL NOT NULL DEFAULT 0.0 CHECK (max_load >= 0.0 AND max_load <= 1.0),
generated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
PRIMARY KEY (rollup_date, environment, engine_mode, service_key)
);
CREATE INDEX IF NOT EXISTS idx_admin_health_daily_rollup_latest
ON app.admin_health_daily_rollup(last_observed_at DESC, service_key);
CREATE TABLE IF NOT EXISTS app.support_ticket (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
reporter_id UUID REFERENCES app.app_user(user_id) ON DELETE SET NULL,
@ -115,6 +137,8 @@ CREATE TABLE IF NOT EXISTS app.support_ticket (
subject TEXT NOT NULL,
body TEXT NOT NULL,
source_path TEXT NOT NULL DEFAULT '',
fingerprint TEXT NOT NULL DEFAULT '',
parent_ticket_id UUID REFERENCES app.support_ticket(id) ON DELETE SET NULL,
assigned_group TEXT NOT NULL DEFAULT '',
resolution_note TEXT NOT NULL DEFAULT '',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
@ -129,15 +153,68 @@ CREATE INDEX IF NOT EXISTS idx_support_ticket_status_priority
CREATE INDEX IF NOT EXISTS idx_support_ticket_reporter
ON app.support_ticket(reporter_id, created_at DESC);
CREATE INDEX IF NOT EXISTS idx_support_ticket_fingerprint
ON app.support_ticket(fingerprint, created_at DESC)
WHERE fingerprint <> '';
CREATE INDEX IF NOT EXISTS idx_support_ticket_parent
ON app.support_ticket(parent_ticket_id)
WHERE parent_ticket_id IS NOT NULL;
CREATE TABLE IF NOT EXISTS app.learner_prepost_measure (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
learner_id UUID NOT NULL REFERENCES app.app_user(user_id) ON DELETE CASCADE,
pilot_id TEXT NOT NULL DEFAULT 'phase3-pilot-draft',
measure_name TEXT NOT NULL CHECK (
measure_name IN ('self_efficacy','skill_proficiency','training_satisfaction')
),
timepoint TEXT NOT NULL CHECK (timepoint IN ('pre','post')),
raw_score NUMERIC(8,3) NOT NULL,
min_score NUMERIC(8,3) NOT NULL DEFAULT 1.0,
max_score NUMERIC(8,3) NOT NULL DEFAULT 5.0,
instrument_version TEXT NOT NULL DEFAULT 'pilot-prepost-scaffold-2026-06-28',
item_count INTEGER NOT NULL DEFAULT 1 CHECK (item_count >= 1 AND item_count <= 80),
collected_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
CHECK (max_score > min_score),
CHECK (raw_score >= min_score AND raw_score <= max_score),
UNIQUE (learner_id, pilot_id, measure_name, timepoint, instrument_version)
);
CREATE INDEX IF NOT EXISTS idx_learner_prepost_measure_pilot
ON app.learner_prepost_measure(pilot_id, measure_name, timepoint);
CREATE INDEX IF NOT EXISTS idx_learner_prepost_measure_learner
ON app.learner_prepost_measure(learner_id, pilot_id);
ALTER TABLE app.admin_health_event ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_admin_health_event_select ON app.admin_health_event;
DROP POLICY IF EXISTS p_admin_health_event_insert ON app.admin_health_event;
DROP POLICY IF EXISTS p_admin_health_event_delete ON app.admin_health_event;
CREATE POLICY p_admin_health_event_select
ON app.admin_health_event FOR SELECT
USING (app.current_role_name() = 'admin');
CREATE POLICY p_admin_health_event_insert
ON app.admin_health_event FOR INSERT
WITH CHECK (app.current_role_name() = 'admin');
CREATE POLICY p_admin_health_event_delete
ON app.admin_health_event FOR DELETE
USING (app.current_role_name() = 'admin');
ALTER TABLE app.admin_health_daily_rollup ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_admin_health_daily_rollup_select ON app.admin_health_daily_rollup;
DROP POLICY IF EXISTS p_admin_health_daily_rollup_insert ON app.admin_health_daily_rollup;
DROP POLICY IF EXISTS p_admin_health_daily_rollup_update ON app.admin_health_daily_rollup;
CREATE POLICY p_admin_health_daily_rollup_select
ON app.admin_health_daily_rollup FOR SELECT
USING (app.current_role_name() = 'admin');
CREATE POLICY p_admin_health_daily_rollup_insert
ON app.admin_health_daily_rollup FOR INSERT
WITH CHECK (app.current_role_name() = 'admin');
CREATE POLICY p_admin_health_daily_rollup_update
ON app.admin_health_daily_rollup FOR UPDATE
USING (app.current_role_name() = 'admin')
WITH CHECK (app.current_role_name() = 'admin');
ALTER TABLE app.support_ticket ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_support_ticket_select ON app.support_ticket;
@ -164,6 +241,41 @@ CREATE POLICY p_support_ticket_delete
ON app.support_ticket FOR DELETE
USING (app.current_role_name() = 'admin');
ALTER TABLE app.learner_prepost_measure ENABLE ROW LEVEL SECURITY;
DROP POLICY IF EXISTS p_learner_prepost_measure_select ON app.learner_prepost_measure;
DROP POLICY IF EXISTS p_learner_prepost_measure_insert ON app.learner_prepost_measure;
DROP POLICY IF EXISTS p_learner_prepost_measure_update ON app.learner_prepost_measure;
CREATE POLICY p_learner_prepost_measure_select
ON app.learner_prepost_measure FOR SELECT
USING (
app.current_role_name() = 'admin'
OR learner_id = app.current_uid()
OR (
app.current_role_name() = 'instructor'
AND EXISTS (
SELECT 1 FROM app.app_user u
WHERE u.user_id = app.learner_prepost_measure.learner_id
AND u.cohort = current_setting('app.current_cohort', true)
)
)
);
CREATE POLICY p_learner_prepost_measure_insert
ON app.learner_prepost_measure FOR INSERT
WITH CHECK (
app.current_role_name() = 'admin'
OR learner_id = app.current_uid()
);
CREATE POLICY p_learner_prepost_measure_update
ON app.learner_prepost_measure FOR UPDATE
USING (
app.current_role_name() = 'admin'
OR learner_id = app.current_uid()
)
WITH CHECK (
app.current_role_name() = 'admin'
OR learner_id = app.current_uid()
);
ALTER TABLE app.sessions
ADD COLUMN IF NOT EXISTS runtime_case_id UUID,
ADD COLUMN IF NOT EXISTS persona_code TEXT,