From 24904c0ec742aea7af85e336ab8b3f8e1efb5fc8 Mon Sep 17 00:00:00 2001 From: Nguyen Ngo Date: Fri, 8 May 2026 13:24:07 -0400 Subject: [PATCH] 05/08 Updated code: fixed some issues --- CLAUDE.md | 24 ++++++++++++++++++++++-- app/models/issue.py | 3 +++ app/utils/sla.py | 6 ++++-- 3 files changed, 29 insertions(+), 4 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index b1d6fd4..8ede823 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -2,7 +2,7 @@ > **Audience:** AI assistants and developers working on this codebase. > **Purpose:** Authoritative reference for architecture, conventions, gotchas, and decisions. -> **Last reviewed:** May 2026 (Phase B complete — iPad offline inspection app) +> **Last reviewed:** May 2026 (Phase B complete — iPad offline inspection app; Critical/Notable hardening pass) --- @@ -618,7 +618,9 @@ limiter = Limiter( ``` phase1_projects_roles → phase6_features → phase7_mobile_api → phase8_notification_matrix → phase9_user_full_name → phase10_customer_password_setup → phase11_director_role - → phase12_performance_indexes → phase_b_mobile_local_id ← HEAD + → phase12_performance_indexes → phase_b_mobile_local_id + → phase13_issue_facility → phase14_facility_created_at + → phase15_audit_log_indexes ← HEAD ``` ### phase_b_mobile_local_id @@ -626,6 +628,21 @@ phase1_projects_roles → phase6_features → phase7_mobile_api → phase8_notif Adds `mobile_local_id VARCHAR(64) NULL` + index to both `inspections` and `issues`. Uses `INFORMATION_SCHEMA.COLUMNS` and `INFORMATION_SCHEMA.STATISTICS` existence checks — safe to re-run. +### phase13_issue_facility + +Adds nullable `facility_id` FK column to `issues`; back-fills from `areas.facility_id`; makes `area_id` nullable. +Uses direct `ALTER TABLE` + `INFORMATION_SCHEMA` checks — safe to re-run. + +### phase14_facility_created_at + +Adds nullable `created_at` `DATETIME` column to `facilities`. +Uses direct `ALTER TABLE` + `INFORMATION_SCHEMA` check — safe to re-run. + +### phase15_audit_log_indexes + +Adds individual indexes on `audit_logs.action` and `audit_logs.entity_type`. +Uses `INFORMATION_SCHEMA.STATISTICS` existence checks — safe to re-run. + ### MySQL ENUM Change Protocol (3 steps — always follow) ```sql -- 1. Expand @@ -748,6 +765,9 @@ timeout = 30 | 28 | **`hmac.compare_digest()` for token comparison** | Prevents timing oracle attacks | | 29 | **`get_customer_scope()` uses bulk project query** | Replaces per-assignment loop | | 30 | **CSV exports always call `log_action(ACTION_EXPORT, ...)`** | Data exports are compliance-relevant audit events | +| 31 | **Do NOT add an explicit `Issue.area` relationship** | `Area.issues` declares `backref='area'`, supplying `Issue.area` automatically. A second declaration on `Issue` raises `ConflictingBackreferences` at startup. The dependency is documented here; do not "fix" it by adding an explicit relationship. | +| 32 | **Do not sync an issue when its parent `LocalInspection.syncStatus == "failed"`** | Submitting without `inspection_id` creates orphaned server records; mark issue `"failed"` instead | +| 33 | **f-string fallback strings must use double-quotes inside single-quoted f-strings** | Python 3.11 raises `SyntaxError` on nested same-delimiter quotes; use `"\u2014"` not `'—'` inside `f'...'` | --- diff --git a/app/models/issue.py b/app/models/issue.py index 058319c..065ac5a 100644 --- a/app/models/issue.py +++ b/app/models/issue.py @@ -70,6 +70,9 @@ class Issue(db.Model): mobile_local_id = db.Column(db.String(64), nullable=True, index=True) # idempotency key for mobile submissions # 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 + # with that backref at mapper configuration time (CLAUDE.md rule 31 revised). facility = db.relationship('Facility', foreign_keys=[facility_id], backref='direct_issues') assigned_user = db.relationship('User', foreign_keys=[assigned_to], backref='assigned_issues') verifier = db.relationship('User', foreign_keys=[verified_by], backref='verified_issues') diff --git a/app/utils/sla.py b/app/utils/sla.py index 80e9d6a..5c58f8a 100644 --- a/app/utils/sla.py +++ b/app/utils/sla.py @@ -137,10 +137,12 @@ def send_sla_alerts(): hrs = sla_hours_remaining(issue) deadline = sla_deadline(issue) + facility_name = issue.resolved_facility.name if issue.resolved_facility else "\u2014" + if status == 'breached': title = f'🚨 SLA Breached — Issue #{issue.id} ({issue.severity.title()})' body = ( - f'Issue #{issue.id} at {issue.resolved_facility.name if issue.resolved_facility else '—'} ' + f'Issue #{issue.id} at {facility_name} ' f'has breached its SLA deadline. ' f'Severity: {issue.severity.title()}. ' f'Deadline was {deadline.strftime("%Y-%m-%d %H:%M") if deadline else "N/A"}. ' @@ -149,7 +151,7 @@ def send_sla_alerts(): else: # at_risk title = f'⚠️ SLA At Risk — Issue #{issue.id} ({issue.severity.title()})' body = ( - f'Issue #{issue.id} at {issue.resolved_facility.name if issue.resolved_facility else '—'} ' + f'Issue #{issue.id} at {facility_name} ' f'is approaching its SLA deadline with approximately ' f'{abs(hrs):.1f}h remaining. ' f'Severity: {issue.severity.title()}. '