-- PostgreSQL-oriented schema for Rolemaster critical tables. -- It is intentionally hybrid: relational axes + raw text + parsed JSON. create table critical_table ( id bigint generated always as identity primary key, slug text not null unique, display_name text not null, family text not null check (family in ('standard', 'variant_column', 'grouped_variant')), source_pdf text not null, source_page integer not null default 1, extraction_method text not null check (extraction_method in ('text', 'ocr', 'manual')), notes text, created_at timestamptz not null default now() ); create table critical_group ( id bigint generated always as identity primary key, critical_table_id bigint not null references critical_table(id) on delete cascade, group_key text not null, label text not null, sort_order integer not null, unique (critical_table_id, group_key) ); create table critical_column ( id bigint generated always as identity primary key, critical_table_id bigint not null references critical_table(id) on delete cascade, column_key text not null, label text not null, role text not null default 'severity' check (role in ('severity', 'variant', 'other')), sort_order integer not null, unique (critical_table_id, column_key) ); create table critical_roll_band ( id bigint generated always as identity primary key, critical_table_id bigint not null references critical_table(id) on delete cascade, label text not null, min_roll integer not null, max_roll integer, sort_order integer not null, check (max_roll is null or max_roll >= min_roll), unique (critical_table_id, label) ); create index critical_roll_band_lookup_idx on critical_roll_band (critical_table_id, min_roll, max_roll); create table critical_result ( id bigint generated always as identity primary key, critical_table_id bigint not null references critical_table(id) on delete cascade, critical_group_id bigint references critical_group(id) on delete cascade, critical_column_id bigint not null references critical_column(id) on delete cascade, critical_roll_band_id bigint not null references critical_roll_band(id) on delete cascade, raw_cell_text text not null, description_text text, raw_affix_text text, parsed_json jsonb not null default '{}'::jsonb, parse_status text not null default 'raw' check (parse_status in ('raw', 'partial', 'parsed', 'verified')), source_bbox jsonb, created_at timestamptz not null default now() ); create unique index critical_result_lookup_uidx on critical_result ( critical_table_id, coalesce(critical_group_id, 0), critical_column_id, critical_roll_band_id ); create table critical_branch ( id bigint generated always as identity primary key, critical_result_id bigint not null references critical_result(id) on delete cascade, branch_kind text not null default 'conditional' check (branch_kind in ('conditional', 'note', 'override')), condition_key text, condition_text text not null, condition_json jsonb not null default '{}'::jsonb, raw_text text not null, description_text text, raw_affix_text text, parsed_json jsonb not null default '{}'::jsonb, sort_order integer not null default 1 ); create table critical_effect ( id bigint generated always as identity primary key, critical_result_id bigint references critical_result(id) on delete cascade, critical_branch_id bigint references critical_branch(id) on delete cascade, effect_code text not null, target text, value_integer integer, value_decimal numeric(10, 2), duration_rounds integer, per_round integer, modifier integer, body_part text, is_permanent boolean not null default false, source_type text not null default 'symbol' check (source_type in ('symbol', 'prose', 'manual')), source_text text, check ((critical_result_id is not null) <> (critical_branch_id is not null)) ); create index critical_effect_lookup_idx on critical_effect (effect_code, target); create index critical_effect_result_idx on critical_effect (critical_result_id); create index critical_effect_branch_idx on critical_effect (critical_branch_id); create index critical_result_parsed_json_gin on critical_result using gin (parsed_json); create index critical_branch_parsed_json_gin on critical_branch using gin (parsed_json); -- Example lookup pattern: -- -- select -- t.slug as critical_type, -- t.display_name as table_name, -- g.group_key, -- c.column_key, -- rb.label as roll_band, -- rb.min_roll, -- rb.max_roll, -- r.description_text, -- r.raw_affix_text, -- r.raw_cell_text, -- r.parsed_json -- from critical_result r -- join critical_table t on t.id = r.critical_table_id -- left join critical_group g on g.id = r.critical_group_id -- join critical_column c on c.id = r.critical_column_id -- join critical_roll_band rb on rb.id = r.critical_roll_band_id -- where t.slug = 'slash' -- and c.column_key = 'C' -- and 38 >= rb.min_roll -- and (rb.max_roll is null or 38 <= rb.max_roll);