refs #729: seed RDS carrier performance demo
This commit is contained in:
@@ -68,6 +68,7 @@ DDS 변형의 능력 (멀티테넌트, OAuth2 federated identity, MAC 모드,
|
||||
| VPD 백오피스 화면 | `src/main/resources/templates/` | Thymeleaf 서버 렌더링 화면 |
|
||||
| VPD 백오피스 CSS/JS | `src/main/resources/static/` | 화면 동작과 스타일 |
|
||||
| VPD/ASO/FGA/ORDS/Select AI SQL | `sql/adb/` | ADB 적용·검증 SQL 스크립트 |
|
||||
| 원격 원장 seed/검증 SQL | `sql/source/` | PostgreSQL·MySQL 가상 원장과 HMM 선사 실적 데이터 |
|
||||
| DDS 백오피스 | `dds-backoffice/` | VPD와 분리된 DDS 전용 Spring Boot 데모 |
|
||||
| Streamlit 외피 / PoC4 MCP AI Console | `poc4_active_source_20260714/` | ReAct-style로 MCP tool을 호출하는 PoC4 데모 소스 snapshot |
|
||||
| Streamlit 외피 메인 화면 | `poc4_active_source_20260714/apps/poc4/mcp_discovery_ui.py` | MCP discovery, tool routing, ReAct-style agent loop, FGA evidence 표시 |
|
||||
@@ -97,6 +98,7 @@ Git 기준은 다음과 같다.
|
||||
| 계층 | 객체 | 역할 |
|
||||
|---|---|---|
|
||||
| 원격 | `public.customers` (PG), `ecommerce_poc.customers` (MySQL) | 원본 데이터 |
|
||||
| 원격 HMM 데모 | `hmm_demo.carriers`, `hmm_demo.carrier_monthly_performance` (PG) | 가상 선사 기준정보와 최근 18개월 월간 KPI |
|
||||
| ADB - 연결 | `RDS_POSTGRES_LINK`, `RDS_LINK` | `DBMS_CLOUD_ADMIN.CREATE_DATABASE_LINK` 로 만든 heterogeneous DB Link |
|
||||
| ADB - 매핑 | `app_customer`, `app_user`, `app_group`, `user_group`, `db_source`, `permission` | 누가 어느 소스의 어느 region 을 볼 수 있는지 |
|
||||
| ADB - 컨텍스트 | `vpd_ctx` (Secure Application Context) + `ctx_pkg` | 로그인 시 권한을 세션 컨텍스트로 로딩 |
|
||||
@@ -261,6 +263,8 @@ CREATE DATA GRANT admin.alice_apac_grant
|
||||
├── sql/
|
||||
│ ├── source/
|
||||
│ │ ├── postgres_setup.sql # 원격 PG: customers + 12 rows
|
||||
│ │ ├── postgres_hmm_carrier_performance.sql # 원격 PG: HMM 가상 선사 8개 + 월간 KPI 144 rows
|
||||
│ │ ├── postgres_hmm_carrier_performance_verify.sql # HMM 선사 실적 건수·패턴·제약·TLS·권한 검증
|
||||
│ │ └── mysql_setup.sql # 원격 MySQL: customers + 12 rows
|
||||
│ └── adb/
|
||||
│ ├── 00_cleanup.sql # 멱등 teardown
|
||||
|
||||
139
docs/design/729-rds-carrier-performance-demo/README.md
Normal file
139
docs/design/729-rds-carrier-performance-demo/README.md
Normal file
@@ -0,0 +1,139 @@
|
||||
# HMM RDS PostgreSQL 가상 선사 실적 데이터
|
||||
|
||||
- Redmine: #729
|
||||
- 적용 브랜치: `hmm-backoffice`
|
||||
- 원격 원장: AWS RDS for PostgreSQL
|
||||
- 대상 스키마: `hmm_demo`
|
||||
- 기준월 범위: 2025-02-01 ~ 2026-07-01
|
||||
|
||||
## 1. 배경
|
||||
|
||||
HMM 데모는 Oracle ADB의 직원·조직·권한 데이터와 AWS RDS PostgreSQL의 선사 실적을
|
||||
federation으로 결합한다. 사용자는 ADB에서 발급한 토큰으로 식별하고, 팀원은 자신에게
|
||||
배정된 선사만, 팀장은 팀원에게 배정된 선사 전체를 조회하는 흐름을 보여준다.
|
||||
|
||||
이번 단계에서는 federation 연결 전에 RDS에 재현 가능한 가상 선사 실적 원장을 만든다.
|
||||
실제 회사나 실제 재무 실적처럼 오인되지 않도록 모든 선사명과 수치는 데모 전용으로
|
||||
생성한다.
|
||||
|
||||
## 2. 시스템별 책임
|
||||
|
||||
| 시스템 | 보관 정보 | 접근 제어 책임 |
|
||||
|---|---|---|
|
||||
| RDS PostgreSQL | 선사 기본정보, 선사별 월간 KPI | 전용 스키마와 조회 전용 역할 |
|
||||
| Oracle ADB | 직원, 팀, 담당 선사 배정, 사용자 토큰 | VPD와 사용자 세션 context |
|
||||
| Select AI/MCP | 자연어 질문, 보안 View 조회 | ADB에서 허용한 View만 사용 |
|
||||
|
||||
직원 사번이나 팀 정보는 RDS에 복제하지 않는다. RDS 데이터에는 선사 코드만 두고, 직원과
|
||||
선사의 배정 관계는 후속 ADB 단계에서 `HMM_CARRIER_ASSIGNMENTS`로 관리한다.
|
||||
|
||||
## 3. 데이터 모델
|
||||
|
||||
### 3.1 `hmm_demo.carriers`
|
||||
|
||||
가상 선사 기준정보다.
|
||||
|
||||
| 컬럼 | 형식 | 설명 |
|
||||
|---|---|---|
|
||||
| `carrier_code` | `varchar(10)` | 가상 선사 식별 코드, PK |
|
||||
| `carrier_name` | `varchar(120)` | 가상 선사명, UNIQUE |
|
||||
| `country_code` | `char(2)` | ISO 형식의 데모 국가 코드 |
|
||||
| `alliance_name` | `varchar(80)` | 가상 협력 네트워크 |
|
||||
| `service_region` | `varchar(40)` | 주력 운항 권역 |
|
||||
| `performance_tier` | `varchar(10)` | `CORE`, `GROWTH`, `WATCH` |
|
||||
| `active` | `boolean` | 사용 여부 |
|
||||
| `created_at` | `timestamptz` | 최초 생성 시각 |
|
||||
|
||||
### 3.2 `hmm_demo.carrier_monthly_performance`
|
||||
|
||||
선사별 월간 실적 fact다. 한 선사·한 기준월에 한 행만 허용한다.
|
||||
|
||||
| 컬럼 | 형식 | 설명 |
|
||||
|---|---|---|
|
||||
| `carrier_code` | `varchar(10)` | 선사 코드, FK |
|
||||
| `performance_month` | `date` | 월의 첫째 날 |
|
||||
| `contracted_teu` | `integer` | 계약 물동량 |
|
||||
| `shipped_teu` | `integer` | 실제 운송 물동량 |
|
||||
| `revenue_usd` | `numeric(16,2)` | 매출 |
|
||||
| `gross_margin_usd` | `numeric(16,2)` | 매출총이익, 위험 패턴은 음수 허용 |
|
||||
| `schedule_reliability_pct` | `numeric(5,2)` | 정시 운항률 |
|
||||
| `vessel_utilization_pct` | `numeric(5,2)` | 선복 활용률 |
|
||||
| `claim_rate_pct` | `numeric(5,2)` | 클레임 비율 |
|
||||
| `yoy_growth_pct` | `numeric(7,2)` | 전년 동월 대비 성장률 |
|
||||
| `risk_level` | `varchar(10)` | `GREEN`, `AMBER`, `RED` |
|
||||
| `updated_at` | `timestamptz` | 적재 시각 |
|
||||
|
||||
PK는 `(carrier_code, performance_month)`다. 퍼센트 값은 0~100, 물동량과 매출은 0 이상을
|
||||
CHECK 제약으로 보장한다. 월별 조회와 위험 선사 조회에 각각 인덱스를 둔다.
|
||||
|
||||
### 3.3 `hmm_demo.carrier_performance_latest_v`
|
||||
|
||||
가장 최근 기준월의 선사명과 KPI를 한 번에 조회하는 View다. 후속 federation 점검과
|
||||
백오피스 미리보기에서 사용한다.
|
||||
|
||||
## 4. 가상 데이터
|
||||
|
||||
8개 가상 선사와 18개월 실적 144행을 만든다.
|
||||
|
||||
| 코드 | 가상 선사 | 의도한 데모 패턴 |
|
||||
|---|---|---|
|
||||
| `C001` | Bluewave Maritime | 높은 정시율과 지속 성장 |
|
||||
| `C002` | Pacific Star Lines | 안정적 실적 |
|
||||
| `C003` | Korea Ocean Link | 정시율과 클레임률 회복 |
|
||||
| `C004` | Nordic Seaways | 계절성 변동 |
|
||||
| `C005` | Orient Bridge Shipping | 점진적 물동량·마진 하락 |
|
||||
| `C006` | Atlantic Merchant Line | 정시율 저하와 클레임 증가 |
|
||||
| `C007` | Global Horizon Cargo | 빠른 성장 |
|
||||
| `C008` | Southern Cross Marine | 지속적인 고위험 패턴 |
|
||||
|
||||
모든 수치는 프로파일 상수와 월 순번으로 계산한다. 난수를 사용하지 않으므로 어느 환경에서
|
||||
다시 실행해도 같은 결과가 만들어진다.
|
||||
|
||||
## 5. 재실행과 변경 규칙
|
||||
|
||||
- `CREATE SCHEMA/TABLE IF NOT EXISTS`를 사용한다.
|
||||
- 기준정보와 실적은 PK 기준 `ON CONFLICT DO UPDATE`로 현행화한다.
|
||||
- 스크립트가 소유하지 않은 행은 삭제하지 않는다.
|
||||
- 전체 실행은 하나의 transaction으로 처리한다.
|
||||
- 오류가 발생하면 `ON_ERROR_STOP`과 transaction rollback으로 부분 적재를 방지한다.
|
||||
- 비밀번호, endpoint, CA 파일 경로는 SQL에 넣지 않는다.
|
||||
|
||||
## 6. 보안
|
||||
|
||||
- `hmm_federation_reader`는 `NOLOGIN` 그룹 역할로 만든다.
|
||||
- 해당 역할에는 `hmm_demo`의 `USAGE`와 테이블/View `SELECT`만 부여한다.
|
||||
- ADB database link용 LOGIN 역할과 비밀번호는 federation 단계에서 별도로 생성하고
|
||||
`hmm_federation_reader` 역할만 상속시킨다.
|
||||
- DDL/DML 권한과 `public` 스키마 변경 권한은 federation 역할에 부여하지 않는다.
|
||||
- RDS 연결은 AWS global CA bundle과 `sslmode=verify-full`로 검증한다.
|
||||
|
||||
## 7. 적재 순서
|
||||
|
||||
1. RDS TLS와 현재 계정의 CREATE 권한을 확인한다.
|
||||
2. `sql/source/postgres_hmm_carrier_performance.sql`을 실행한다.
|
||||
3. `sql/source/postgres_hmm_carrier_performance_verify.sql`을 실행한다.
|
||||
4. 재실행 후에도 동일한 선사 8개와 실적 144행인지 확인한다.
|
||||
5. 후속 작업에서 ADB credential과 PostgreSQL database link를 만든다.
|
||||
|
||||
## 8. 검증 기준
|
||||
|
||||
- 선사 수: `8`
|
||||
- 월간 실적: `144`
|
||||
- 선사별 월 수: 모두 `18`
|
||||
- 기간: `2025-02-01` ~ `2026-07-01`
|
||||
- 최신월 위험 분포에 `GREEN`, `AMBER`, `RED`가 모두 존재
|
||||
- `C001`의 최신 매출이 최초 매출보다 증가
|
||||
- `C005`, `C006`, `C008`의 최신 매출이 최초 매출보다 감소
|
||||
- 존재하지 않는 선사 코드, 잘못된 퍼센트, 월 첫째 날이 아닌 날짜 입력 거부
|
||||
- `hmm_federation_reader`에 `SELECT`만 부여
|
||||
- TLS 1.2 이상으로 실제 조회 성공
|
||||
|
||||
## 9. 제외 범위
|
||||
|
||||
- ADB의 `HMM_CARRIER_ASSIGNMENTS` 생성과 직원별 배정
|
||||
- ADB PostgreSQL database link 생성
|
||||
- ADB 보안 View와 VPD 정책
|
||||
- Select AI profile 및 MCP tool 변경
|
||||
- 실제 선사명이나 실제 경영 실적 적재
|
||||
|
||||
이 항목들은 RDS 가상 원장이 검증된 뒤 별도 단계로 진행한다.
|
||||
300
sql/source/postgres_hmm_carrier_performance.sql
Normal file
300
sql/source/postgres_hmm_carrier_performance.sql
Normal file
@@ -0,0 +1,300 @@
|
||||
-- ============================================================
|
||||
-- HMM demo carrier performance source data for PostgreSQL.
|
||||
--
|
||||
-- Target: AWS RDS for PostgreSQL, database postgres
|
||||
-- Schema: hmm_demo
|
||||
-- Redmine: #729
|
||||
--
|
||||
-- This script is idempotent. It owns only C001..C008 and their
|
||||
-- 2025-02 through 2026-07 monthly demo rows. It never stores an
|
||||
-- RDS endpoint, password, or certificate path.
|
||||
--
|
||||
-- Example:
|
||||
-- PGPASSWORD="$PG_PASSWORD" psql \
|
||||
-- "host=$PG_HOST port=5432 dbname=postgres user=$PG_USER \
|
||||
-- sslmode=verify-full sslrootcert=$PG_SSL_ROOT_CERT" \
|
||||
-- -X -f sql/source/postgres_hmm_carrier_performance.sql
|
||||
-- ============================================================
|
||||
|
||||
\set ON_ERROR_STOP on
|
||||
\echo === HMM carrier performance demo: begin ===
|
||||
|
||||
BEGIN;
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS hmm_demo AUTHORIZATION postgres;
|
||||
COMMENT ON SCHEMA hmm_demo IS
|
||||
'HMM federation demonstration data. All carrier names and KPI values are fictional.';
|
||||
|
||||
REVOKE CREATE ON SCHEMA hmm_demo FROM PUBLIC;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hmm_demo.carriers (
|
||||
carrier_code VARCHAR(10) PRIMARY KEY,
|
||||
carrier_name VARCHAR(120) NOT NULL UNIQUE,
|
||||
country_code CHAR(2) NOT NULL CHECK (country_code ~ '^[A-Z]{2}$'),
|
||||
alliance_name VARCHAR(80),
|
||||
service_region VARCHAR(40) NOT NULL,
|
||||
performance_tier VARCHAR(10) NOT NULL
|
||||
CHECK (performance_tier IN ('CORE', 'GROWTH', 'WATCH')),
|
||||
active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hmm_demo.carriers IS
|
||||
'Fictional carrier master data for the HMM ADB and PostgreSQL federation demo.';
|
||||
COMMENT ON COLUMN hmm_demo.carriers.carrier_code IS 'Fictional carrier identifier.';
|
||||
COMMENT ON COLUMN hmm_demo.carriers.performance_tier IS
|
||||
'Demo portfolio tier: CORE, GROWTH, or WATCH.';
|
||||
|
||||
CREATE TABLE IF NOT EXISTS hmm_demo.carrier_monthly_performance (
|
||||
carrier_code VARCHAR(10) NOT NULL
|
||||
REFERENCES hmm_demo.carriers (carrier_code),
|
||||
performance_month DATE NOT NULL
|
||||
CHECK (performance_month = date_trunc('month', performance_month)::DATE),
|
||||
contracted_teu INTEGER NOT NULL CHECK (contracted_teu >= 0),
|
||||
shipped_teu INTEGER NOT NULL CHECK (shipped_teu >= 0),
|
||||
revenue_usd NUMERIC(16,2) NOT NULL CHECK (revenue_usd >= 0),
|
||||
gross_margin_usd NUMERIC(16,2) NOT NULL,
|
||||
schedule_reliability_pct NUMERIC(5,2) NOT NULL
|
||||
CHECK (schedule_reliability_pct BETWEEN 0 AND 100),
|
||||
vessel_utilization_pct NUMERIC(5,2) NOT NULL
|
||||
CHECK (vessel_utilization_pct BETWEEN 0 AND 100),
|
||||
claim_rate_pct NUMERIC(5,2) NOT NULL
|
||||
CHECK (claim_rate_pct BETWEEN 0 AND 100),
|
||||
yoy_growth_pct NUMERIC(7,2) NOT NULL,
|
||||
risk_level VARCHAR(10) NOT NULL
|
||||
CHECK (risk_level IN ('GREEN', 'AMBER', 'RED')),
|
||||
updated_at TIMESTAMPTZ NOT NULL,
|
||||
PRIMARY KEY (carrier_code, performance_month)
|
||||
);
|
||||
|
||||
COMMENT ON TABLE hmm_demo.carrier_monthly_performance IS
|
||||
'Deterministic fictional monthly carrier KPIs for 2025-02 through 2026-07.';
|
||||
COMMENT ON COLUMN hmm_demo.carrier_monthly_performance.performance_month IS
|
||||
'First calendar day of the KPI month.';
|
||||
COMMENT ON COLUMN hmm_demo.carrier_monthly_performance.risk_level IS
|
||||
'Derived demo risk level based on margin, reliability, and claim rate.';
|
||||
|
||||
CREATE INDEX IF NOT EXISTS ix_carrier_perf_month
|
||||
ON hmm_demo.carrier_monthly_performance (performance_month, carrier_code);
|
||||
CREATE INDEX IF NOT EXISTS ix_carrier_perf_risk
|
||||
ON hmm_demo.carrier_monthly_performance (risk_level, performance_month);
|
||||
|
||||
INSERT INTO hmm_demo.carriers (
|
||||
carrier_code,
|
||||
carrier_name,
|
||||
country_code,
|
||||
alliance_name,
|
||||
service_region,
|
||||
performance_tier,
|
||||
active
|
||||
) VALUES
|
||||
('C001', 'Bluewave Maritime', 'SG', 'North Pacific Network', 'ASIA-PACIFIC', 'CORE', TRUE),
|
||||
('C002', 'Pacific Star Lines', 'JP', 'East Ocean Connect', 'ASIA-PACIFIC', 'CORE', TRUE),
|
||||
('C003', 'Korea Ocean Link', 'KR', 'East Ocean Connect', 'ASIA-PACIFIC', 'GROWTH', TRUE),
|
||||
('C004', 'Nordic Seaways', 'DK', 'Northern Trade Group', 'EUROPE', 'CORE', TRUE),
|
||||
('C005', 'Orient Bridge Shipping', 'TW', 'North Pacific Network', 'ASIA-PACIFIC', 'WATCH', TRUE),
|
||||
('C006', 'Atlantic Merchant Line', 'DE', 'Atlantic Cargo Forum', 'EUROPE-AMERICAS', 'WATCH', TRUE),
|
||||
('C007', 'Global Horizon Cargo', 'US', 'Atlantic Cargo Forum', 'AMERICAS', 'GROWTH', TRUE),
|
||||
('C008', 'Southern Cross Marine', 'FR', 'Southern Ocean Group', 'OCEANIA', 'WATCH', TRUE)
|
||||
ON CONFLICT (carrier_code) DO UPDATE SET
|
||||
carrier_name = EXCLUDED.carrier_name,
|
||||
country_code = EXCLUDED.country_code,
|
||||
alliance_name = EXCLUDED.alliance_name,
|
||||
service_region = EXCLUDED.service_region,
|
||||
performance_tier = EXCLUDED.performance_tier,
|
||||
active = EXCLUDED.active;
|
||||
|
||||
WITH month_series AS (
|
||||
SELECT
|
||||
month_index,
|
||||
(DATE '2025-02-01' + make_interval(months => month_index))::DATE AS performance_month,
|
||||
CASE month_index % 6
|
||||
WHEN 0 THEN -1
|
||||
WHEN 1 THEN 0
|
||||
WHEN 2 THEN 1
|
||||
WHEN 3 THEN 2
|
||||
WHEN 4 THEN 1
|
||||
ELSE -1
|
||||
END::NUMERIC AS seasonal_factor
|
||||
FROM generate_series(0, 17) AS series(month_index)
|
||||
),
|
||||
profiles (
|
||||
carrier_code,
|
||||
base_teu,
|
||||
monthly_teu_step,
|
||||
seasonal_teu,
|
||||
utilization_base,
|
||||
utilization_step,
|
||||
revenue_per_teu,
|
||||
revenue_step,
|
||||
margin_pct_base,
|
||||
margin_pct_step,
|
||||
reliability_base,
|
||||
reliability_step,
|
||||
claim_rate_base,
|
||||
claim_rate_step,
|
||||
yoy_growth_base,
|
||||
yoy_growth_step
|
||||
) AS (
|
||||
VALUES
|
||||
('C001', 1800::NUMERIC, 25::NUMERIC, 80::NUMERIC, 91.5::NUMERIC, 0.10::NUMERIC, 1850::NUMERIC, 4::NUMERIC, 15.0::NUMERIC, 0.10::NUMERIC, 95.0::NUMERIC, 0.05::NUMERIC, 0.35::NUMERIC, -0.005::NUMERIC, 8.0::NUMERIC, 0.25::NUMERIC),
|
||||
('C002', 1500::NUMERIC, 5::NUMERIC, 60::NUMERIC, 88.5::NUMERIC, 0.02::NUMERIC, 1780::NUMERIC, 3::NUMERIC, 12.0::NUMERIC, 0.00::NUMERIC, 92.5::NUMERIC, 0.00::NUMERIC, 0.45::NUMERIC, 0.005::NUMERIC, 3.0::NUMERIC, 0.05::NUMERIC),
|
||||
('C003', 1050::NUMERIC, 15::NUMERIC, 50::NUMERIC, 83.0::NUMERIC, 0.20::NUMERIC, 1920::NUMERIC, 2::NUMERIC, 8.0::NUMERIC, 0.15::NUMERIC, 88.5::NUMERIC, 0.25::NUMERIC, 1.20::NUMERIC, -0.040::NUMERIC, -4.0::NUMERIC, 0.60::NUMERIC),
|
||||
('C004', 1350::NUMERIC, 0::NUMERIC, 180::NUMERIC, 86.0::NUMERIC, 0.00::NUMERIC, 1810::NUMERIC, 3::NUMERIC, 10.0::NUMERIC, 0.00::NUMERIC, 93.0::NUMERIC, 0.00::NUMERIC, 0.55::NUMERIC, 0.010::NUMERIC, 1.0::NUMERIC, 0.10::NUMERIC),
|
||||
('C005', 1300::NUMERIC, -20::NUMERIC, 70::NUMERIC, 88.0::NUMERIC, -0.10::NUMERIC, 1850::NUMERIC, -2::NUMERIC, 8.0::NUMERIC, -0.20::NUMERIC, 91.0::NUMERIC, -0.15::NUMERIC, 0.80::NUMERIC, 0.030::NUMERIC, -2.0::NUMERIC, -0.50::NUMERIC),
|
||||
('C006', 1250::NUMERIC, -10::NUMERIC, 90::NUMERIC, 84.0::NUMERIC, -0.12::NUMERIC, 1800::NUMERIC, -3::NUMERIC, 6.0::NUMERIC, -0.15::NUMERIC, 87.0::NUMERIC, -0.25::NUMERIC, 1.50::NUMERIC, 0.050::NUMERIC, -5.0::NUMERIC, -0.70::NUMERIC),
|
||||
('C007', 800::NUMERIC, 30::NUMERIC, 100::NUMERIC, 82.0::NUMERIC, 0.25::NUMERIC, 2050::NUMERIC, 5::NUMERIC, 9.0::NUMERIC, 0.18::NUMERIC, 90.5::NUMERIC, 0.15::NUMERIC, 0.70::NUMERIC, -0.020::NUMERIC, 12.0::NUMERIC, 0.50::NUMERIC),
|
||||
('C008', 1150::NUMERIC, -25::NUMERIC, 60::NUMERIC, 80.0::NUMERIC, -0.15::NUMERIC, 1750::NUMERIC, -4::NUMERIC, 3.0::NUMERIC, -0.20::NUMERIC, 85.0::NUMERIC, -0.30::NUMERIC, 2.00::NUMERIC, 0.080::NUMERIC, -8.0::NUMERIC, -0.80::NUMERIC)
|
||||
),
|
||||
base_metrics AS (
|
||||
SELECT
|
||||
p.carrier_code,
|
||||
m.performance_month,
|
||||
ROUND(
|
||||
p.base_teu
|
||||
+ p.monthly_teu_step * m.month_index
|
||||
+ p.seasonal_teu * m.seasonal_factor
|
||||
)::INTEGER AS contracted_teu,
|
||||
LEAST(
|
||||
98.0::NUMERIC,
|
||||
GREATEST(
|
||||
60.0::NUMERIC,
|
||||
p.utilization_base
|
||||
+ p.utilization_step * m.month_index
|
||||
+ m.seasonal_factor * 0.50
|
||||
)
|
||||
) AS utilization_pct,
|
||||
p.revenue_per_teu + p.revenue_step * m.month_index AS unit_revenue,
|
||||
p.margin_pct_base + p.margin_pct_step * m.month_index AS margin_pct,
|
||||
LEAST(
|
||||
99.5::NUMERIC,
|
||||
GREATEST(
|
||||
60.0::NUMERIC,
|
||||
p.reliability_base
|
||||
+ p.reliability_step * m.month_index
|
||||
+ m.seasonal_factor * 0.20
|
||||
)
|
||||
) AS reliability_pct,
|
||||
LEAST(
|
||||
100.0::NUMERIC,
|
||||
GREATEST(
|
||||
0.0::NUMERIC,
|
||||
p.claim_rate_base + p.claim_rate_step * m.month_index
|
||||
)
|
||||
) AS claim_pct,
|
||||
p.yoy_growth_base + p.yoy_growth_step * m.month_index AS yoy_pct
|
||||
FROM profiles p
|
||||
CROSS JOIN month_series m
|
||||
),
|
||||
volume_metrics AS (
|
||||
SELECT
|
||||
b.*,
|
||||
ROUND(b.contracted_teu * b.utilization_pct / 100.0)::INTEGER AS shipped_teu
|
||||
FROM base_metrics b
|
||||
),
|
||||
financial_metrics AS (
|
||||
SELECT
|
||||
v.*,
|
||||
ROUND(v.shipped_teu * v.unit_revenue, 2) AS revenue_usd
|
||||
FROM volume_metrics v
|
||||
),
|
||||
final_metrics AS (
|
||||
SELECT
|
||||
f.*,
|
||||
ROUND(f.revenue_usd * f.margin_pct / 100.0, 2) AS gross_margin_usd
|
||||
FROM financial_metrics f
|
||||
)
|
||||
INSERT INTO hmm_demo.carrier_monthly_performance (
|
||||
carrier_code,
|
||||
performance_month,
|
||||
contracted_teu,
|
||||
shipped_teu,
|
||||
revenue_usd,
|
||||
gross_margin_usd,
|
||||
schedule_reliability_pct,
|
||||
vessel_utilization_pct,
|
||||
claim_rate_pct,
|
||||
yoy_growth_pct,
|
||||
risk_level,
|
||||
updated_at
|
||||
)
|
||||
SELECT
|
||||
f.carrier_code,
|
||||
f.performance_month,
|
||||
f.contracted_teu,
|
||||
f.shipped_teu,
|
||||
f.revenue_usd,
|
||||
f.gross_margin_usd,
|
||||
ROUND(f.reliability_pct, 2),
|
||||
ROUND(f.utilization_pct, 2),
|
||||
ROUND(f.claim_pct, 2),
|
||||
ROUND(f.yoy_pct, 2),
|
||||
CASE
|
||||
WHEN f.gross_margin_usd < 0
|
||||
OR f.reliability_pct < 87
|
||||
OR f.claim_pct >= 2 THEN 'RED'
|
||||
WHEN f.margin_pct < 7
|
||||
OR f.reliability_pct < 92
|
||||
OR f.claim_pct >= 1 THEN 'AMBER'
|
||||
ELSE 'GREEN'
|
||||
END,
|
||||
TIMESTAMPTZ '2026-07-24 00:00:00+00'
|
||||
FROM final_metrics f
|
||||
ON CONFLICT (carrier_code, performance_month) DO UPDATE SET
|
||||
contracted_teu = EXCLUDED.contracted_teu,
|
||||
shipped_teu = EXCLUDED.shipped_teu,
|
||||
revenue_usd = EXCLUDED.revenue_usd,
|
||||
gross_margin_usd = EXCLUDED.gross_margin_usd,
|
||||
schedule_reliability_pct = EXCLUDED.schedule_reliability_pct,
|
||||
vessel_utilization_pct = EXCLUDED.vessel_utilization_pct,
|
||||
claim_rate_pct = EXCLUDED.claim_rate_pct,
|
||||
yoy_growth_pct = EXCLUDED.yoy_growth_pct,
|
||||
risk_level = EXCLUDED.risk_level,
|
||||
updated_at = EXCLUDED.updated_at;
|
||||
|
||||
CREATE OR REPLACE VIEW hmm_demo.carrier_performance_latest_v AS
|
||||
SELECT
|
||||
c.carrier_code,
|
||||
c.carrier_name,
|
||||
c.country_code,
|
||||
c.alliance_name,
|
||||
c.service_region,
|
||||
c.performance_tier,
|
||||
p.performance_month,
|
||||
p.contracted_teu,
|
||||
p.shipped_teu,
|
||||
p.revenue_usd,
|
||||
p.gross_margin_usd,
|
||||
p.schedule_reliability_pct,
|
||||
p.vessel_utilization_pct,
|
||||
p.claim_rate_pct,
|
||||
p.yoy_growth_pct,
|
||||
p.risk_level
|
||||
FROM hmm_demo.carriers c
|
||||
JOIN hmm_demo.carrier_monthly_performance p
|
||||
ON p.carrier_code = c.carrier_code
|
||||
WHERE p.performance_month = (
|
||||
SELECT MAX(latest.performance_month)
|
||||
FROM hmm_demo.carrier_monthly_performance latest
|
||||
);
|
||||
|
||||
COMMENT ON VIEW hmm_demo.carrier_performance_latest_v IS
|
||||
'Latest fictional monthly KPI for each active demo carrier.';
|
||||
|
||||
DO $role$
|
||||
BEGIN
|
||||
IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'hmm_federation_reader') THEN
|
||||
CREATE ROLE hmm_federation_reader NOLOGIN;
|
||||
END IF;
|
||||
END
|
||||
$role$;
|
||||
|
||||
GRANT USAGE ON SCHEMA hmm_demo TO hmm_federation_reader;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA hmm_demo TO hmm_federation_reader;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA hmm_demo
|
||||
GRANT SELECT ON TABLES TO hmm_federation_reader;
|
||||
|
||||
COMMIT;
|
||||
|
||||
\echo === HMM carrier performance demo: complete ===
|
||||
\echo Next: run sql/source/postgres_hmm_carrier_performance_verify.sql
|
||||
324
sql/source/postgres_hmm_carrier_performance_verify.sql
Normal file
324
sql/source/postgres_hmm_carrier_performance_verify.sql
Normal file
@@ -0,0 +1,324 @@
|
||||
-- ============================================================
|
||||
-- Verification for HMM demo carrier performance data.
|
||||
-- Redmine: #729
|
||||
-- ============================================================
|
||||
|
||||
\set ON_ERROR_STOP on
|
||||
\pset pager off
|
||||
\echo === HMM carrier performance demo: verification begin ===
|
||||
|
||||
DO $verify$
|
||||
DECLARE
|
||||
carrier_count INTEGER;
|
||||
performance_count INTEGER;
|
||||
invalid_month_count INTEGER;
|
||||
latest_month DATE;
|
||||
earliest_month DATE;
|
||||
wrong_series_count INTEGER;
|
||||
latest_risk_count INTEGER;
|
||||
wrong_trend_count INTEGER;
|
||||
reader_can_login BOOLEAN;
|
||||
BEGIN
|
||||
SELECT COUNT(*)
|
||||
INTO carrier_count
|
||||
FROM hmm_demo.carriers
|
||||
WHERE carrier_code BETWEEN 'C001' AND 'C008';
|
||||
|
||||
SELECT COUNT(*), MIN(performance_month), MAX(performance_month)
|
||||
INTO performance_count, earliest_month, latest_month
|
||||
FROM hmm_demo.carrier_monthly_performance
|
||||
WHERE carrier_code BETWEEN 'C001' AND 'C008'
|
||||
AND performance_month BETWEEN DATE '2025-02-01' AND DATE '2026-07-01';
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO invalid_month_count
|
||||
FROM hmm_demo.carrier_monthly_performance
|
||||
WHERE performance_month <> date_trunc('month', performance_month)::DATE;
|
||||
|
||||
SELECT COUNT(*)
|
||||
INTO wrong_series_count
|
||||
FROM (
|
||||
SELECT carrier_code, COUNT(*) AS month_count
|
||||
FROM hmm_demo.carrier_monthly_performance
|
||||
WHERE carrier_code BETWEEN 'C001' AND 'C008'
|
||||
AND performance_month BETWEEN DATE '2025-02-01' AND DATE '2026-07-01'
|
||||
GROUP BY carrier_code
|
||||
HAVING COUNT(*) <> 18
|
||||
) wrong_series;
|
||||
|
||||
SELECT COUNT(DISTINCT risk_level)
|
||||
INTO latest_risk_count
|
||||
FROM hmm_demo.carrier_performance_latest_v;
|
||||
|
||||
WITH boundaries AS (
|
||||
SELECT
|
||||
carrier_code,
|
||||
MAX(revenue_usd) FILTER (WHERE performance_month = DATE '2025-02-01')
|
||||
AS first_revenue,
|
||||
MAX(revenue_usd) FILTER (WHERE performance_month = DATE '2026-07-01')
|
||||
AS latest_revenue
|
||||
FROM hmm_demo.carrier_monthly_performance
|
||||
WHERE carrier_code BETWEEN 'C001' AND 'C008'
|
||||
GROUP BY carrier_code
|
||||
)
|
||||
SELECT COUNT(*)
|
||||
INTO wrong_trend_count
|
||||
FROM boundaries
|
||||
WHERE (carrier_code IN ('C001', 'C002', 'C003', 'C004', 'C007')
|
||||
AND latest_revenue <= first_revenue)
|
||||
OR (carrier_code IN ('C005', 'C006', 'C008')
|
||||
AND latest_revenue >= first_revenue);
|
||||
|
||||
SELECT rolcanlogin
|
||||
INTO reader_can_login
|
||||
FROM pg_roles
|
||||
WHERE rolname = 'hmm_federation_reader';
|
||||
|
||||
IF carrier_count <> 8 THEN
|
||||
RAISE EXCEPTION 'Expected 8 carriers, found %', carrier_count;
|
||||
END IF;
|
||||
IF performance_count <> 144 THEN
|
||||
RAISE EXCEPTION 'Expected 144 monthly performance rows, found %', performance_count;
|
||||
END IF;
|
||||
IF earliest_month <> DATE '2025-02-01' OR latest_month <> DATE '2026-07-01' THEN
|
||||
RAISE EXCEPTION 'Unexpected period: % through %', earliest_month, latest_month;
|
||||
END IF;
|
||||
IF invalid_month_count <> 0 OR wrong_series_count <> 0 THEN
|
||||
RAISE EXCEPTION
|
||||
'Invalid monthly series: invalid dates %, wrong carrier series %',
|
||||
invalid_month_count,
|
||||
wrong_series_count;
|
||||
END IF;
|
||||
IF latest_risk_count <> 3 THEN
|
||||
RAISE EXCEPTION 'Expected GREEN, AMBER, RED latest risks, found % levels',
|
||||
latest_risk_count;
|
||||
END IF;
|
||||
IF wrong_trend_count <> 0 THEN
|
||||
RAISE EXCEPTION 'Unexpected first-to-latest trend for % carriers', wrong_trend_count;
|
||||
END IF;
|
||||
IF reader_can_login IS DISTINCT FROM FALSE THEN
|
||||
RAISE EXCEPTION 'hmm_federation_reader must remain a NOLOGIN role';
|
||||
END IF;
|
||||
|
||||
IF NOT has_schema_privilege('hmm_federation_reader', 'hmm_demo', 'USAGE') THEN
|
||||
RAISE EXCEPTION 'hmm_federation_reader lacks schema USAGE';
|
||||
END IF;
|
||||
IF has_schema_privilege('hmm_federation_reader', 'hmm_demo', 'CREATE') THEN
|
||||
RAISE EXCEPTION 'hmm_federation_reader unexpectedly has schema CREATE';
|
||||
END IF;
|
||||
IF NOT has_table_privilege(
|
||||
'hmm_federation_reader',
|
||||
'hmm_demo.carrier_monthly_performance',
|
||||
'SELECT'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'hmm_federation_reader lacks SELECT';
|
||||
END IF;
|
||||
IF has_table_privilege(
|
||||
'hmm_federation_reader',
|
||||
'hmm_demo.carrier_monthly_performance',
|
||||
'INSERT'
|
||||
) THEN
|
||||
RAISE EXCEPTION 'hmm_federation_reader unexpectedly has INSERT';
|
||||
END IF;
|
||||
END
|
||||
$verify$;
|
||||
|
||||
DO $constraint_tests$
|
||||
BEGIN
|
||||
BEGIN
|
||||
INSERT INTO hmm_demo.carrier_monthly_performance (
|
||||
carrier_code,
|
||||
performance_month,
|
||||
contracted_teu,
|
||||
shipped_teu,
|
||||
revenue_usd,
|
||||
gross_margin_usd,
|
||||
schedule_reliability_pct,
|
||||
vessel_utilization_pct,
|
||||
claim_rate_pct,
|
||||
yoy_growth_pct,
|
||||
risk_level,
|
||||
updated_at
|
||||
) VALUES (
|
||||
'C001',
|
||||
DATE '2026-07-02',
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
90,
|
||||
90,
|
||||
1,
|
||||
1,
|
||||
'GREEN',
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
RAISE EXCEPTION 'Month-first-day constraint did not reject invalid input';
|
||||
EXCEPTION
|
||||
WHEN check_violation THEN
|
||||
NULL;
|
||||
END;
|
||||
|
||||
BEGIN
|
||||
INSERT INTO hmm_demo.carrier_monthly_performance (
|
||||
carrier_code,
|
||||
performance_month,
|
||||
contracted_teu,
|
||||
shipped_teu,
|
||||
revenue_usd,
|
||||
gross_margin_usd,
|
||||
schedule_reliability_pct,
|
||||
vessel_utilization_pct,
|
||||
claim_rate_pct,
|
||||
yoy_growth_pct,
|
||||
risk_level,
|
||||
updated_at
|
||||
) VALUES (
|
||||
'UNKNOWN',
|
||||
DATE '2026-08-01',
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
90,
|
||||
90,
|
||||
1,
|
||||
1,
|
||||
'GREEN',
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
RAISE EXCEPTION 'Foreign key constraint did not reject unknown carrier';
|
||||
EXCEPTION
|
||||
WHEN foreign_key_violation THEN
|
||||
NULL;
|
||||
END;
|
||||
|
||||
BEGIN
|
||||
INSERT INTO hmm_demo.carrier_monthly_performance (
|
||||
carrier_code,
|
||||
performance_month,
|
||||
contracted_teu,
|
||||
shipped_teu,
|
||||
revenue_usd,
|
||||
gross_margin_usd,
|
||||
schedule_reliability_pct,
|
||||
vessel_utilization_pct,
|
||||
claim_rate_pct,
|
||||
yoy_growth_pct,
|
||||
risk_level,
|
||||
updated_at
|
||||
) VALUES (
|
||||
'C001',
|
||||
DATE '2026-08-01',
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
1,
|
||||
101,
|
||||
90,
|
||||
1,
|
||||
1,
|
||||
'GREEN',
|
||||
CURRENT_TIMESTAMP
|
||||
);
|
||||
RAISE EXCEPTION 'Percentage constraint did not reject invalid input';
|
||||
EXCEPTION
|
||||
WHEN check_violation THEN
|
||||
NULL;
|
||||
END;
|
||||
END
|
||||
$constraint_tests$;
|
||||
|
||||
\echo --- Row counts and period ---
|
||||
SELECT
|
||||
COUNT(DISTINCT carrier_code) AS carrier_count,
|
||||
COUNT(*) AS monthly_row_count,
|
||||
MIN(performance_month) AS first_month,
|
||||
MAX(performance_month) AS latest_month
|
||||
FROM hmm_demo.carrier_monthly_performance
|
||||
WHERE carrier_code BETWEEN 'C001' AND 'C008'
|
||||
AND performance_month BETWEEN DATE '2025-02-01' AND DATE '2026-07-01';
|
||||
|
||||
\echo --- Per-carrier series ---
|
||||
SELECT
|
||||
c.carrier_code,
|
||||
c.carrier_name,
|
||||
COUNT(p.*) AS month_count,
|
||||
MIN(p.performance_month) AS first_month,
|
||||
MAX(p.performance_month) AS latest_month
|
||||
FROM hmm_demo.carriers c
|
||||
JOIN hmm_demo.carrier_monthly_performance p
|
||||
ON p.carrier_code = c.carrier_code
|
||||
WHERE c.carrier_code BETWEEN 'C001' AND 'C008'
|
||||
GROUP BY c.carrier_code, c.carrier_name
|
||||
ORDER BY c.carrier_code;
|
||||
|
||||
\echo --- Latest KPI and risk distribution ---
|
||||
SELECT
|
||||
carrier_code,
|
||||
carrier_name,
|
||||
performance_month,
|
||||
shipped_teu,
|
||||
revenue_usd,
|
||||
gross_margin_usd,
|
||||
schedule_reliability_pct,
|
||||
claim_rate_pct,
|
||||
risk_level
|
||||
FROM hmm_demo.carrier_performance_latest_v
|
||||
ORDER BY carrier_code;
|
||||
|
||||
SELECT risk_level, COUNT(*) AS carrier_count
|
||||
FROM hmm_demo.carrier_performance_latest_v
|
||||
GROUP BY risk_level
|
||||
ORDER BY risk_level;
|
||||
|
||||
\echo --- First-to-latest revenue trend ---
|
||||
WITH boundaries AS (
|
||||
SELECT
|
||||
carrier_code,
|
||||
MAX(revenue_usd) FILTER (WHERE performance_month = DATE '2025-02-01') AS first_revenue,
|
||||
MAX(revenue_usd) FILTER (WHERE performance_month = DATE '2026-07-01') AS latest_revenue
|
||||
FROM hmm_demo.carrier_monthly_performance
|
||||
WHERE carrier_code BETWEEN 'C001' AND 'C008'
|
||||
GROUP BY carrier_code
|
||||
)
|
||||
SELECT
|
||||
carrier_code,
|
||||
first_revenue,
|
||||
latest_revenue,
|
||||
CASE
|
||||
WHEN latest_revenue > first_revenue THEN 'UP'
|
||||
WHEN latest_revenue < first_revenue THEN 'DOWN'
|
||||
ELSE 'FLAT'
|
||||
END AS trend
|
||||
FROM boundaries
|
||||
ORDER BY carrier_code;
|
||||
|
||||
\echo --- TLS session ---
|
||||
SELECT
|
||||
ssl,
|
||||
version AS tls_version,
|
||||
cipher,
|
||||
bits
|
||||
FROM pg_stat_ssl
|
||||
WHERE pid = pg_backend_pid();
|
||||
|
||||
\echo --- Read-only role privileges ---
|
||||
SELECT
|
||||
(SELECT NOT rolcanlogin
|
||||
FROM pg_roles
|
||||
WHERE rolname = 'hmm_federation_reader') AS no_login,
|
||||
has_schema_privilege('hmm_federation_reader', 'hmm_demo', 'USAGE') AS schema_usage,
|
||||
has_schema_privilege('hmm_federation_reader', 'hmm_demo', 'CREATE') AS schema_create,
|
||||
has_table_privilege(
|
||||
'hmm_federation_reader',
|
||||
'hmm_demo.carrier_monthly_performance',
|
||||
'SELECT'
|
||||
) AS can_select,
|
||||
has_table_privilege(
|
||||
'hmm_federation_reader',
|
||||
'hmm_demo.carrier_monthly_performance',
|
||||
'INSERT'
|
||||
) AS can_insert;
|
||||
|
||||
\echo === HMM carrier performance demo: verification complete ===
|
||||
Reference in New Issue
Block a user