Skip to main content

Key Components

info

This page provides a detailed overview of the major components in AquaGen API and how they work together.

🧩 Component Overview

1️⃣ Flask Application (app/__init__.py)

The central Flask application that bootstraps the entire system.

Key Responsibilities

  • Initialize Flask app with CORS and JWT
  • Register API blueprints (user, admin, external, qa)
  • Configure Azure Application Insights telemetry
  • Set up custom template filters
  • Configure JWT token lifecycle
  • Implement user context loading

Configuration

# JWT Configuration
app.config["JWT_TOKEN_LOCATION"] = ["headers", "query_string"]
app.config["JWT_SECRET_KEY"] = Config.SECRET_KEY
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(hours=4)
app.config["JWT_REFRESH_TOKEN_EXPIRES"] = timedelta(days=14)

Custom Template Filters

FilterPurposeExample Output
decimal_formatFormat numbers with precision123.456123.46
date_time_to_dateConvert UTC datetime to date2024-11-09T10:30:00Z09/11/2024
date_to_dateFormat date strings2024-11-0909/11/2024
date_time_to_timeExtract time from datetime2024-11-09T10:30:00Z10:30:00

Middleware Stack

2️⃣ API Blueprints (app/apis/)

User Blueprint (user.py)

Purpose: User-facing API endpoints

Namespaces (35 total):

  • user - User profile and settings
  • accounts - Account management
  • reports - Report generation
  • device_data - Device data retrieval
  • device_data_v2 - Enhanced device data API
  • alerts - Alert management
  • notifications - Notification endpoints
  • landing_page - Dashboard data
  • summary_page - Summary reports
  • water_balance - Water balance analysis
  • granular_data - Time-series data
  • quality_data - Quality parameters
  • And more...

Authentication: All endpoints require JWT (@jwt_required())

Admin Blueprint (admin.py)

Purpose: Administrative operations

Namespaces (24 total):

  • admin_login - Admin authentication
  • industry - Industry CRUD operations
  • unit - Unit/device management
  • user - User management
  • unit_data - Admin unit data access
  • workspace - Workspace configuration
  • automated_report - Scheduled reports
  • offline_devices - Offline device tracking
  • notification - System notifications
  • insights - Analytics insights
  • archive_data - Data migration/archiving
  • gchats - Google Chat integration
  • validator - Data validation tools
  • hourly_consumption_alert_email - Hourly consumption alert emails
  • global_logs - Audit logs
  • shift_monitoring - Shift tracking
  • bp_trigger - Batch processing triggers
  • bathy_data_processor - Bathymetry data processing
  • plc_event_hub_test - PLC event hub testing
  • water_quality_satellite - Satellite water quality (Lake Pulse)
  • verify_monthly_data - Monthly data verification
  • water_balance_leakage_detection - Leakage detection
  • water_balance_leakage_evaluator - Leakage evaluation

Authentication: Requires admin role (@admin_required())

External Blueprint (external.py)

Purpose: Third-party integrations

Namespaces:

  • external_login - External system authentication
  • water_ratio - Water measurement standards

Blueprint Registration

app.register_blueprint(user_blueprint)   # /api/user
app.register_blueprint(admin_blueprint) # /api/admin
app.register_blueprint(external_blueprint) # /api/external
app.register_blueprint(qa_blueprint) # /api/qa

3️⃣ Route Handlers (app/routes/)

info

Route handlers parse HTTP requests, invoke the appropriate service, and pass the result to formatters before returning the HTTP response.

Pre-Route Processing

1. Rate Limiter

flask-limiter applies before any route logic runs.

  • Default limits: 200 requests/day, 50 requests/hour per IP
  • Key function: X-Forwarded-For header → falls back to remote_addr
  • Storage: In-memory (fixed-window strategy)
  • Custom limits for specific operations:
OperationLimits
OTP generate3/min, 10/hr, 20/day
OTP verify5/min, 15/hr
Login10/min, 50/hr
User create5/hr, 20/day

2. Auth Interceptor (app/auth/auth.py)

Runs as @app.before_request on every request.

Public routes (skip auth entirely):

  • /api/user/user/login, /logout, /otplogin, /refresh, /swaggerLogin, /validateCredentials
  • /api/admin/auth/login, /refresh, /swaggerLogin
  • /api/external/auth/swaggerLogin, /api/qa/auth/swaggerLogin
  • All Swagger/docs UI paths (/api/*/docs, /api/*/swagger.json, /static, /swaggerui)
  • /api/user/virtual/*, /api/admin/shift-monitoring/*, /api/admin/bpTrigger, /api/admin/bathyDataProcessor/*

For all other routes, the interceptor:

  1. Extracts token from Authorization: Bearer <token> header or ?jwt= query param
  2. Decodes token to get userId and industryId
  3. Calls TokenService.validate_token() — checks token is active in token_logs_container (skipped for INTERNAL industry)
  4. Calls verify_jwt_in_request() to verify signature and expiry
  5. Sets g.user_id, g.industry_id, g.user_permissions on Flask's request context

Returns 401 for missing/invalid tokens, 440 for session-expired tokens.

3. JWT User Loader

After token verification, Flask-JWT-Extended calls user_lookup_loader to hydrate the current user:

  • Loads industry data from industries_container
  • Sets g.timezone for the request
  • Loads alertsConfig and evaluates leakage monitoring state
  • Supports targetIndustryId header for internal/company cross-industry access

4. Timezone Handler

Sets g.timezone = 'Asia/Kolkata' as default if not already set by the JWT user loader (e.g. for public routes).

5. IP Tracker (app/security_checks/track_user.py)

Runs as @app.before_request after auth:

  • Resolves client IP from X-Forwarded-For (first entry) or remote_addr
  • Skips geo-lookup for private IPs (RFC1918 ranges)
  • For public IPs: fetches city, region, country from ipinfo.io (cached via @lru_cache(maxsize=1000))
  • Writes a log entry to the database with userId, industryId, ip, city, region, country, created_on

Route-Level Decorators

These are applied per endpoint inside the route handler:

DecoratorPurpose
@jwt_required()Validates JWT and makes current_user available
@permission_required(p)Checks claims["permissions"] list, returns 401 if missing
@jwt_or_redirect(url)For browser routes — redirects to url if not authenticated via cookie
@validate_values(rules)Scans string params for SQL injection patterns, raises ValidationError (405)

Post-Response: Security Headers

Applied to every response via @app.after_request:

HeaderValue
Content-Security-Policyframe-ancestors <ALLOWED_FRAME_ANCESTORS>
X-Content-Type-Optionsnosniff
X-XSS-Protection1; mode=block
Referrer-Policystrict-origin-when-cross-origin

Route Files

User API (/api/user)

FileArea
report.pyReport generation (all types and formats)
device_data.pySensor readings by date range
device_data_v2.pyEnhanced device data with formatter pipeline
alerts_routes.pyAlert listing and creation
granular_data.pyHigh-frequency time-series data
landing_page.pyDashboard summary data
summary_page.pySummary report data
water_balance.pyWater balance analysis
water_neutrality_page.pyWater Neutrality Index data
ground_water_level_graph_data.pyGroundwater level graphs
gwi.pyGWI (Groundwater Intelligence)
fwi_route.pyFWI (Fresh Water Intelligence)
rainfall_data.pyRainfall measurements
virtual_device_data.pyManual / virtual node data entry
notification.pyUser notifications
user.pyUser profile, login, and settings
accounts.pyAccount management
scada.pySCADA diagram editor
ro_efficiency.pyRO plant efficiency tracking
standard_category_view.pyStandard category metadata
additional_feature.pyFeature flags per industry
subscription.pySubscription management
plc_register.pyPLC register read/write
send_mail.pyDirect email sending
common.pyShared utility endpoints
uwms.pyUWMS data
quality_reports_data_extraction.pyQuality report data extraction
batch_processing.pyBatch processing triggers
external_device_data.pyExternal device data access
executive_summary/executiveSummaryData.pyExecutive summary dashboard
executive_summary/executiveSummaryVirtualLogin.pyExecutive summary virtual login
gpt/AquaGPT routes

Admin API (/api/admin)

FileArea
admin/admin_login.pyAdmin authentication
admin/industry.pyIndustry CRUD and configuration
admin/unit.pyUnit / device management
admin/user.pyUser management
admin/device_data.pyAdmin device data access
admin/workspace.pyWorkspace configuration
admin/automated_report.pyScheduled report management
admin/offline_devices.pyOffline device tracking
admin/admin_notification.pySystem notifications
admin/insights.pyAnalytics insights
admin/archive_data.pyData migration and archiving
admin/gchats.pyGoogle Chat integration
admin/validator.pyData validation tools
admin/hourly_consumption_alert_email.pyHourly consumption alert emails
admin/global_logs.pyAudit logs
admin/shift_monitoring.pyShift tracking
admin/bp_trigger.pyBatch processing triggers
admin/bathy_data_processor.pyBathymetry data processing
admin/plc_eventhub.pyPLC event hub testing
admin/water_quality_satellite.pySatellite water quality (Lake Pulse)
admin/verify_monthly_data.pyMonthly data verification
admin/water_balance_leakage_formula.pyLeakage detection
admin/water_balance_leakage_evaluator.pyLeakage evaluation

External API (/api/external)

FileArea
external/external_login.pyExternal system authentication
external/water_ratio_data.pyWater measurement standards

4️⃣ Service Layer (app/services/)

info

Services implement all business logic. A route handler instantiates a service with the parsed request data and calls a method on it.

The service:

  1. Reads industry/user context from current_user (hydrated by the JWT user loader on every request) — no extra auth calls needed
  2. Determines which units, categories, or date ranges are relevant based on request parameters
  3. Calls DatabaseSupporter static methods to fetch raw data from Cosmos DB — often in parallel using ThreadPoolExecutor for multi-unit or multi-date queries
  4. Applies business logic — aggregations, threshold checks, graph computations, date/timezone conversions
  5. Returns structured Python dicts/lists back to the route handler

The route handler then passes this data to a formatter. Services do not call formatters directly.

Key patterns across services:

  • current_user is always available — contains industryId, unitsMapping, categories, daysOffset, alertsConfig, and full industry metadata
  • Date/time handling goes through DateTimeUtil for timezone-aware conversions
  • Water balance, GWI, FWI, and landing page services load their configuration documents from additional_feature_container at init time
  • Alert processing resolves recipient contacts from users_container, then dispatches to channel classes (Email, SMS, WhatsApp, FCM, Web) based on per-recipient channel config

Report Services

ReportService (Dispatcher)

app/services/report/report.py

The main dispatcher. Accepts a ReportRequestData object and routes to the appropriate report class based on serviceType.

ServiceTypeClassDescription
waterFlowReportWater flow/consumption reports
energyEnergyReportEnergy consumption reports
levelLevelReportWater level/tank reports
qualityQualityReportWater quality parameter reports
borewellBorewellReportGroundwater borewell reports
rain_waterRainWaterReportRainfall collection reports
summarySummaryReportMulti-category summary reports
smart_citySmartCityReportSmart city monitoring reports
water_balanceWaterBalanceReportInflow vs outflow analysis
alertAlertReportAlert history reports
consolidatedConsolidatedReportMonthly consolidated reports
custom_waterCustomFlowReportCustom water configuration
custom_energyCustomEnergyReportCustom energy configuration
custom_levelCustomLevelReportCustom level configuration
custom_qualityCustomQualityReportCustom quality configuration
custom_withdrawalCustomWithdrawalReportCustom withdrawal reports
ground_water_withdrawalGroundWaterWithdrawalReportGW extraction reports
executiveExecutiveReportExecutive summary reports

Each report class in app/services/report/report_types/ follows this pattern:

  1. Accepts ReportRequestData
  2. Queries DatabaseSupporter for raw device data
  3. Aggregates and calculates metrics (daily totals, hourly breakdowns, etc.)
  4. Returns structured data dict consumed by ReportFormatter

Alert Services

AlertsProcessor

app/services/alerts/alerts_processor.py

The central alert evaluation engine. For each alert trigger:

  1. Evaluates alert condition against current data (threshold, offline, flow anomaly, quality)
  2. Resolves recipient contacts from user IDs
  3. Dispatches notifications across enabled channels

AlertsProcessor dispatches to a content class based on alert_type:

Alert TypeContent Class
unit_thresholdUnitThresholdAlertContent
hourly_thresholdHourlyThresholdAlertContent
monthly_thresholdMonthlyThresholdAlertContent
device_statusDeviceStatusAlertContent
level_exceedLevelAlertContent
stable_flow_alertStableFlowAlertContent
flow_rate_*FlowRateAlertContent
quality_thresholdQualityThresholdAlertContent
energy_thresholdEnergyAlertContent
no_flow_n_daysNoFlowAlertContent
no_level_variation_n_daysNoLevelVariationAlertContent
water_balance_leakageWaterBalanceLeakageAlertContent

Notification channels: EmailChannel (SMTP), FirebaseChannel (FCM push), MessageChannel (SMS), WhatsappChannel, WebChannel (Azure Web PubSub).

Service Groups

User-Facing Services

ServicePurpose
device_data.pyFetch and process IoT sensor readings by date/category
device_data_v2/Enhanced device data pipeline with per-category formatters
latest_data_service.pyMost recent reading per unit
granula_data_service.pyHigh-frequency time-series data
landing_page/Dashboard aggregations (today, yesterday, past 3 months)
summary_page.pySummary report data aggregation
water_balance.pyInflow/outflow modelling
water_neutrality_service.pyWater Neutrality Index computation
gwi_service.pyGroundwater Intelligence calculations
fwi/Fresh Water Intelligence and rainfall data
groundwater_level_graph/Groundwater level trend graphs
ro_efficiency_service.pyReverse-osmosis plant performance tracking
scada.pySCADA diagram node/edge configuration
virtual_device_data.pyManual data entry for nodes without physical sensors
alerts/alerts_processor.pyEvaluate alert rules and dispatch multi-channel notifications
notification.pyUser notification management
accounts.pyAccount and credential management
user.pyUser profile, login, OTP, SSO
report/Report generation — dispatches to service-specific report classes
executive_summary/Cross-industry executive dashboard

Admin Services (services/admin/)

ServicePurpose
admin_login.pyAdmin authentication
industry.pyIndustry CRUD, timezone, and shift configuration
unit.pyUnit/device management
user.pyUser management and permissions
workspace.pyWorkspace configuration
global_logs.pyAudit log retrieval
insights.pyAnalytics and insights
gchats.pyGoogle Chat webhook management
hourly_consumption_alert_email.pyHourly consumption alert emails
archive_data.pyData migration and archiving
bp_trigger.pyBatch processing triggers
bathy_data_processor.pyBathymetry data processing
water_quality_satellite_processor.pySatellite water quality processing
verify_monthly_data.pyMonthly data verification
automated_report/automated_report.pyAutomated report generation and email delivery
plc_eventhub.pyPLC event hub integration

Notification & Communication Services

ServicePurpose
firebase/firebase_suppoter.pyFirebase FCM push notifications
sms/sms_suppoter.pySMS delivery
send_mail_service.pyEmail via Gmail SMTP

Infrastructure Services

ServicePurpose
token_validation.pyJWT token lifecycle — save, validate, revoke
secret_vault_service.pyAzure Key Vault secret fetching
google_verify_token.pyGoogle SSO token verification
plc_register_service.pyPLC register read/write
web_socket.pyWebSocket support
external/water_ratio.pyWater ratio standards for external API

5️⃣ Formatters (app/formatters/)

info

Formatters are called directly from route handlers after the service returns data. They receive the raw structured data from the service and transform it into the final response — a JSON dict, a rendered HTML string, an in-memory PDF/XLSX/CSV file stream (BytesIO), or a graph-ready structure.

How Formatters Work

  1. Route handler receives processed data from the service
  2. Route instantiates the appropriate formatter, passing the data and any format/display arguments
  3. Formatter reads current_user context (units mapping, industry meta, alerts config) where needed
  4. For device data v2: DeviceDataV2FormatterBuilder selects the correct formatter class based on standardCategoryId — flow, stock, energy, quality, or groundwater level — each extending DeviceDataV2FormatterTemplate
  5. Each formatter class implements format() returning the final structure
  6. For reports: ReportFormatter renders a Jinja2 HTML template first, then converts to PDF via xhtml2pdf (standard) or Playwright (executive summary), to XLSX via xlsxwriter, or to CSV via pandas — all written to a BytesIO stream (no disk writes)
  7. The route returns the formatter output directly as the HTTP response

Device Data Formatters

FormatterPurpose
device_data_formatter.pyBasic device data response (v1)
device_data_v2/device_data_v2_formatter.pyEntry point for v2 device data pipeline
device_data_v2/formatters/device_data_formatter_template.pyBase class — all v2 formatters extend this
device_data_v2/formatters/flow_device_data_formatter.pyFlow / water category
device_data_v2/formatters/stock_device_data_formatter.pyStock / tank level category
device_data_v2/formatters/energy_device_data_formatter.pyEnergy category
device_data_v2/formatters/quality_device_data_formatter.pyQuality parameters
device_data_v2/formatters/gw_device_data_formatter.pyGroundwater level
device_data_v2/formatters/device_data_formatter_builder.pySelects correct formatter by standardCategoryId
granular_data_formatter.pyHigh-frequency time-series data
rainfall_data_formatter.pyRainfall measurements

Report Formatters (formatters/report/)

FormatterPurpose
report.pyEntry point — dispatches to HTML/PDF/XLSX/CSV output
flow_formatter.pyWater flow report data
level_formatter.pyLevel report data
borewell_formatter.pyBorewell report data
quality_formatter.pyQuality report data
water_balance_formatter.pyWater balance report data
smart_city_formatter.pySmart city report data
rain_water_formatter.pyRainwater report data
monthly_consolidated_formatter.pyMonthly consolidated report
daily_summary_formatter.pyDaily summary report
report_util.py / report_util_v2.pyShared report utility methods
xlsx_formatter/XLSX-specific formatting (alignment, colour, font, image)

Page / Dashboard Formatters

FormatterPurpose
industry_data_formatter.pyIndustry data loaded on every authenticated request
landing_page_formatter.pyDashboard landing page data
summary_page_formatter.pySummary page data
water_balance_data_formatter.pyWater balance graphs and metrics
water_neutrality_data_formatter.pyWater Neutrality Index data
gwi_formatter.pyGWI response
fwi_formatter.pyFWI response
scada_data_formatter.pySCADA diagram nodes and edges
executive_summary_data_formatter.pyExecutive summary dashboard
alerts_formatter.pyAlert list response
notification_data_formatter.pyNotification list response
ro_efficiency/ro_efficiency_formatter.pyRO efficiency metrics

6️⃣ Database Layer (app/database/)

info

The database layer is an abstraction between Cosmos DB configuration and the rest of the project. Services and routes never interact with the Cosmos DB SDK directly — they go through this layer.

Files

FilePurpose
database_config.pyInitialises the Cosmos DB client, creates database references, and exposes named container clients used everywhere else
database_supporter.py122 static methods covering all read/write operations across every container
queires_list.py94 centralised query string builders — all Cosmos DB SQL queries defined here, validated against SQL injection via @validate_values()

How It Works

  • database_config.py connects to Cosmos DB at startup using credentials from Key Vault and exposes container clients (industries_container, users_container, devices_data_container, etc.) as module-level variables
  • DatabaseSupporter imports those container clients and provides static methods that services call — e.g. DatabaseSupporter.get_industry_details_by_id(industry_id)
  • Queries (in queires_list.py) builds parameterised query strings; DatabaseSupporter passes these to the Cosmos DB SDK
warning

Never build Cosmos DB query strings inline in services or routes. All queries must be defined in queires_list.py and accessed through DatabaseSupporter to ensure consistent SQL injection protection via @validate_values().

Parallel Queries

tip

Where multiple independent queries are needed (e.g. fetching data for multiple units or date ranges), DatabaseSupporter uses ThreadPoolExecutor to run them concurrently and return combined results.

7️⃣ Configuration (app/config.py)

Purpose: Centralized configuration with Azure Key Vault

class Config(object):
# Database
COSMOSDBENDPOINT = smi.get(SecretName.COSMOSDBENDPOINT)
COSMOSDBKEY = smi.get(SecretName.COSMOSDBKEY)

# Authentication
AZURE_AD_APP_ID = smi.get(SecretName.AZURE_AD_APP_ID)
AZURE_AD_ISSUER = smi.get(SecretName.AZURE_AD_ISSUER)

# Environment
ENVIRONMENT = os.environ.get('ENVIRONMENT', 'development')

Security Features:

  • All secrets fetched from Azure Key Vault
  • No hardcoded credentials
  • Environment-specific configuration
  • Runtime secret rotation support

8️⃣ Utilities (app/util/)

DateTimeUtil

  • Timezone conversion (UTC ↔ Local)
  • Date parsing and formatting
  • Shift-aware time calculations

NumberUtil

  • Number truncation
  • Decimal precision handling
  • SI unit formatting

SIUnitUtil

  • Unit conversion (L, KL, ML, GL)
  • Power units (W, KW, MW)
  • Quality parameter units

CalculationUtil

  • Statistical calculations (min, max, avg)
  • Trend analysis
  • Percentage calculations

🔟 Data Models (app/models/)

Model Types:

Example Model:

from flask_restx import fields

GenericSuccessModel = {
'message': fields.String(description='Success message'),
'status': fields.String(default='success'),
'status_code': fields.Integer(default=200),
'data': fields.Raw(description='Response data')
}

Component Interaction

All components work together following the layered architecture pattern. Routes handle requests, services process business logic, formatters transform data, and the database supporter manages persistence.

Standard Response Envelope

All JSON responses from AquaGen API follow this structure:

{
"status": "success" | "failed",
"status_code": 200,
"message": "Human-readable message",
"data": { ... }
}

Error Handling

ValidationError (app/security_checks/input_validator.py):
Raised by @validate_values() when SQL injection patterns are detected in string parameters. Caught by @app.errorhandler(ValidationError), returns JSON with HTTP 405.

Rate limit (429):
flask-limiter returns {"message": "Too many requests. Please try again later.", "status": "failed"}.

Report/Alert flows:
Exceptions inside services and formatters propagate up to the route handler and are returned as {"status": "failed", "status_code": 500, "message": "<error>"}.

Industry not found:
@CachedData.jwt.user_lookup_error_loader aborts with 400 "Industry not found" when the JWT's industryId does not exist in industries_container.

📚 Next Steps