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.456 → 123.46
date_time_to_dateConvert UTC datetime to date2024-11-09T10:30:00Z → 09/11/2024
date_to_dateFormat date strings2024-11-09 → 09/11/2024
date_time_to_timeExtract time from datetime2024-11-09T10:30:00Z → 10: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​