From 3745962827f0fc38c7dc21fe080a6026fed54b3f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Mon, 14 Apr 2025 12:17:44 +0300 Subject: [PATCH] Add PowerClamp module with controller and service for energy data retrieval --- libs/common/src/constants/controller-route.ts | 8 +++ libs/common/src/constants/sql-query-path.ts | 1 + .../src/helper/services/sql-loader.service.ts | 28 ++++++++ .../modules/device/entities/device.entity.ts | 2 +- .../fact_daily_energy_consumed.sql | 65 +++++++++++++++++++ src/app.module.ts | 3 +- src/power-clamp/controllers/index.ts | 1 + .../controllers/power-clamp.controller.ts | 26 ++++++++ src/power-clamp/power-clamp.module.ts | 12 ++++ src/power-clamp/services/index.ts | 1 + .../services/power-clamp.service.ts | 27 ++++++++ 11 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 libs/common/src/constants/sql-query-path.ts create mode 100644 libs/common/src/helper/services/sql-loader.service.ts create mode 100644 libs/common/src/sql/queries/power-clamp/fact_daily_energy_consumed.sql create mode 100644 src/power-clamp/controllers/index.ts create mode 100644 src/power-clamp/controllers/power-clamp.controller.ts create mode 100644 src/power-clamp/power-clamp.module.ts create mode 100644 src/power-clamp/services/index.ts create mode 100644 src/power-clamp/services/power-clamp.service.ts diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index b4dcbf9..1c8685d 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -482,7 +482,15 @@ export class ControllerRoute { 'This endpoint retrieves all devices in a specified group within a space, based on the group name and space UUID.'; }; }; + static PowerClamp = class { + public static readonly ROUTE = 'power-clamp'; + static ACTIONS = class { + public static readonly GET_ENERGY_SUMMARY = 'Get power clamp data'; + public static readonly GET_ENERGY_DESCRIPTION = + 'This endpoint retrieves power clamp data for a specific device.'; + }; + }; static DEVICE = class { public static readonly ROUTE = 'devices'; diff --git a/libs/common/src/constants/sql-query-path.ts b/libs/common/src/constants/sql-query-path.ts new file mode 100644 index 0000000..47417b0 --- /dev/null +++ b/libs/common/src/constants/sql-query-path.ts @@ -0,0 +1 @@ +export const SQL_QUERIES_PATH = 'libs/common/src/sql/queries'; diff --git a/libs/common/src/helper/services/sql-loader.service.ts b/libs/common/src/helper/services/sql-loader.service.ts new file mode 100644 index 0000000..cd51dba --- /dev/null +++ b/libs/common/src/helper/services/sql-loader.service.ts @@ -0,0 +1,28 @@ +import { SQL_QUERIES_PATH } from '@app/common/constants/sql-query-path'; +import { Injectable, Logger } from '@nestjs/common'; +import { readFileSync } from 'fs'; +import { join } from 'path'; + +@Injectable() +export class SqlLoaderService { + private readonly logger = new Logger(SqlLoaderService.name); + private readonly sqlRootPath = join(__dirname, '../sql/queries'); + + loadQuery(module: string, queryName: string): string { + const filePath = join( + process.cwd(), + SQL_QUERIES_PATH, + module, + `${queryName}.sql`, + ); + try { + return readFileSync(filePath, 'utf8'); + } catch (error) { + this.logger.error( + `Failed to load SQL query: ${module}/${queryName}`, + error.stack, + ); + throw new Error(`SQL query not found: ${module}/${queryName}`); + } + } +} diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 26d2e96..3bfbdef 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -33,7 +33,7 @@ export class DeviceEntity extends AbstractEntity { }) isActive: boolean; - @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: false }) + @ManyToOne(() => UserEntity, (user) => user.userSpaces, { nullable: true }) user: UserEntity; @OneToMany( diff --git a/libs/common/src/sql/queries/power-clamp/fact_daily_energy_consumed.sql b/libs/common/src/sql/queries/power-clamp/fact_daily_energy_consumed.sql new file mode 100644 index 0000000..00b0ca0 --- /dev/null +++ b/libs/common/src/sql/queries/power-clamp/fact_daily_energy_consumed.sql @@ -0,0 +1,65 @@ +-- model shows the energy consumed per day per device + +WITH total_energy AS ( + SELECT + device_id, + event_time::date AS date, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + where code='EnergyConsumed' + GROUP BY device_id, date +) + +, energy_phase_A AS ( + SELECT + device_id, + event_time::date AS date, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + where code='EnergyConsumedA' + GROUP BY device_id, date +) + +, energy_phase_B AS ( + SELECT + device_id, + event_time::date AS date, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + where code='EnergyConsumedB' + GROUP BY device_id, date +) + +, energy_phase_C AS ( + SELECT + device_id, + event_time::date AS date, + MIN(value)::integer AS min_value, + MAX(value)::integer AS max_value + FROM "device-status-log" + where code='EnergyConsumedC' + GROUP BY device_id, date +) + + +SELECT + total_energy.device_id, + total_energy.date, + (total_energy.max_value-total_energy.min_value) as energy_consumed_kW, + (energy_phase_A.max_value-energy_phase_A.min_value) as energy_consumed_A, + (energy_phase_B.max_value-energy_phase_B.min_value) as energy_consumed_B, + (energy_phase_C.max_value-energy_phase_C.min_value) as energy_consumed_C +FROM total_energy +JOIN energy_phase_A + ON total_energy.device_id=energy_phase_A.device_id + and total_energy.date=energy_phase_A.date +JOIN energy_phase_B + ON total_energy.device_id=energy_phase_B.device_id + and total_energy.date=energy_phase_B.date +JOIN energy_phase_C + ON total_energy.device_id=energy_phase_C.device_id + and total_energy.date=energy_phase_C.date + diff --git a/src/app.module.ts b/src/app.module.ts index cebd61a..d31ec6e 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -31,6 +31,7 @@ import { PrivacyPolicyModule } from './privacy-policy/privacy-policy.module'; import { TagModule } from './tags/tags.module'; import { ClientModule } from './client/client.module'; import { DeviceCommissionModule } from './commission-device/commission-device.module'; +import { PowerClampModule } from './power-clamp/power-clamp.module'; @Module({ imports: [ ConfigModule.forRoot({ @@ -64,8 +65,8 @@ import { DeviceCommissionModule } from './commission-device/commission-device.mo TermsConditionsModule, PrivacyPolicyModule, TagModule, - DeviceCommissionModule, + PowerClampModule, ], providers: [ { diff --git a/src/power-clamp/controllers/index.ts b/src/power-clamp/controllers/index.ts new file mode 100644 index 0000000..d1dca2a --- /dev/null +++ b/src/power-clamp/controllers/index.ts @@ -0,0 +1 @@ +export * from './power-clamp.controller'; diff --git a/src/power-clamp/controllers/power-clamp.controller.ts b/src/power-clamp/controllers/power-clamp.controller.ts new file mode 100644 index 0000000..621893d --- /dev/null +++ b/src/power-clamp/controllers/power-clamp.controller.ts @@ -0,0 +1,26 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { ApiTags, ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { EnableDisableStatusEnum } from '@app/common/constants/days.enum'; +import { ControllerRoute } from '@app/common/constants/controller-route'; +import { JwtAuthGuard } from '@app/common/guards/jwt.auth.guard'; +import { PowerClampService } from '../services/power-clamp.service'; + +@ApiTags('Power Clamp Module') +@Controller({ + version: EnableDisableStatusEnum.ENABLED, + path: ControllerRoute.PowerClamp.ROUTE, +}) +export class PowerClampController { + constructor(private readonly powerClampService: PowerClampService) {} + + @ApiBearerAuth() + @UseGuards(JwtAuthGuard) + @Get() + @ApiOperation({ + summary: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_SUMMARY, + description: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_DESCRIPTION, + }) + async getPowerClampData() { + return await this.powerClampService.getPowerClampData(); + } +} diff --git a/src/power-clamp/power-clamp.module.ts b/src/power-clamp/power-clamp.module.ts new file mode 100644 index 0000000..528d920 --- /dev/null +++ b/src/power-clamp/power-clamp.module.ts @@ -0,0 +1,12 @@ +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { PowerClampService } from './services/power-clamp.service'; +import { PowerClampController } from './controllers'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; +@Module({ + imports: [ConfigModule], + controllers: [PowerClampController], + providers: [PowerClampService, SqlLoaderService], + exports: [PowerClampService], +}) +export class PowerClampModule {} diff --git a/src/power-clamp/services/index.ts b/src/power-clamp/services/index.ts new file mode 100644 index 0000000..0cc195c --- /dev/null +++ b/src/power-clamp/services/index.ts @@ -0,0 +1 @@ +export * from './power-clamp.service'; diff --git a/src/power-clamp/services/power-clamp.service.ts b/src/power-clamp/services/power-clamp.service.ts new file mode 100644 index 0000000..c266a44 --- /dev/null +++ b/src/power-clamp/services/power-clamp.service.ts @@ -0,0 +1,27 @@ +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; +import { Injectable } from '@nestjs/common'; +import { DataSource } from 'typeorm'; + +@Injectable() +export class PowerClampService { + constructor( + private readonly sqlLoader: SqlLoaderService, + private readonly dataSource: DataSource, + ) {} + + async getPowerClampData() { + const sql = this.sqlLoader.loadQuery( + 'power-clamp', + 'fact_daily_energy_consumed', + ); + return this.dataSource.manager.query(sql); + } + + async getEnergyConsumed(code: string) { + const sql = this.sqlLoader.loadQuery( + 'energy', + 'energy_consumed_with_params', + ); + return this.dataSource.manager.query(sql, [code]); + } +}