AI Agent Development Guidelines
This document provides coding standards and guidelines for AI agents (including Claude, GitHub Copilot, Cursor, and other AI assistants) when working on the NOAA CAP Emergency Alert System codebase.
๐ฏ Core Principles
- Safety First: Never commit secrets, API keys, or sensitive data
- Preserve Existing Patterns: Follow the established code style and architecture
- Test Before Commit: Always verify changes work in Docker before committing
- Focused Changes: Keep fixes targeted to the specific issue
- Document Changes: Update relevant documentation when adding features
- Check Bug Screenshots: When discussing bugs, always check the
/bugsdirectory first for screenshots - Follow Versioning: Bug fixes increment by 0.0.+1, feature upgrades increment by 0.+1.0
- File Naming Convention: When superseding files, rename the old one with
oldsuffix, NEVER usenewsuffix for replacement files - Repository Organization: Every file must live in an appropriate directory unless necessary to be in the root (e.g.,
requirements.txt,Dockerfile,README.md,LICENSE, etc.). Documentation, summaries, and development artifacts belong in thedocs/directory structure.
๐ Bug Tracking & Screenshots
When discussing or investigating bugs:
- Check
/bugsDirectory First โ Before starting any bug investigation, check the/bugsdirectory for screenshots and other evidence - Screenshots Over Text โ Since AI assistants can't receive images directly in chat, users will place bug screenshots in
/bugs - Name Descriptively โ Screenshot filenames should indicate the issue (e.g.,
adminspacingissue.jpeg,darkmodecontrast_bug.png) - Document Fixes โ When fixing a bug shown in a screenshot, reference the screenshot filename in commit messages
- Clean Up After โ Once a bug is fixed and verified, move the screenshot to
/bugs/resolvedor delete it
๐งญ Documentation & UX Standards
- Link Accuracy Matters โ Reference primary sources (e.g., FCC consent decrees via
docs.fcc.gov) instead of news summaries. Broken or redirected links must be updated immediately. - Theory of Operation Is Canonical โ Whenever you touch ingestion, SAME generation, or verification logic, review and update `docs/architecture/THEORY_OF_OPERATION.md` so diagrams, timelines, and checklists match the code.
- Surface Docs In-App โ Front-end templates (
templates/) should link to the corresponding Markdown resources indocs/. Keep/about,/help,/terms, and/privacysynchronized with repository guidance. - Documentation Updates Required โ When adding new features or changing workflows, update:
templates/help.htmlโ User-facing help documentationtemplates/about.htmlโ System overview and feature descriptions- Relevant Markdown files in
docs/directory - This ensures users always have current information about system capabilities
- Brand Consistency โ Use
static/img/eas-system-wordmark.svgfor hero sections, headers, and major UI cards when expanding documentation pages. The logo must remain accessible (includealttext). - Mermaid-Friendly Markdown โ GitHub-flavoured Mermaid diagrams are welcome in repository docs. Keep them accurate by naming real modules, packages, and endpoints.
๐จ MANDATORY: Frontend UI for Every Backend Feature
CRITICAL RULE: Every backend feature MUST have a corresponding frontend user interface. Backend-only features are UNACCEPTABLE.When implementing ANY new feature:
- Backend + Frontend Together
- โ
CORRECT: Create API endpoint
/api/gpio/activateAND UI page/gpio_control - โ WRONG: Create API endpoint without UI (user cannot access it!)
- Navigation Access Required
- Every new page must be accessible from the navigation menu
- Add appropriate menu items in
templates/base.html - Consider: Which dropdown menu does this belong in? (Operations, Analytics, Admin, Settings)
- If creating a new major feature, create a new navigation section
- Documentation Requirements
- Document the UI access path: "Navigate to Operations โ GPIO Control"
- Include screenshots showing how to access the feature
- Update
docs/NEW_FEATURES.mdor relevant guides - Add inline help text or tooltips in the UI
- Form Input Standards
- Binary choices (true/false, yes/no, enabled/disabled) MUST use:
- Dropdown menus with fixed options, OR
- Radio button groups, OR
- Toggle switches
- โ NEVER use free-text inputs for binary choices - users will make capitalization errors
- โ Example (Dropdown):
<select class="form-select" name="enabled">
<option value="true">Enabled</option>
<option value="false">Disabled</option>
</select>
- โ Example (Radio):
<div class="form-check">
<input class="form-check-input" type="radio" name="enabled" value="true" id="enabled-yes">
<label class="form-check-label" for="enabled-yes">Enabled</label>
</div>
<div class="form-check">
<input class="form-check-input" type="radio" name="enabled" value="false" id="enabled-no">
<label class="form-check-label" for="enabled-no">Disabled</label>
</div>
- โ Example (Toggle Switch):
<div class="form-check form-switch">
<input class="form-check-input" type="checkbox" role="switch" id="enabledSwitch" name="enabled">
<label class="form-check-label" for="enabledSwitch">Enable Feature</label>
</div>
- Pre-Commit Checklist for New Features
- [ ] Backend API endpoints created
- [ ] Frontend UI page created (HTML template)
- [ ] Navigation menu updated to access the page
- [ ] Forms use proper input types (no text inputs for binary choices)
- [ ] Documentation updated with access instructions
- [ ] Feature tested end-to-end through the UI
- [ ] Error handling displays user-friendly messages
- Examples of Complete Features
- โ
RBAC Management: Backend routes in
/security/roles+ Frontend UI at/admin/rbac+ Navigation in Admin menu - โ
Audit Logs: Backend routes in
/security/audit-logs+ Frontend UI at/admin/audit-logs+ Export button + Filtering - โ
GPIO Control: Backend API
/api/gpio/*+ Frontend UI/gpio_control+ Statistics page/admin/gpio/statistics
- What Counts as "Accessible"
- User can find and use the feature without reading code
- Feature is discoverable through navigation or obvious links
- No need to manually type URLs or use API tools
- All CRUD operations (Create, Read, Update, Delete) have UI buttons/forms
Modularity & File Size
- Prefer small, focused modules โ Aim to keep Python modules under ~400 lines and HTML templates under ~300 lines.
- Refactor before things get unwieldy โ When adding more than one new class or multiple functions to a module already above 350 lines, create or use a sibling module/package instead of expanding the existing file.
- Extract repeated markup โ Move duplicated template fragments into
templates/components/and use Flask blueprints or helper modules to share behavior. - Stay consistent with existing structure โ Place new Python packages within
appcore/orapputils/as appropriate, and keep front-end assets organized understatic/andtemplates/using the same layout patterns as current files. - File Naming Convention โ When a file supersedes a previous one:
- CORRECT: Rename old file to
filename_old.ext, new file becomesfilename.ext - WRONG: Never use
filename_new.extas the replacement - this creates confusion - Example:
navbar.html(active) supersedesnavbar_old.html(deprecated) - Pre-commit self-check โ Confirm any touched file still meets these size expectations or has been split appropriately before finalizing changes.
๐ Code Style Standards
Python Code Style
- Indentation: Use 4 spaces (never tabs) for all Python code
- Line Length: Keep lines under 100 characters where practical
- Naming Conventions:
- Functions and variables:
snake_case - Classes:
PascalCase - Constants:
UPPERSNAKECASE - Private methods:
leadingunderscore
Good
def calculatealertintersections(alertid, boundarytype="county"):
"""Calculate intersections for a specific alert."""
passBad
def calculateAlertIntersections(alertId, boundaryType="county"): # Wrong naming
pass # Wrong indentation
Logging Standards
- Always use the existing logger - Never create new logger instances
- Log Levels:
logger.debug()- Detailed diagnostic informationlogger.info()- General informational messageslogger.warning()- Warning messages for potentially harmful situationslogger.error()- Error messages for serious problemslogger.critical()- Critical failures
Good - Uses existing logger
logger.info(f"Processing alert {alert_id}")
logger.error(f"Failed to connect to database: {str(e)}")Bad - Creates new logger
import logging
my_logger = logging.getLogger(name) # Don't do this!
Error Handling
- Always catch specific exceptions - Never use bare
except: - Include context in error messages - Help with debugging
- Roll back database transactions on errors
Good
try:
alert = CAPAlert.query.getor404(alert_id)
# ... do work ...
db.session.commit()
except OperationalError as e:
db.session.rollback()
logger.error(f"Database error processing alert {alert_id}: {str(e)}")
return jsonify({'error': 'Database connection failed'}), 500
except Exception as e:
db.session.rollback()
logger.error(f"Unexpected error in process_alert: {str(e)}")
return jsonify({'error': str(e)}), 500Bad
try:
# ... code ...
except: # Too broad!
pass # Silently ignoring errors!
๐๏ธ Database Guidelines
SQLAlchemy Patterns
- Use the session properly - Always commit or rollback
- Query efficiently - Use
.filter()for conditions,.all()or.first()appropriately - Handle geometry - Remember that
geomfields are PostGIS types
Good
try:
alert = CAPAlert.query.filterby(identifier=capid).first()
if alert:
alert.status = 'expired'
db.session.commit()
logger.info(f"Marked alert {cap_id} as expired")
else:
logger.warning(f"Alert {cap_id} not found")
except Exception as e:
db.session.rollback()
logger.error(f"Error marking alert as expired: {str(e)}")
PostGIS Spatial Queries
- Use PostGIS functions -
STIntersects,STArea,ST_GeomFromGeoJSON - Check for NULL geometry - Always verify
alert.geom is not None - Handle spatial queries carefully - They can be slow on large datasets
Good - Checks for geometry and uses PostGIS functions
if alert.geom and boundary.geom:
intersection = db.session.query(
func.ST_Intersects(alert.geom, boundary.geom).label('intersects'),
func.STArea(func.STIntersection(alert.geom, boundary.geom)).label('area')
).first()
๐จ Frontend Guidelines
Template Standards
- Extend base.html - All templates should use
{% extends "base.html" %} - Use theme variables - Reference CSS variables:
var(--primary-color),var(--text-color),var(--bg-color) - Support all themes - EAS Station has multiple built-in themes (Cosmo, Dark, Coffee, Spring, and color-based themes)
- Test in multiple themes - Always test in both light (Cosmo) and dark themes at minimum
- Be responsive - Use Bootstrap 5 grid classes for mobile support
- Theme Variable Categories:
- Colors:
--primary-color,--secondary-color,--accent-color - Status:
--success-color,--danger-color,--warning-color,--info-color - Text:
--text-color,--text-secondary,--text-muted - Backgrounds:
--bg-color,--surface-color,--bg-card - Borders:
--border-color,--shadow-color
{% extends "base.html" %}{% block title %}My Feature - EAS Station{% endblock %}
{% block extra_css %}
<style>
.my-custom-class {
background-color: var(--bg-color);
color: var(--text-color);
border: 1px solid var(--border-color);
}
/ All themes automatically inherit CSS variables /
/ No need for theme-specific overrides unless absolutely necessary /
</style>
{% endblock %}
{% block content %}
<div class="container-fluid mt-4">
<h1>My Feature</h1>
<!-- Content here -->
</div>
{% endblock %}
JavaScript Patterns
- Use existing global functions -
showToast(),toggleTheme(),setTheme(),showThemeSelector(),exportToExcel() - Avoid jQuery - Use vanilla JavaScript and modern ES6+ features
- Handle errors gracefully - Show user-friendly messages using toast notifications
- Theme System Functions:
setTheme(themeName)- Switch to a specific themetoggleTheme()- Toggle between light and dark modesgetCurrentTheme()- Get current active theme namegetCurrentThemeMode()- Get current theme mode ('light' or 'dark')getAvailableThemes()- Get list of all available themesshowThemeSelector()- Display theme selection modal with import/exportexportTheme(themeName)- Export theme as JSONdownloadTheme(themeName)- Download theme fileimportTheme(jsonString)- Import custom theme from JSONdeleteTheme(themeName)- Remove custom theme (built-in themes cannot be deleted)
Template Structure & Page Elements
CRITICAL: Know which files are actually being used vs orphaned duplicates.Active Template Files
| Element | Active File | Lines | Status |
|---|---|---|---|
| Base Template | templates/base.html |
163 | โ All pages extend this |
| Navbar | templates/components/navbar.html |
420+ | โ Included in base.html (renamed from navbar_new.html) |
| Footer | Inline in templates/base.html |
103-144 | โ Inline in base template |
| System Banner | Inline in templates/base.html |
72-81 | โ Inline in base template |
| Flash Messages | Inline in templates/base.html |
84-95 | โ Inline in base template |
Deprecated Files (DO NOT EDIT)
| File | Status | Action Required |
|---|---|---|
templates/base_new.html |
โ Not used anywhere | Can be deleted |
templates/components/navbar_old.html |
โ Superseded by navbar.html | Keep as reference, do not edit |
components/navbar.html |
โ Wrong directory | Should be deleted |
components/footer.html |
โ Was deleted (not included) | Already removed |
components/page_header.html |
โ ๏ธ Macro component, wrong location | Move to templates/components/ if used |
When Making Changes to Page Elements
Changing the Navbar:- โ
Edit:
templates/components/navbar.html - โ Don't edit:
templates/components/navbar_old.html(deprecated) - โ Don't edit:
components/navbar.html(wrong location) - Features: Bootstrap 5 navbar, dropdowns, health indicator, theme selector (palette icon), quick theme toggle
- โ
Edit:
templates/base.html(lines 103-144) - โ Don't edit:
components/footer.html(deleted - was orphaned)
- โ
Edit:
templates/base.html(lines 72-81)
- โ
Edit:
templates/base.html(lines 84-95)
- โ
Always extend
base.html - โ
Use
{% block content %}for page content - โ
Add navigation link to
templates/components/navbar.html - โ Never extend
base_new.html(orphaned)
Quick Verification
Before editing any template file:
- Search for usage:
grep -r "include.*filename" templates/ - Check extends:
grep -r "extends.*filename" templates/ - Verify in Python:
grep -r "render_template.*filename" . - Consult documentation: See docs/frontend/TEMPLATE_STRUCTURE.md
๐จ Theme System Architecture
Overview
EAS Station features a comprehensive theme system with 11 built-in themes and support for custom theme import/export.
Built-in Themes
| Theme | Mode | Description | Primary Use Case |
|---|---|---|---|
| Cosmo | Light | Default vibrant blue/purple theme | General use, professional |
| Dark | Dark | Enhanced dark mode with high contrast | Night use, reduced eye strain |
| Coffee | Dark | Warm coffee-inspired browns | Cozy, warm aesthetic |
| Spring | Light | Fresh green nature-inspired | Bright, energetic feel |
| Red | Light | Bold red accent theme | Alert-focused, high energy |
| Green | Light | Nature-inspired green | Calm, environmental |
| Blue | Light | Ocean blue theme | Professional, trustworthy |
| Purple | Light | Royal purple theme | Creative, elegant |
| Pink | Light | Soft pink theme | Friendly, approachable |
| Orange | Light | Energetic orange theme | Warm, enthusiastic |
| Yellow | Light | Bright yellow theme | Cheerful, optimistic |
Theme System Files
Core Files:static/js/core/theme.js- Theme management, switching, import/exportstatic/css/base.css- All theme color definitions (CSS variables)templates/base.html- Theme initialization (data-theme="cosmo")templates/components/navbar.html- Theme selector UI (palette icon + quick toggle)
CSS Variable Structure
Every theme defines these CSS variables:
Colors:--primary-color,--primary-soft- Main brand colors--secondary-color,--secondary-soft- Secondary brand colors--accent-color- Accent/highlight color
--success-color- Success states (green)--danger-color- Error/danger states (red)--warning-color- Warning states (yellow/orange)--info-color- Information states (blue)--critical-color- Critical alerts (bright red/pink)
--bg-color- Page background--surface-color- Card/panel background--bg-card- Card background (same as surface)--light-color- Light background shade--dark-color- Dark background shade
--text-color- Primary text--text-secondary- Secondary/muted text--text-muted- Very subtle text
--border-color- Border colors--shadow-color- Box shadow colors--radius-sm/md/lg- Border radius values--spacing-xs/sm/md/lg/xl- Spacing scale
Adding a New Theme
- Add theme definition to
static/js/core/theme.js:
const THEMES = {
// ...existing themes...
'mytheme': {
name: 'My Theme',
mode: 'light', // or 'dark'
description: 'Description of my theme',
builtin: true
}
};
- Add CSS variables to
static/css/base.css:
[data-theme="mytheme"] {
--primary-color: #your-color;
--secondary-color: #your-color;
/ ...all other variables... /
}
- Test in multiple UI contexts:
- Cards and panels
- Buttons and forms
- Navigation bar gradient
- Status indicators
- Text readability (all three levels)
Theme Import/Export
Users can create custom themes and share them:
Export:window.downloadTheme('cosmo'); // Downloads theme-cosmo.json
Import:
- Users click theme selector (palette icon in navbar)
- Upload JSON file in modal
- Custom theme appears in selector
- Stored in localStorage
{
"name": "mytheme",
"displayName": "My Theme",
"mode": "light",
"description": "My custom theme",
"version": "1.0",
"exported": "2025-01-14T13:00:00.000Z"
}
Theme Persistence
- Current theme stored in
localStorage.setItem('theme', themeName) - Custom themes stored in
localStorage.setItem('customThemes', jsonString) - Automatically loaded on page load
- Survives browser sessions
Navbar Theme Controls
Two buttons in navbar:- Palette Icon (
<i class="fas fa-palette">) - Opens theme selector modal
- Grid of all themes with previews
- Import/Export functionality
- Delete custom themes
- Sun/Moon Icon (
<i class="fas fa-sun/moon">) - Quick toggle
- Toggles between light and dark modes
- Switches between Cosmo (light) and Dark (dark)
Dark Mode Best Practices
When designing for dark mode themes:
- Higher contrast: Text should be brighter (#f8f9fc not #f5f6fa)
- Softer shadows: Use
rgba(0,0,0,0.5)instead ofrgba(0,0,0,0.4) - Vibrant accents: Status colors should be 15-20% brighter than light mode
- Deeper backgrounds: Multiple levels (#12182a, #1e2538, #2d3548)
- Muted borders: Borders should be subtle but visible (#343d54)
๐ Security Guidelines
Critical Security Rules
- NEVER commit
.envfile - It contains secrets - NEVER hardcode credentials - Always use environment variables
- NEVER expose debug endpoints - Remove before production
- ALWAYS validate user input - Especially file uploads
- ALWAYS use parameterized queries - Prevent SQL injection
Environment Variables
Good - Uses environment variable
SECRETKEY = os.environ.get('SECRETKEY')
if not SECRETKEY and os.environ.get('FLASKENV') == 'production':
raise ValueError("SECRET_KEY required in production")Bad - Hardcoded secret
SECRET_KEY = "my-secret-key-12345" # NEVER DO THIS!
File Uploads
Good - Validates file type and content
if not file.filename.lower().endswith('.geojson'):
return jsonify({'error': 'Only GeoJSON files allowed'}), 400try:
geojson_data = json.loads(file.read().decode('utf-8'))
# Validate structure...
except json.JSONDecodeError:
return jsonify({'error': 'Invalid JSON format'}), 400
๐ณ Docker & Deployment
Testing Changes
Before committing, always test in Docker:
Rebuild and test
sudo docker compose build
sudo docker compose up -d
sudo docker compose logs -f appCheck for errors
sudo docker compose ps
curl http://localhost:5000/health
Environment Configuration
- Use
.env.exampleas template - Never commit.env - Document new variables - Add to both
.env.exampleand README - Provide sensible defaults - Make local development easy
Persistent Environment System
CRITICAL CONCEPT: EAS Station uses a persistent volume for configuration that survives container rebuilds, Git pull & redeploy operations, and version upgrades.How It Works
- Persistent Volume: Docker volume
app-configis mounted at/app-config/inside the container - Persistent Config File: Configuration is stored in
/app-config/.env(not/app/.env) - Setup Wizard: First-time deployments run the Setup Wizard at
http://localhost/setupwhich creates and populates/app-config/.env - Web UI Management: Users configure settings via the Settings โ Environment page, which updates
/app-config/.env - Container Startup:
docker-entrypoint.shchecks for/app-config/.envand loads it into the application environment
Why This Matters
Without persistent environment:- โ Portainer "Pull and redeploy" would wipe all configuration
- โ Users would need to reconfigure after every Git update
- โ Version upgrades would reset all settings to defaults
- โ Manual editing of Docker Compose files required for config changes
- โ Configuration survives "Pull and redeploy" operations
- โ Git updates don't affect user configuration
- โ Settings persist across version upgrades
- โ Users configure via web UI (Settings โ Environment)
- โ Setup Wizard only runs once on first deployment
Entrypoint Initialization Logic
The docker-entrypoint.sh script handles initialization:
If CONFIG_PATH is set (default: /app-config/.env)
if [ -n "$CONFIG_PATH" ]; then
# Create persistent config directory if needed
mkdir -p "$(dirname "$CONFIG_PATH")"
# If file doesn't exist or is empty, initialize it
if [ ! -f "$CONFIG_PATH" ] || [ file is empty ]; then
# Transfer environment variables from stack.env to persistent file
# This happens ONCE on first deploy
echo "SECRETKEY=${SECRETKEY:-}" >> "$CONFIG_PATH"
echo "POSTGRESHOST=${POSTGRESHOST:-alerts-db}" >> "$CONFIG_PATH"
# ... all other variables ...
fi
# Load the persistent config into environment
export $(cat "$CONFIG_PATH" | grep -v '^#' | xargs)
fi
Configuration Flow
First Deployment (Portainer Git Deploy):- Stack deployed with
stack.envenvironment variables - Container starts,
docker-entrypoint.shruns - Creates
/app-config/.envand copies values fromstack.env - User visits
http://localhost/setupto complete configuration - Setup Wizard writes final config to
/app-config/.env
- Portainer pulls latest code from Git
- Rebuilds containers with updated code
docker-entrypoint.shfinds existing/app-config/.env- Loads configuration from persistent file
- User configuration is preserved automatically
- User navigates to Settings โ Environment
- Changes a setting (e.g., poll interval from 180 to 300 seconds)
- Backend updates
/app-config/.envfile - Restart container to apply:
docker compose restart app
Variable Precedence
Priority order (highest to lowest):- Environment variables set in
docker-compose.ymlenvironment:section - Variables loaded from
/app-config/.env(persistent config) - Variables from
stack.envfile (only used on first deploy) - Hardcoded defaults in Python code
docker-compose.yml environment section
environment:
POSTGRESHOST: ${POSTGRESHOST:-host.docker.internal} # From stack.env on first deployFirst deploy: POSTGRES_HOST=host.docker.internal is written to /app-config/.env
Pull & redeploy: /app-config/.env still has POSTGRES_HOST=host.docker.internal
Configuration is preserved!
User changes it via web UI to external-db.example.com
/app-config/.env now has: POSTGRES_HOST=external-db.example.com
Restart applies the change
Auto-Detected vs User-Configured Variables
Some variables are auto-detected at runtime and should NOT be written to the persistent config if not explicitly set:
Auto-Detected Variables:GIT_COMMIT- Auto-detected from.git/HEADand.git/refs/at runtimeHOSTNAME- Auto-detected by system- Build-time values that shouldn't be frozen in config
SECRET_KEY- Must be generated and persistedPOSTGRES_HOST- User's database serverEASBROADCASTENABLED- User's feature preferences- All settings in Settings โ Environment page
โ
CORRECT - Only write if explicitly set
$([ -n "${GITCOMMIT:-}" ] && echo "GITCOMMIT=${GITCOMMIT}" || echo "# GITCOMMIT not set - will auto-detect")โ WRONG - Writes "unknown" and prevents auto-detection
GITCOMMIT=${GITCOMMIT:-unknown}
Adding New Environment Variables
When adding a new environment variable to the system, you MUST update these files:
.env.example- Add the variable with documentation and a default valuestack.env- Add the variable with the default value for Docker deploymentsdocker-entrypoint.sh- Add the variable to the initialization section if it needs to be available during container startupwebapp/admin/environment.py- REQUIRED: Add the variable to the appropriate category inENV_CATEGORIESto make it accessible in the web UI settings page. This is how users configure the system!apputils/setupwizard.py- If the variable is part of initial setup, add it to the appropriate wizard section with matching validation
/app-config/.env and managed through the web UI. ALL user-configurable environment variables MUST be added to webapp/admin/environment.py, otherwise users cannot change them without editing Docker Compose files.Environment Variable Validation
CRITICAL: Validation rules MUST match betweenwebapp/admin/environment.py and apputils/setupwizard.py. Users should not be able to enter invalid values in either interface.When adding validation:
- SECRETKEY: Use
validatesecretkeyin setup wizard (min 32 chars),minlength: 32, pattern: ^[A-Za-z0-9]{32,}$in environment.py - Port numbers: Use
validateportin setup wizard,min: 1, max: 65535in environment.py - IP addresses: Use
validateipv4in setup wizard,pattern: IPv4 regexin environment.py - GPIO pins: Use
validategpio_pinin setup wizard,min: 2, max: 27in environment.py - Station IDs: Use
validatestation_idin setup wizard,pattern: ^[A-Z0-9/]{1,8}$in environment.py - Originator codes: Use dropdown in both (4 options: WXR, EAS, PEP, CIV)
Variable Types in environment.py
text- Text input fieldnumber- Numeric input with optional min/max/steppassword- Password field with masking (setsensitive: True)select- Dropdown with predefined optionstextarea- Multi-line text input
boolean type. Always use select with options: ['false', 'true'] for yes/no or true/false values. This prevents end users from inputting invalid responses and breaking functionality.โ WRONG - Don't use boolean type
{
'key': 'SOME_FLAG',
'type': 'boolean',
'default': 'false',
}โ
CORRECT - Use select with explicit options
{
'key': 'SOME_FLAG',
'type': 'select',
'options': ['false', 'true'],
'default': 'false',
}
Input Validation Best Practices
ALWAYS add validation attributes to prevent invalid input.Important Principle: If a field has only a fixed set of valid values (e.g., 4 originator codes, specific status codes), use aselect dropdown instead of a text field with regex validation. This provides the best user experience and prevents any possibility of invalid input.Port Numbers:
{
'key': 'SOME_PORT',
'label': 'Port',
'type': 'number',
'default': '8080',
'min': 1, # Ports start at 1
'max': 65535, # Maximum valid port
}
IP Addresses:
{
'key': 'SOME_IP',
'label': 'IP Address',
'type': 'text',
'pattern': '^((25[0-5]|(2[0-4]|1\\d|[1-9]|)\\d)\\.?\\b){4}$',
'title': 'Must be a valid IPv4 address (e.g., 192.168.1.100)',
'placeholder': '192.168.1.100',
}
GPIO Pins (Raspberry Pi BCM):
{
'key': 'GPIO_PIN',
'label': 'GPIO Pin',
'type': 'number',
'min': 2, # Valid GPIO range
'max': 27, # Standard BCM numbering
'placeholder': 'e.g., 17',
}
Conditional Field Visibility
Use the category attribute to group fields that should be disabled when their parent feature is disabled.
- Enable/Disable Field - A select dropdown or text field that controls enablement
- Dependent Fields - Fields with
categoryattribute linking them to the parent
Parent enable/disable field
{
'key': 'EASBROADCASTENABLED',
'label': 'Enable EAS Broadcasting',
'type': 'select',
'options': ['false', 'true'],
'default': 'false',
'description': 'Enable SAME/EAS audio generation',
},Dependent fields (will be grayed out when EASBROADCASTENABLED is false)
{
'key': 'EASSTATIONID',
'label': 'Station ID',
'type': 'text',
'category': 'eas_enabled', # Links to parent feature
},
Category Naming Convention:
eas_enabled- EAS broadcast featuregpio_enabled- GPIO control featureled_enabled- LED display featurevfd_enabled- VFD display featureemail- Email notification sub-fieldsazure_openai- Azure OpenAI TTS sub-fields
Variable Categories
Variables are organized into categories in webapp/admin/environment.py:
- core - Essential application configuration (SECRETKEY, LOGLEVEL, etc.)
- database - PostgreSQL connection settings
- polling - CAP feed polling configuration
- location - Default location and coverage area
- eas - EAS broadcast settings
- gpio - GPIO relay control
- tts - Text-to-speech providers
- led - LED display configuration
- vfd - VFD display configuration
- notifications - Email and SMS alerts
- performance - Caching and worker settings
- docker - Container and infrastructure settings
- icecast - Icecast streaming server configuration
Choose the most appropriate category for your variable, or create a new one if needed.
Docker Compose Files - CRITICAL
IMPORTANT: When editing Docker Compose files, you MUST update BOTH files:docker-compose.yml- Main compose filedocker-compose.embedded-db.yml- Embedded database variant
These files have parallel structure but different configurations (external vs embedded database). Any changes to service definitions, environment variables, ports, volumes, etc. must be applied to BOTH files to maintain consistency.
๐ Documentation Standards
Code Documentation
def calculatecoveragepercentages(alert_id, intersections):
"""
Calculate actual coverage percentages for each boundary type. Args:
alert_id (int): The CAP alert ID
intersections (list): List of (intersection, boundary) tuples
Returns:
dict: Coverage data by boundary type with percentages and areas
Example:
>>> coverage = calculatecoveragepercentages(123, intersections)
>>> print(coverage['county']['coverage_percentage'])
45.2
"""
# Implementation...
When to Update Documentation
- README.md - Add new features, API endpoints, configuration options
- AGENTS.md - New patterns, standards, or guidelines
- Inline comments - Complex logic that isn't obvious
- Docstrings - All public functions and classes
Documentation Location Policy
CRITICAL: All documentation files MUST be located in the/docs folder, NOT in the repository root.Directory Structure:
/docs/development/- Development guidelines, coding standards, agent instructions/docs/guides/- User guides, setup instructions, how-to documents/docs/reference/- Technical reference materials, function trees, known bugs/docs/security/- Security analysis, implementation checklists, audit reports/docs/architecture/- System architecture, theory of operation/docs/process/- Contributing guidelines, PR templates, issue templates/docs/frontend/- UI/UX documentation, component libraries/docs/hardware/- Hardware integration, GPIO, SDR setup/docs/audio/- Audio system documentation/docs/compliance/- FCC compliance, regulatory documentation/docs/deployment/- Deployment guides, Docker, infrastructure/docs/roadmap/- Project roadmap, feature planning/docs/runbooks/- Operational procedures, troubleshooting
README.md- Project overview and quick start (GitHub standard).env.example- Environment variable templatedocker-compose.yml- Docker composition filesLICENSE- License file
- Choose the appropriate subdirectory based on the content type
- Use descriptive filenames in UPPERCASEWITHUNDERSCORES.md format
- Update relevant index files (like
docs/INDEX.md) - Link from related documents to ensure discoverability
- NEVER create .md files in the root unless they are README.md
- Use
git mvto preserve file history - Update all references in other markdown files
- Update navigation links in index files
- Test all links to ensure they're not broken
- โ
docs/guides/SETUP_WIZARD.md- Setup guide - โ
docs/reference/KNOWN_BUGS.md- Bug list - โ
docs/security/SECURITYANALYSISINDEX.md- Security docs - โ
SETUP_GUIDE.md(in root) - Should be in docs/guides/ - โ
BUG_LIST.md(in root) - Should be in docs/reference/
๐ง Common Patterns
Flask Route Pattern
@app.route('/api/my_endpoint', methods=['POST'])
def my_endpoint():
"""Brief description of what this endpoint does."""
try:
# 1. Validate input
data = request.get_json()
if not data or 'required_field' not in data:
return jsonify({'error': 'Missing required field'}), 400 # 2. Do the work
result = performoperation(data['requiredfield'])
# 3. Log success
logger.info(f"Successfully processed {data['required_field']}")
# 4. Return response
return jsonify({
'success': True,
'result': result
})
except SpecificException as e:
logger.error(f"Specific error in my_endpoint: {str(e)}")
return jsonify({'error': 'Specific error occurred'}), 400
except Exception as e:
logger.error(f"Unexpected error in my_endpoint: {str(e)}")
return jsonify({'error': 'Internal server error'}), 500
Database Query Pattern
try:
# Query with joins if needed
results = db.session.query(CAPAlert, Boundary)\
.join(Intersection, CAPAlert.id == Intersection.capalertid)\
.join(Boundary, Boundary.id == Intersection.boundary_id)\
.filter(CAPAlert.status == 'active')\
.all() # Process results
for alert, boundary in results:
# ... do work ...
# Commit if making changes
db.session.commit()
except OperationalError as e:
db.session.rollback()
logger.error(f"Database error: {str(e)}")
except Exception as e:
db.session.rollback()
logger.error(f"Error processing query: {str(e)}")
๐ซ Anti-Patterns to Avoid
Don't Do These
โ Don't use bare excepts
try:
risky_operation()
except:
passโ Don't create new loggers
import logging
logger = logging.getLogger(name)โ Don't hardcode paths
with open('/app/data/file.txt') as f:
# Use environment variables or config insteadโ Don't commit commented-out code
old_function() # Delete instead of commenting
def unused_function():
pass
โ Don't ignore return values
db.session.commit() # What if it fails?โ Don't use mutable default arguments
def processalerts(alertids=[]): # Bug! Use None instead
pass
Do These Instead
โ
Catch specific exceptions
try:
risky_operation()
except ValueError as e:
logger.error(f"Invalid value: {str(e)}")โ
Use existing logger
logger.info("Using the pre-configured logger")โ
Use environment variables or config
datadir = os.environ.get('DATADIR', '/app/data')
with open(os.path.join(data_dir, 'file.txt')) as f:
passโ
Remove dead code completely
Code is in git history if you need it
โ
Handle commit errors
try:
db.session.commit()
except Exception as e:
db.session.rollback()
logger.error(f"Commit failed: {str(e)}")โ
Use None for mutable defaults
def processalerts(alertids=None):
if alert_ids is None:
alert_ids = []
๐งช Testing Guidelines
Manual Testing Checklist
Before committing changes:
- [ ] Code passes Python syntax check:
python3 -m py_compile app.py - [ ] Docker build succeeds:
sudo docker compose build - [ ] Application starts without errors:
sudo docker compose up -d - [ ] Health check passes:
curl http://localhost:5000/health - [ ] Logs show no errors:
sudo docker compose logs -f app - [ ] UI tested in browser (light and dark mode)
- [ ] Database queries work as expected
Edge Cases to Consider
- Empty/null data - What if no alerts exist?
- Invalid input - What if user provides bad data?
- Database failures - What if connection is lost?
- Large datasets - Will this scale?
- Concurrent access - What if multiple users access simultaneously?
๐ฆ Dependency Management
Adding New Dependencies
CRITICAL: When adding ANY new dependency to the project (Python libraries, system packages, Docker images, or infrastructure programs), you MUST update the documentation.For Python Dependencies:
- Add to
requirements.txt- Include version pin - Test in Docker - Rebuild and verify
- Update attribution - Add to
docs/reference/dependency_attribution.md - Document if needed - Update README if it affects users
- Keep minimal - Only add if truly necessary
requirements.txt
flask==2.3.3
requests==2.31.0
new-library==1.2.3 # Add with version
pyshp==2.3.1 # Shapefile reader for converting boundary files to GeoJSON
Current Python Dependencies:
- pyshp 2.3.1 - Shapefile reader library for ESRI Shapefile (.shp) processing
- Used for converting TIGER/Line shapefiles to GeoJSON format
- Enables web-based shapefile upload and conversion in admin interface
- Required for
/admin/uploadshapefileand/admin/listshapefilesendpoints - Lightweight alternative to GDAL/Fiona (no complex system dependencies)
For System Packages and Infrastructure Components:
When adding system packages (apt/yum), Docker images, or infrastructure programs (nginx, certbot, redis, etc.):
- Update Dockerfile or docker-compose.yml - Add the package/service
- Update
docs/reference/dependency_attribution.md- Add to "System Package Dependencies" section
- Package name and version
- Purpose and what it's used for
- License information
- Whether it's required or optional
- Update
docs/reference/SYSTEM_DEPENDENCIES.mdif it exists - Create deployment documentation - Explain how it works and why it's needed
- Attribution is mandatory - All software used in deployment must be properly credited
Infrastructure Components
Component
Version
Purpose
License
nginx
1.25+ (Alpine)
Reverse proxy for HTTPS termination and Let's Encrypt ACME support
BSD-2-Clause
certbot
2.0+
Automated Let's Encrypt SSL certificate management and renewal
Apache-2.0
Why this matters:
- Open source attribution is a legal requirement for many licenses
- Users need to understand what software is running in their deployment
- Proper documentation helps with security audits and compliance
- Future maintainers need to know what dependencies exist and why
๐ Git Workflow
Versioning Convention
CRITICAL: Follow semantic versioning for all releases:- Bug Fixes: Increment patch version by
0.0.+1 - Example:
2.3.12โ2.3.13 - Includes: Bug fixes, security patches, minor corrections
- No new features or breaking changes
- Feature Upgrades: Increment minor version by
0.+1.0 - Example:
2.3.12โ2.4.0 - Includes: New features, enhancements, non-breaking changes
- Reset patch version to 0
- Major Releases: Increment major version by
+1.0.0(rare) - Example:
2.3.12โ3.0.0 - Includes: Breaking changes, major architecture changes
- Reset minor and patch versions to 0
/VERSION (single line, format: MAJOR.MINOR.PATCH)Before Every Commit:
- Update
/VERSIONfile with appropriate increment - Update
docs/reference/CHANGELOG.mdunder[Unreleased]section - Ensure
.env.examplereflects any new environment variables
Commit Messages
Follow this format:
Short summary (50 chars or less)More detailed explanation if needed. Wrap at 72 characters.
- Bullet points are okay
- Use imperative mood: "Add feature" not "Added feature"
Fixes #123
Good Examples:
Add dark mode support to system health pageRefactors system_health.html to extend base.html template,
adding theme switching and consistent styling across the app.
Remove duplicate endpoint /admin/calculatesinglealert
This endpoint duplicated functionality from calculate_intersections.
Simplifies codebase by ~60 lines.
Branch Naming
- Feature:
feature/feature-name - Bug fix:
fix/bug-description - Docs:
docs/what-changed - Refactor:
refactor/component-name
๐ Code Navigation & Architecture Reference
Function Tree Documentation
For quick navigation and understanding of the codebase structure, refer to the comprehensive function tree documentation:
docs/reference/FUNCTION_TREE.md(Primary Reference)- Complete catalog of all major modules, classes, and functions
- 24 database models, 150+ functions, 98+ classes documented
- Every entry includes file path, line number, and signature
- Module dependency graph and database schema overview
- Use this to: Find where specific functions are defined, understand module organization
docs/reference/FUNCTIONTREEINDEX.md(Quick Reference)- Quick navigation guide for different user types (developers, agents, operators)
- Task-based lookup table (e.g., "Add API endpoint" โ relevant files)
- Complete module file structure tree
- Search tips and common patterns
- Use this to: Quickly find where to add new features or fix bugs
docs/reference/FUNCTIONTREESUMMARY.txt(Overview)- Overview of documentation contents
- Key statistics and metrics
- Maintenance guidelines
- Use this to: Understand the scope and coverage of the function tree
How to Use Function Tree for Development
When adding a new feature:- Search docs/reference/FUNCTION_TREE_INDEX.md for similar features
- Identify the module pattern (e.g., routes in
webapp/, models inapp_core/) - Follow the established patterns from similar functions
- Update docs/reference/FUNCTION_TREE.md if you add new significant functions or modules
- Search docs/reference/FUNCTION_TREE.md for the function/class mentioned in the bug report
- Note the file path and line number
- Check related functions in the same module
- Look for similar patterns in other modules for consistency
- Start with docs/reference/FUNCTION_TREE_SUMMARY.txt to understand subsystem coverage
- Use docs/reference/FUNCTION_TREE_INDEX.md to find the subsystem you're interested in
- Dive into docs/reference/FUNCTION_TREE.md for detailed function signatures and locations
Known Bugs Documentation
docs/reference/KNOWN_BUGS.md contains a comprehensive list of identified issues:
- RBAC (Role-Based Access Control) issues
- Text-to-Speech (TTS) configuration issues
- Display Screens page issues
- Environment Settings page issues
- GPIO configuration parsing issues
- Docker/Portainer deployment issues
- Check docs/reference/KNOWN_BUGS.md to see if your issue is already documented
- If fixing a bug, remove it from docs/reference/KNOWN_BUGS.md in your commit
- If discovering a new bug, add it to docs/reference/KNOWN_BUGS.md with detailed analysis
๐ Learning Resources
Python & Flask
PostGIS & Spatial
Docker
๐ค Getting Help
If you're unsure about something:
- Check existing code - Look for similar patterns
- Review this document - Follow established guidelines
- Check documentation - README, code comments, docstrings
- Ask questions - Better to ask than break things
โ Pre-Commit Checklist
Before committing code, verify:
- [ ] Version incremented properly โ Bug fix (+0.0.1) or feature (+0.1.0) in
/VERSIONfile - [ ] Documentation updated โ If features changed, update
templates/help.htmlandtemplates/about.html - [ ] Bug screenshots checked โ If fixing a bug, verified screenshot in
/bugsdirectory - [ ] Follows Python PEP 8 style (4-space indentation)
- [ ] Uses existing logger, not new logger instance
- [ ] Includes proper error handling with specific exceptions
- [ ] Bump
VERSION, mirror.env.example, and update[Unreleased]indocs/reference/CHANGELOG.mdfor any behavioural change (seetests/testreleasemetadata.py) - [ ] Touched files remain within recommended size guidelines or were refactored into smaller units
- [ ] No secrets or credentials in code
- [ ] No
.envfile committed (check git status) - [ ] Templates extend
base.htmlwith theme support - [ ] Database transactions properly handled (commit/rollback)
- [ ] Tested in Docker locally
- [ ] Documentation updated if needed
- [ ] Cross-check docs and UI links (README, Theory of Operation,
/about,/help) for accuracy and live references - [ ] Commit message follows format guidelines
Remember: When in doubt, look at existing code patterns and follow them. Consistency is more important than perfection.
๐ค Agent Activity Log
- 2024-11-12: Repository automation agent reviewed these guidelines before making any changes. All updates in this session comply with the established standards.
- 2025-01-14: Updated AGENTS.md with comprehensive theme system documentation, file naming conventions (old suffix rule), template structure updates (navbar.html active, navbarold.html deprecated), and JavaScript theme API functions. Added detailed theme architecture section covering all 11 built-in themes, CSS variable structure, import/export functionality, and dark mode best practices.
This document is served from docs/development/AGENTS.md in the EAS Station installation.