06/12 Add features: 1. Vendor/Contractor assignment 2. Period-over-period comparison 3. Trend Alerts (cron)

This commit is contained in:
2026-06-12 16:27:09 -04:00
parent cf22fe87fe
commit 0fe5954794
12 changed files with 447 additions and 2 deletions
@@ -0,0 +1,50 @@
"""phase26 — vendor/contractor columns on issues
Adds three nullable columns to the issues table:
vendor_name VARCHAR(100) — name of the external contractor or vendor
vendor_contact VARCHAR(200) — phone number or email for the vendor
vendor_notes TEXT — notes about what the vendor is handling
These columns are populated only when a third-party contractor is
assigned to resolve an issue, separate from the internal assigned_to staff user.
"""
import sqlalchemy as sa
from alembic import op
revision = 'phase26_issue_vendor'
down_revision = 'phase25_inspection_gps'
branch_labels = None
depends_on = None
def _col_exists(bind, table: str, column: str) -> bool:
result = bind.execute(sa.text(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS "
"WHERE TABLE_SCHEMA = DATABASE() "
" AND TABLE_NAME = :t "
" AND COLUMN_NAME = :c"
), {'t': table, 'c': column})
return result.scalar() > 0
def upgrade():
bind = op.get_bind()
if not _col_exists(bind, 'issues', 'vendor_name'):
op.add_column('issues',
sa.Column('vendor_name', sa.String(100), nullable=True))
if not _col_exists(bind, 'issues', 'vendor_contact'):
op.add_column('issues',
sa.Column('vendor_contact', sa.String(200), nullable=True))
if not _col_exists(bind, 'issues', 'vendor_notes'):
op.add_column('issues',
sa.Column('vendor_notes', sa.Text, nullable=True))
def downgrade():
op.drop_column('issues', 'vendor_notes')
op.drop_column('issues', 'vendor_contact')
op.drop_column('issues', 'vendor_name')
@@ -0,0 +1,48 @@
"""phase27 — facility score alert tracking table
Creates facility_score_alerts table used by the score-trend cron job to
deduplicate notifications: once an alert fires for a facility, a row is
inserted here. The cron skips the facility if an alert was sent within
the last 24 hours, preventing alert storms on persistent score drops.
"""
import sqlalchemy as sa
from alembic import op
revision = 'phase27_score_alerts'
down_revision = 'phase26_issue_vendor'
branch_labels = None
depends_on = None
def _table_exists(bind, table: str) -> bool:
result = bind.execute(sa.text(
"SELECT COUNT(*) FROM information_schema.tables "
"WHERE table_schema = DATABASE() AND table_name = :t"
), {'t': table})
return result.scalar() > 0
def upgrade():
bind = op.get_bind()
if not _table_exists(bind, 'facility_score_alerts'):
op.execute(sa.text("""
CREATE TABLE facility_score_alerts (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
facility_id INT NOT NULL,
sent_at DATETIME NOT NULL,
current_avg DECIMAL(5,2) NOT NULL,
prior_avg DECIMAL(5,2) NOT NULL,
delta DECIMAL(5,2) NOT NULL,
CONSTRAINT fk_fsa_facility FOREIGN KEY (facility_id)
REFERENCES facilities(id) ON DELETE CASCADE,
INDEX ix_fsa_facility_sent (facility_id, sent_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
"""))
def downgrade():
bind = op.get_bind()
if _table_exists(bind, 'facility_score_alerts'):
op.execute(sa.text('DROP TABLE facility_score_alerts'))