20260729_350_device_observability.py 3.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. """Add canonical device operational events and evidence relations."""
  2. from alembic import op
  3. revision = "20260729_350"
  4. down_revision = "20260729_340"
  5. branch_labels = None
  6. depends_on = None
  7. def upgrade() -> None:
  8. op.execute(
  9. """
  10. CREATE TABLE public.device_operational_events (
  11. uid UUID PRIMARY KEY,
  12. source_uid UUID NOT NULL
  13. REFERENCES public.ingestion_sources(uid) ON DELETE RESTRICT,
  14. source_entity VARCHAR(300) NOT NULL,
  15. source_code VARCHAR(300) NOT NULL,
  16. event_type VARCHAR(30) NOT NULL
  17. CHECK (
  18. event_type IN (
  19. 'alarm','fault','maintenance','downtime'
  20. )
  21. ),
  22. asset_uid UUID NOT NULL
  23. REFERENCES public.device_assets(uid) ON DELETE RESTRICT,
  24. component_uid UUID
  25. REFERENCES public.device_assets(uid) ON DELETE RESTRICT,
  26. title VARCHAR(500) NOT NULL,
  27. severity VARCHAR(20) NOT NULL
  28. CHECK (severity IN ('info','warning','error','critical')),
  29. status VARCHAR(20) NOT NULL
  30. CHECK (status IN ('observed','resolved')),
  31. occurred_at TIMESTAMPTZ NOT NULL,
  32. ended_at TIMESTAMPTZ
  33. CHECK (ended_at IS NULL OR ended_at >= occurred_at),
  34. evidence_refs JSONB NOT NULL,
  35. content_hash CHAR(64) NOT NULL,
  36. created_by UUID NOT NULL
  37. REFERENCES public.users(id) ON DELETE RESTRICT,
  38. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  39. UNIQUE (source_uid, source_entity, source_code)
  40. );
  41. CREATE INDEX idx_device_operational_event_asset_time
  42. ON public.device_operational_events(
  43. asset_uid, occurred_at DESC, uid
  44. );
  45. CREATE INDEX idx_device_operational_event_worklist
  46. ON public.device_operational_events(
  47. event_type, severity, status, occurred_at DESC
  48. );
  49. CREATE TABLE public.device_evidence_relations (
  50. uid UUID PRIMARY KEY,
  51. from_kind VARCHAR(30) NOT NULL
  52. CHECK (
  53. from_kind IN ('asset','event','quality_issue')
  54. ),
  55. from_uid UUID NOT NULL,
  56. relation_type VARCHAR(40) NOT NULL
  57. CHECK (
  58. relation_type IN (
  59. 'part_of','occurred_on','indicates','triggered',
  60. 'addresses','impacts','evidences','related_to'
  61. )
  62. ),
  63. to_kind VARCHAR(30) NOT NULL
  64. CHECK (
  65. to_kind IN ('asset','event','quality_issue')
  66. ),
  67. to_uid UUID NOT NULL,
  68. evidence_refs JSONB NOT NULL,
  69. source VARCHAR(30) NOT NULL
  70. CHECK (source IN ('imported','manual','derived')),
  71. created_by UUID NOT NULL
  72. REFERENCES public.users(id) ON DELETE RESTRICT,
  73. created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
  74. CHECK (NOT (from_kind = to_kind AND from_uid = to_uid)),
  75. UNIQUE (
  76. from_kind, from_uid, relation_type, to_kind, to_uid
  77. )
  78. );
  79. CREATE INDEX idx_device_evidence_relation_from
  80. ON public.device_evidence_relations(
  81. from_kind, from_uid, created_at, uid
  82. );
  83. CREATE INDEX idx_device_evidence_relation_to
  84. ON public.device_evidence_relations(
  85. to_kind, to_uid, created_at, uid
  86. );
  87. """
  88. )
  89. def downgrade() -> None:
  90. # Operational evidence is retained during application rollback.
  91. pass