20260729_320_device_quality.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. """Add immutable device quality policies, runs, results, and evidence."""
  2. from alembic import op
  3. revision = "20260729_320"
  4. down_revision = "20260729_310"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(
  9. """
  10. CREATE TABLE public.device_quality_profiles (
  11. uid UUID PRIMARY KEY,
  12. code VARCHAR(120) NOT NULL UNIQUE,
  13. name VARCHAR(300) NOT NULL,
  14. created_by VARCHAR(120) NOT NULL,
  15. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
  16. );
  17. CREATE TABLE public.device_quality_profile_versions (
  18. uid UUID PRIMARY KEY,
  19. profile_uid UUID NOT NULL
  20. REFERENCES public.device_quality_profiles(uid)
  21. ON DELETE RESTRICT,
  22. version INTEGER NOT NULL CHECK (version > 0),
  23. status VARCHAR(20) NOT NULL DEFAULT 'draft'
  24. CHECK (status IN ('draft','published','superseded')),
  25. rules JSONB NOT NULL,
  26. content_hash CHAR(64) NOT NULL,
  27. created_by VARCHAR(120) NOT NULL,
  28. published_by VARCHAR(120),
  29. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  30. published_at TIMESTAMPTZ,
  31. UNIQUE (profile_uid, version),
  32. UNIQUE (profile_uid, content_hash)
  33. );
  34. CREATE UNIQUE INDEX uq_device_quality_active_profile
  35. ON public.device_quality_profile_versions(profile_uid)
  36. WHERE status = 'published';
  37. CREATE INDEX idx_device_quality_profile_versions
  38. ON public.device_quality_profile_versions(
  39. profile_uid, version DESC
  40. );
  41. CREATE TABLE public.device_quality_runs (
  42. uid UUID PRIMARY KEY,
  43. policy_version_uid UUID NOT NULL
  44. REFERENCES public.device_quality_profile_versions(uid)
  45. ON DELETE RESTRICT,
  46. policy_hash CHAR(64) NOT NULL,
  47. source_uid UUID
  48. REFERENCES public.ingestion_sources(uid)
  49. ON DELETE RESTRICT,
  50. status VARCHAR(20) NOT NULL
  51. CHECK (status IN ('success','failed')),
  52. total_assets INTEGER NOT NULL CHECK (total_assets >= 0),
  53. total_violations INTEGER NOT NULL CHECK (total_violations >= 0),
  54. score DOUBLE PRECISION NOT NULL
  55. CHECK (score >= 0 AND score <= 100),
  56. created_by VARCHAR(120) NOT NULL,
  57. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP
  58. );
  59. CREATE INDEX idx_device_quality_runs_created
  60. ON public.device_quality_runs(created_at DESC, uid);
  61. CREATE INDEX idx_device_quality_runs_policy
  62. ON public.device_quality_runs(policy_version_uid, created_at DESC);
  63. CREATE TABLE public.device_quality_rule_results (
  64. uid UUID PRIMARY KEY,
  65. run_uid UUID NOT NULL
  66. REFERENCES public.device_quality_runs(uid)
  67. ON DELETE CASCADE,
  68. rule_code VARCHAR(120) NOT NULL,
  69. severity VARCHAR(20) NOT NULL
  70. CHECK (severity IN ('info','warning','error','critical')),
  71. weight DOUBLE PRECISION NOT NULL
  72. CHECK (weight >= 0 AND weight <= 100),
  73. status VARCHAR(30) NOT NULL
  74. CHECK (status IN ('passed','violated','not_applicable')),
  75. evaluated_count INTEGER NOT NULL CHECK (evaluated_count >= 0),
  76. violation_count INTEGER NOT NULL CHECK (
  77. violation_count >= 0
  78. AND violation_count <= evaluated_count
  79. ),
  80. sampled_count INTEGER NOT NULL CHECK (
  81. sampled_count >= 0
  82. AND sampled_count <= violation_count
  83. ),
  84. pass_rate DOUBLE PRECISION NOT NULL
  85. CHECK (pass_rate >= 0 AND pass_rate <= 1),
  86. weighted_score DOUBLE PRECISION NOT NULL CHECK (
  87. weighted_score >= 0 AND weighted_score <= weight
  88. ),
  89. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  90. UNIQUE (run_uid, rule_code)
  91. );
  92. CREATE INDEX idx_device_quality_rule_results
  93. ON public.device_quality_rule_results(
  94. run_uid, severity, rule_code
  95. );
  96. CREATE TABLE public.device_quality_violation_samples (
  97. uid UUID PRIMARY KEY,
  98. run_uid UUID NOT NULL
  99. REFERENCES public.device_quality_runs(uid)
  100. ON DELETE CASCADE,
  101. rule_code VARCHAR(120) NOT NULL,
  102. severity VARCHAR(20) NOT NULL
  103. CHECK (severity IN ('info','warning','error','critical')),
  104. asset_uid UUID NOT NULL
  105. REFERENCES public.device_assets(uid)
  106. ON DELETE RESTRICT,
  107. field_name VARCHAR(300) NOT NULL,
  108. source_uid UUID
  109. REFERENCES public.ingestion_sources(uid)
  110. ON DELETE RESTRICT,
  111. source_mapping_uid UUID
  112. REFERENCES public.device_asset_source_mappings(uid)
  113. ON DELETE RESTRICT,
  114. message VARCHAR(1000) NOT NULL,
  115. evidence JSONB NOT NULL,
  116. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  117. expires_at TIMESTAMPTZ NOT NULL,
  118. UNIQUE (run_uid, rule_code, asset_uid, field_name)
  119. );
  120. CREATE INDEX idx_device_quality_violation_run
  121. ON public.device_quality_violation_samples(
  122. run_uid, rule_code, asset_uid
  123. );
  124. CREATE INDEX idx_device_quality_violation_retention
  125. ON public.device_quality_violation_samples(expires_at);
  126. CREATE TABLE public.device_quality_asset_scores (
  127. uid UUID PRIMARY KEY,
  128. run_uid UUID NOT NULL
  129. REFERENCES public.device_quality_runs(uid)
  130. ON DELETE CASCADE,
  131. asset_uid UUID NOT NULL
  132. REFERENCES public.device_assets(uid)
  133. ON DELETE RESTRICT,
  134. asset_type VARCHAR(40) NOT NULL,
  135. status VARCHAR(30) NOT NULL
  136. CHECK (status IN ('passed','violated','not_applicable')),
  137. evaluated_rule_count INTEGER NOT NULL
  138. CHECK (evaluated_rule_count >= 0),
  139. violation_count INTEGER NOT NULL CHECK (
  140. violation_count >= 0
  141. AND violation_count <= evaluated_rule_count
  142. ),
  143. score DOUBLE PRECISION NOT NULL
  144. CHECK (score >= 0 AND score <= 100),
  145. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  146. UNIQUE (run_uid, asset_uid)
  147. );
  148. CREATE INDEX idx_device_quality_asset_scores
  149. ON public.device_quality_asset_scores(
  150. run_uid, score, asset_uid
  151. );
  152. """
  153. )
  154. def downgrade() -> None:
  155. # Quality policies, runs, scores, and violation evidence are retained.
  156. pass