-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathschema.sql
More file actions
445 lines (369 loc) · 16.1 KB
/
Copy pathschema.sql
File metadata and controls
445 lines (369 loc) · 16.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
-- ============================================================
-- INSURANCE ANALYTICS — BI CONTRACT V1 (FROZEN)
-- PostgreSQL / Supabase
-- ============================================================
-- Ensure the schema exists; default schema is 'public'
CREATE SCHEMA IF NOT EXISTS public;
-- Grant access to the schema for the default roles
GRANT USAGE ON SCHEMA public TO anon, authenticated, service_role;
-- ============================================================
-- DIMENSIONS — Core tables for dimensional modeling
-- ============================================================
-- Time dimension table (dim_time)
-- Contains temporal details for all fact records
CREATE TABLE IF NOT EXISTS public.dim_time (
date_key INT PRIMARY KEY, -- Date key (YYYYMMDD) for easy reference
full_date DATE, -- Full date in standard format
year INT, -- Year (e.g., 2025)
month INT, -- Month (1 to 12)
month_name TEXT, -- Month name (e.g., January)
quarter INT, -- Quarter (1 to 4)
year_month TEXT, -- Concatenated year-month (e.g., "2025-01")
day_of_week INT, -- Day of the week (1 to 7)
is_weekend BOOLEAN -- Flag indicating if the day is a weekend
);
-- State dimension table (dim_state)
-- Contains state-related information
CREATE TABLE IF NOT EXISTS public.dim_state (
state_code TEXT PRIMARY KEY, -- State code (e.g., 'NY', 'CA')
region_code TEXT, -- Region code (e.g., 'NE', 'MW')
market_tier TEXT -- Market tier classification (e.g., 'Tier 1')
);
-- Clients dimension table (dim_clients)
-- Contains information about clients
CREATE TABLE IF NOT EXISTS public.dim_clients (
client_id TEXT PRIMARY KEY, -- Unique client ID
registration_year INT, -- Year the client registered
age INT, -- Age of the client
gender TEXT, -- Gender of the client
customer_segment TEXT, -- Customer segment (e.g., 'Individual', 'Corporate')
state_code TEXT REFERENCES public.dim_state(state_code), -- Foreign key to dim_state
region_code TEXT, -- Region code (linked with state)
market_tier TEXT, -- Market tier (linked with state)
max_policies_allowed INT -- Maximum number of policies allowed for this client
);
-- Products dimension table (dim_products)
-- Contains information about product offerings
CREATE TABLE IF NOT EXISTS public.dim_products (
product_key TEXT PRIMARY KEY, -- Unique product key
line_of_business TEXT, -- Line of business (e.g., 'Life', 'Health', 'Auto')
plan_name TEXT -- Plan name (e.g., 'Basic', 'Standard', 'Premium')
);
-- Policies dimension table (dim_policies)
-- Contains policy-level information
CREATE TABLE IF NOT EXISTS public.dim_policies (
policy_id TEXT PRIMARY KEY, -- Unique policy ID
policy_number TEXT, -- Policy number
client_id TEXT REFERENCES public.dim_clients(client_id), -- Foreign key to clients
state_code TEXT REFERENCES public.dim_state(state_code), -- Foreign key to state
region_code TEXT, -- Region code (linked with state)
is_renewal BOOLEAN -- Flag to indicate if the policy is a renewal
);
-- ============================================================
-- FACT TABLES — Core transactional data for BI
-- ============================================================
-- Fact table for policies (fact_policies)
CREATE TABLE IF NOT EXISTS public.fact_policies (
policy_id TEXT PRIMARY KEY REFERENCES public.dim_policies(policy_id), -- Foreign key to dim_policies
product_key TEXT REFERENCES public.dim_products(product_key), -- Foreign key to dim_products
state_code TEXT REFERENCES public.dim_state(state_code), -- Foreign key to dim_state
region_code TEXT, -- Region code (linked with state)
effective_date_key INT REFERENCES public.dim_time(date_key), -- Foreign key to dim_time (effective date)
expiration_date_key INT, -- Expiration date (nullable, no foreign key constraint)
policy_year INT, -- Policy year
policy_month INT, -- Policy month
status TEXT, -- Policy status (e.g., 'Active', 'Expired')
risk_score NUMERIC, -- Risk score (numerical value)
monthly_premium NUMERIC, -- Monthly premium amount
annual_premium NUMERIC -- Annual premium amount
);
-- Fact table for claims (fact_claims)
CREATE TABLE IF NOT EXISTS public.fact_claims (
claim_id TEXT PRIMARY KEY, -- Unique claim ID
policy_id TEXT REFERENCES public.dim_policies(policy_id), -- Foreign key to dim_policies
product_key TEXT REFERENCES public.dim_products(product_key), -- Foreign key to dim_products
line_of_business TEXT, -- Line of business (e.g., 'Life')
state_code TEXT REFERENCES public.dim_state(state_code), -- Foreign key to dim_state
region_code TEXT, -- Region code
claim_type TEXT, -- Type of claim (e.g., 'Death', 'Theft')
claim_status TEXT, -- Claim status (e.g., 'Paid', 'Pending')
fraud_flag BOOLEAN, -- Fraud detection flag
incident_date_key INT REFERENCES public.dim_time(date_key), -- Foreign key to dim_time (incident date)
report_date_key INT REFERENCES public.dim_time(date_key), -- Foreign key to dim_time (report date)
settlement_date_key INT, -- Settlement date (nullable)
days_to_settle INT, -- Number of days to settle the claim
claim_amount_requested NUMERIC, -- Requested claim amount
claim_amount_approved NUMERIC, -- Approved claim amount
claim_amount_paid NUMERIC -- Paid claim amount
);
-- Fact table for expenses (fact_expenses)
CREATE TABLE IF NOT EXISTS public.fact_expenses (
expense_id TEXT PRIMARY KEY, -- Unique expense ID
expense_category TEXT, -- Category of the expense (e.g., 'Operating')
state_code TEXT REFERENCES public.dim_state(state_code), -- Foreign key to dim_state
region_code TEXT, -- Region code
date_key INT REFERENCES public.dim_time(date_key), -- Foreign key to dim_time (month/year)
expense_amount NUMERIC -- Amount of the expense
);
-- Fact table for taxes (fact_taxes)
CREATE TABLE IF NOT EXISTS public.fact_taxes (
tax_id TEXT PRIMARY KEY, -- Unique tax ID
tax_type TEXT, -- Type of tax (e.g., 'State Tax')
state_code TEXT REFERENCES public.dim_state(state_code), -- Foreign key to dim_state
date_key INT REFERENCES public.dim_time(date_key), -- Foreign key to dim_time (tax date)
tax_base NUMERIC, -- Tax base value
tax_rate NUMERIC, -- Tax rate applied
tax_amount NUMERIC, -- Total tax amount
policy_id TEXT
);
-- ============================================================
-- INDEXES — For faster querying and BI performance
-- ============================================================
-- Create indexes for important fact tables
CREATE INDEX IF NOT EXISTS idx_fp_eff_exp
ON public.fact_policies (effective_date_key, expiration_date_key);
CREATE INDEX IF NOT EXISTS idx_fc_incident
ON public.fact_claims (incident_date_key);
CREATE INDEX IF NOT EXISTS idx_fc_policy
ON public.fact_claims (policy_id);
-- ============================================================
-- ROW LEVEL SECURITY (RLS) — Enable RLS for secure access
-- ============================================================
-- Enable Row Level Security on all tables to enforce access policies
ALTER TABLE public.dim_time ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.dim_state ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.dim_clients ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.dim_products ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.dim_policies ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.fact_policies ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.fact_claims ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.fact_expenses ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.fact_taxes ENABLE ROW LEVEL SECURITY;
-- VIEWS
-- ============================================================
-- DASHBOARD 1 — EXECUTIVE PORTFOLIO (MONTHLY SNAPSHOT)
-- ONE VIEW = ONE DASHBOARD
-- ============================================================
CREATE OR REPLACE VIEW public.vw_dash_exec_portfolio AS
SELECT
-- Canonical date for BI tools
TO_DATE(t.year_month || '-01', 'YYYY-MM-DD') AS month_date,
t.year,
t.month,
t.year_month,
-- Portfolio size
COUNT(DISTINCT p.policy_id) AS active_policies,
-- Premium exposure
SUM(p.annual_premium) AS total_annual_premium,
SUM(p.monthly_premium) AS total_monthly_premium,
-- Optional breakdown helpers
p.state_code,
p.region_code,
pr.line_of_business
FROM public.fact_policies p
JOIN public.dim_time t
ON t.date_key BETWEEN p.effective_date_key
AND COALESCE(p.expiration_date_key, p.effective_date_key)
LEFT JOIN public.dim_products pr
ON pr.product_key = p.product_key
GROUP BY
month_date,
t.year,
t.month,
t.year_month,
p.state_code,
p.region_code,
pr.line_of_business;
-- ============================================================
-- DASHBOARD 2 — CLAIMS & LOSS (MONTHLY PERFORMANCE)
-- ONE VIEW = ONE DASHBOARD
-- ============================================================
CREATE OR REPLACE VIEW public.vw_dash_claims_loss AS
SELECT
-- Canonical monthly date
TO_DATE(t.year_month || '-01', 'YYYY-MM-DD') AS month_date,
t.year,
t.month,
t.year_month,
-- Dimensions
p.product_key,
pr.line_of_business,
p.state_code,
p.region_code,
-- Exposure
COUNT(DISTINCT p.policy_id) AS exposed_policies,
-- Claims volume
COUNT(DISTINCT c.claim_id) AS claim_count,
-- Financials
SUM(c.claim_amount_paid) AS total_losses,
SUM(p.annual_premium) AS total_premium,
-- Frequency & severity
CASE
WHEN COUNT(DISTINCT p.policy_id) > 0
THEN COUNT(DISTINCT c.claim_id)::NUMERIC
/ COUNT(DISTINCT p.policy_id)
ELSE NULL
END AS claim_frequency,
CASE
WHEN COUNT(DISTINCT c.claim_id) > 0
THEN SUM(c.claim_amount_paid)
/ COUNT(DISTINCT c.claim_id)
ELSE NULL
END AS claim_severity,
-- Loss Ratio
CASE
WHEN SUM(p.annual_premium) > 0
THEN SUM(c.claim_amount_paid)
/ SUM(p.annual_premium)
ELSE NULL
END AS loss_ratio
FROM public.fact_claims c
JOIN public.fact_policies p
ON p.policy_id = c.policy_id
JOIN public.dim_time t
ON t.date_key = c.incident_date_key
LEFT JOIN public.dim_products pr
ON pr.product_key = p.product_key
GROUP BY
month_date,
t.year,
t.month,
t.year_month,
p.product_key,
pr.line_of_business,
p.state_code,
p.region_code;
-- ============================================================
-- DASHBOARD 3 — OPERATIONS DAILY MONITORING
-- ONE VIEW = ONE DASHBOARD
-- GRAIN: ONE ROW PER DAY / SEGMENT
-- ============================================================
CREATE OR REPLACE VIEW public.vw_dash_operations_daily AS
SELECT
-- Canonical daily date
t.full_date AS day_date,
t.year,
t.month,
t.year_month,
-- Dimensions
p.state_code,
p.region_code,
pr.line_of_business,
-- Portfolio in force (as of day)
COUNT(DISTINCT p.policy_id) AS active_policies,
-- Daily exposure
SUM(p.monthly_premium) AS daily_premium_exposure,
-- New business (policy starts)
COUNT(DISTINCT CASE
WHEN t.date_key = p.effective_date_key
THEN p.policy_id
END) AS policies_started,
-- Policy terminations (expirations)
COUNT(DISTINCT CASE
WHEN t.date_key = p.expiration_date_key
THEN p.policy_id
END) AS policies_ended
FROM public.dim_time t
JOIN public.fact_policies p
ON t.date_key BETWEEN p.effective_date_key
AND COALESCE(p.expiration_date_key, t.date_key)
LEFT JOIN public.dim_products pr
ON pr.product_key = p.product_key
GROUP BY
t.full_date,
t.year,
t.month,
t.year_month,
p.state_code,
p.region_code,
pr.line_of_business;
-- ============================================================
-- DASHBOARD 4 — RISK & UNDERWRITING (DAILY MONITORING)
-- ONE VIEW = ONE DASHBOARD
-- ============================================================
CREATE OR REPLACE VIEW public.vw_dash_risk_daily AS
SELECT
-- Canonical daily date
t.full_date AS day_date,
t.year,
t.month,
t.year_month,
-- Dimensions
dp.state_code,
dp.region_code,
pr.line_of_business,
-- Portfolio base
COUNT(DISTINCT fp.policy_id) AS active_policies,
-- Risk metrics
AVG(fp.risk_score) AS avg_risk_score,
COUNT(DISTINCT CASE
WHEN fp.risk_score >= 0.8 THEN fp.policy_id
END) AS high_risk_policies,
-- Underwriting mix
COUNT(DISTINCT CASE
WHEN dp.is_renewal = false THEN fp.policy_id
END) AS new_business_policies,
COUNT(DISTINCT CASE
WHEN dp.is_renewal = true THEN fp.policy_id
END) AS renewal_policies,
-- New business risk
AVG(CASE
WHEN dp.is_renewal = false THEN fp.risk_score
END) AS avg_new_business_risk
FROM public.dim_time t
JOIN public.fact_policies fp
ON t.date_key BETWEEN fp.effective_date_key
AND COALESCE(fp.expiration_date_key, t.date_key)
JOIN public.dim_policies dp
ON dp.policy_id = fp.policy_id
LEFT JOIN public.dim_products pr
ON pr.product_key = fp.product_key
GROUP BY
t.full_date,
t.year,
t.month,
t.year_month,
dp.state_code,
dp.region_code,
pr.line_of_business;
-- GRANTS
-- Read-only access for BI users
GRANT SELECT ON
public.dim_time,
public.dim_state,
public.dim_clients,
public.dim_products,
public.dim_policies,
public.fact_policies,
public.fact_claims,
public.fact_expenses,
public.fact_taxes
TO authenticated;
-- Full access for service role (ETL, maintenance)
GRANT SELECT, INSERT, UPDATE, DELETE ON
public.dim_time,
public.dim_state,
public.dim_clients,
public.dim_products,
public.dim_policies,
public.fact_policies,
public.fact_claims,
public.fact_expenses,
public.fact_taxes
TO service_role;
-- BI read access (Looker / dashboards)
GRANT SELECT ON
public.vw_dash_exec_portfolio,
public.vw_dash_claims_loss,
public.vw_dash_operations_daily,
public.vw_dash_risk_daily
TO authenticated;
-- Backend & validation access
GRANT SELECT ON
public.vw_dash_exec_portfolio,
public.vw_dash_claims_loss,
public.vw_dash_operations_daily,
public.vw_dash_risk_daily
TO service_role;
-- Notify PostgREST to reload schema metadata
NOTIFY pgrst, 'reload schema';