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
+5
View File
@@ -79,6 +79,11 @@ class Issue(db.Model):
sla_notified = db.Column(db.String(10), nullable=True, default=None)
mobile_local_id = db.Column(db.String(64), nullable=True, index=True) # idempotency key for mobile submissions
# External vendor / contractor assignment (phase26)
vendor_name = db.Column(db.String(100), nullable=True)
vendor_contact = db.Column(db.String(200), nullable=True) # phone or email
vendor_notes = db.Column(db.Text, nullable=True)
# Relationships
# NOTE: Issue.area is provided by the backref on Area.issues (facility.py).
# Do NOT add a second explicit db.relationship('Area') here — it conflicts
+6
View File
@@ -23,6 +23,10 @@ EVENT_ISSUE_FLAGGED = 'issue_flagged'
EVENT_CUSTOMER_INSPECTION_DONE = 'customer_inspection_completed'
EVENT_CUSTOMER_ISSUE_UPDATED = 'customer_issue_updated'
# Fired by the score-trend cron when a facility's rolling avg drops by
# more than the configured threshold vs. the prior period.
EVENT_SCORE_ALERT = 'score_alert'
ALL_EVENT_TYPES = {
EVENT_ISSUE_ASSIGNED: 'Issue assigned to me',
EVENT_ISSUE_STATUS: 'Issue status changed',
@@ -34,6 +38,8 @@ ALL_EVENT_TYPES = {
# Customer-facing — only relevant for customer role accounts
EVENT_CUSTOMER_INSPECTION_DONE: 'Inspection completed at my facility (portal)',
EVENT_CUSTOMER_ISSUE_UPDATED: 'Issue created or updated at my facility (portal)',
# Score trend alert — admin/director management use
EVENT_SCORE_ALERT: 'Facility score trend alert (significant drop detected)',
}
+24
View File
@@ -0,0 +1,24 @@
from app import db
from app.utils.time_utils import now_eastern
class FacilityScoreAlert(db.Model):
"""Records each score-trend alert sent for a facility.
Used by send_score_alerts() to deduplicate cron notifications:
if an alert row exists for a facility within the last 24 hours,
no new alert is sent even if the score is still below threshold.
"""
__tablename__ = 'facility_score_alerts'
id = db.Column(db.Integer, primary_key=True)
facility_id = db.Column(db.Integer, db.ForeignKey('facilities.id', ondelete='CASCADE'), nullable=False)
sent_at = db.Column(db.DateTime, nullable=False, default=now_eastern)
current_avg = db.Column(db.Numeric(5, 2), nullable=False)
prior_avg = db.Column(db.Numeric(5, 2), nullable=False)
delta = db.Column(db.Numeric(5, 2), nullable=False)
facility = db.relationship('Facility', foreign_keys=[facility_id])
def __repr__(self):
return f'<FacilityScoreAlert facility={self.facility_id} delta={self.delta} sent={self.sent_at}>'