diff --git a/libs/common/src/constants/controller-route.ts b/libs/common/src/constants/controller-route.ts index 3ed009e..dcc7b5e 100644 --- a/libs/common/src/constants/controller-route.ts +++ b/libs/common/src/constants/controller-route.ts @@ -494,9 +494,10 @@ export class ControllerRoute { public static readonly ROUTE = 'power-clamp'; static ACTIONS = class { - public static readonly GET_ENERGY_SUMMARY = 'Get power clamp data'; + public static readonly GET_ENERGY_SUMMARY = + 'Get power clamp historical data'; public static readonly GET_ENERGY_DESCRIPTION = - 'This endpoint retrieves power clamp data for a specific device.'; + 'This endpoint retrieves the historical data of a power clamp device based on the provided parameters.'; }; }; static DEVICE = class { diff --git a/libs/common/src/constants/power.clamp.enargy.enum.ts b/libs/common/src/constants/power.clamp.enargy.enum.ts new file mode 100644 index 0000000..bfed05b --- /dev/null +++ b/libs/common/src/constants/power.clamp.enargy.enum.ts @@ -0,0 +1,6 @@ +export enum PowerClampEnergyEnum { + ENERGY_CONSUMED = 'EnergyConsumed', + ENERGY_CONSUMED_A = 'EnergyConsumedA', + ENERGY_CONSUMED_B = 'EnergyConsumedB', + ENERGY_CONSUMED_C = 'EnergyConsumedC', +} diff --git a/libs/common/src/constants/sql-query-path.ts b/libs/common/src/constants/sql-query-path.ts index 47417b0..e22c42d 100644 --- a/libs/common/src/constants/sql-query-path.ts +++ b/libs/common/src/constants/sql-query-path.ts @@ -1 +1,2 @@ export const SQL_QUERIES_PATH = 'libs/common/src/sql/queries'; +export const SQL_PROCEDURES_PATH = 'libs/common/src/sql/procedures'; diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index b4c695d..fa83c76 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -46,6 +46,11 @@ import { ClientEntity } from '../modules/client/entities'; import { TypeOrmWinstonLogger } from '@app/common/logger/services/typeorm.logger'; import { createLogger } from 'winston'; import { winstonLoggerOptions } from '../logger/services/winston.logger'; +import { + PowerClampDailyEntity, + PowerClampHourlyEntity, + PowerClampMonthlyEntity, +} from '../modules/power-clamp/entities/power-clamp.entity'; @Module({ imports: [ TypeOrmModule.forRootAsync({ @@ -101,6 +106,9 @@ import { winstonLoggerOptions } from '../logger/services/winston.logger'; SpaceProductAllocationEntity, SubspaceProductAllocationEntity, ClientEntity, + PowerClampHourlyEntity, + PowerClampDailyEntity, + PowerClampMonthlyEntity, ], namingStrategy: new SnakeNamingStrategy(), synchronize: Boolean(JSON.parse(configService.get('DB_SYNC'))), diff --git a/libs/common/src/firebase/devices-status/devices-status.module.ts b/libs/common/src/firebase/devices-status/devices-status.module.ts index 54d5cfa..217a4d8 100644 --- a/libs/common/src/firebase/devices-status/devices-status.module.ts +++ b/libs/common/src/firebase/devices-status/devices-status.module.ts @@ -3,12 +3,24 @@ import { DeviceStatusFirebaseController } from './controllers/devices-status.con import { DeviceStatusFirebaseService } from './services/devices-status.service'; import { DeviceRepository } from '@app/common/modules/device/repositories'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories/device-status.repository'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ providers: [ DeviceStatusFirebaseService, DeviceRepository, DeviceStatusLogRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], controllers: [DeviceStatusFirebaseController], exports: [DeviceStatusFirebaseService, DeviceStatusLogRepository], diff --git a/libs/common/src/firebase/devices-status/services/devices-status.service.ts b/libs/common/src/firebase/devices-status/services/devices-status.service.ts index 9ef06a0..739d846 100644 --- a/libs/common/src/firebase/devices-status/services/devices-status.service.ts +++ b/libs/common/src/firebase/devices-status/services/devices-status.service.ts @@ -18,6 +18,9 @@ import { runTransaction, } from 'firebase/database'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; +import { ProductType } from '@app/common/constants/product-type.enum'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { PowerClampEnergyEnum } from '@app/common/constants/power.clamp.enargy.enum'; @Injectable() export class DeviceStatusFirebaseService { private tuya: TuyaContext; @@ -25,6 +28,7 @@ export class DeviceStatusFirebaseService { constructor( private readonly configService: ConfigService, private readonly deviceRepository: DeviceRepository, + private readonly powerClampService: PowerClampService, private deviceStatusLogRepository: DeviceStatusLogRepository, ) { const accessKey = this.configService.get('auth-config.ACCESS_KEY'); @@ -79,6 +83,7 @@ export class DeviceStatusFirebaseService { return await this.createDeviceStatusFirebase({ deviceUuid: device.uuid, ...addDeviceStatusDto, + productType: device.productDevice.prodType, }); } // Return null if device not found or no UUID @@ -216,6 +221,25 @@ export class DeviceStatusFirebaseService { }); await this.deviceStatusLogRepository.save(newLogs); + if (addDeviceStatusDto.productType === ProductType.PC) { + const energyCodes = new Set([ + PowerClampEnergyEnum.ENERGY_CONSUMED, + PowerClampEnergyEnum.ENERGY_CONSUMED_A, + PowerClampEnergyEnum.ENERGY_CONSUMED_B, + PowerClampEnergyEnum.ENERGY_CONSUMED_C, + ]); + + const energyStatus = addDeviceStatusDto?.log?.properties?.find((status) => + energyCodes.has(status.code), + ); + + if (energyStatus) { + await this.powerClampService.updateEnergyConsumedHistoricalData( + addDeviceStatusDto.deviceUuid, + ); + } + } + // Return the updated data const snapshot: DataSnapshot = await get(dataRef); return snapshot.val(); diff --git a/libs/common/src/helper/services/power.clamp.service.ts b/libs/common/src/helper/services/power.clamp.service.ts new file mode 100644 index 0000000..b0e852f --- /dev/null +++ b/libs/common/src/helper/services/power.clamp.service.ts @@ -0,0 +1,61 @@ +import { Injectable } from '@nestjs/common'; +import { SqlLoaderService } from './sql-loader.service'; +import { DataSource } from 'typeorm'; +import { SQL_PROCEDURES_PATH } from '@app/common/constants/sql-query-path'; + +@Injectable() +export class PowerClampService { + constructor( + private readonly sqlLoader: SqlLoaderService, + private readonly dataSource: DataSource, + ) {} + + async updateEnergyConsumedHistoricalData(deviceUuid: string): Promise { + try { + const now = new Date(); + const dateStr = now.toLocaleDateString('en-CA'); // YYYY-MM-DD + const hour = now.getHours(); + const monthYear = now + .toLocaleDateString('en-US', { + month: '2-digit', + year: 'numeric', + }) + .replace('/', '-'); // MM-YYYY + + await this.executeProcedure('fact_hourly_energy_consumed_procedure', [ + deviceUuid, + dateStr, + hour, + ]); + + await this.executeProcedure('fact_daily_energy_consumed_procedure', [ + deviceUuid, + dateStr, + ]); + + await this.executeProcedure('fact_monthly_energy_consumed_procedure', [ + deviceUuid, + monthYear, + ]); + } catch (err) { + console.error('Failed to insert or update energy data:', err); + throw err; + } + } + + private async executeProcedure( + procedureFileName: string, + params: (string | number | null)[], + ): Promise { + const query = this.loadQuery(procedureFileName); + await this.dataSource.query(query, params); + } + + private loadQuery(fileName: string): string { + return this.sqlLoader.loadQuery( + 'fact_energy_consumed', + fileName, + SQL_PROCEDURES_PATH, + ); + } +} diff --git a/libs/common/src/helper/services/sql-loader.service.ts b/libs/common/src/helper/services/sql-loader.service.ts index cd51dba..6012c42 100644 --- a/libs/common/src/helper/services/sql-loader.service.ts +++ b/libs/common/src/helper/services/sql-loader.service.ts @@ -1,4 +1,3 @@ -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'; @@ -8,13 +7,8 @@ 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`, - ); + loadQuery(module: string, queryName: string, path: string): string { + const filePath = join(process.cwd(), path, module, `${queryName}.sql`); try { return readFileSync(filePath, 'utf8'); } catch (error) { diff --git a/libs/common/src/modules/device/entities/device.entity.ts b/libs/common/src/modules/device/entities/device.entity.ts index 6ed8e42..9015c40 100644 --- a/libs/common/src/modules/device/entities/device.entity.ts +++ b/libs/common/src/modules/device/entities/device.entity.ts @@ -17,6 +17,7 @@ import { SceneDeviceEntity } from '../../scene-device/entities'; import { SpaceEntity } from '../../space/entities/space.entity'; import { SubspaceEntity } from '../../space/entities/subspace/subspace.entity'; import { NewTagEntity } from '../../tag'; +import { PowerClampHourlyEntity } from '../../power-clamp/entities/power-clamp.entity'; @Entity({ name: 'device' }) @Unique(['deviceTuyaUuid']) @@ -79,7 +80,8 @@ export class DeviceEntity extends AbstractEntity { @OneToMany(() => NewTagEntity, (tag) => tag.devices) // @JoinTable({ name: 'device_tags' }) public tag: NewTagEntity; - + @OneToMany(() => PowerClampHourlyEntity, (powerClamp) => powerClamp.device) + powerClampHourly: PowerClampHourlyEntity[]; constructor(partial: Partial) { super(); Object.assign(this, partial); diff --git a/libs/common/src/modules/power-clamp/dtos/index.ts b/libs/common/src/modules/power-clamp/dtos/index.ts new file mode 100644 index 0000000..4c247e5 --- /dev/null +++ b/libs/common/src/modules/power-clamp/dtos/index.ts @@ -0,0 +1 @@ +export * from './power-clamp.dto'; diff --git a/libs/common/src/modules/power-clamp/dtos/power-clamp.dto.ts b/libs/common/src/modules/power-clamp/dtos/power-clamp.dto.ts new file mode 100644 index 0000000..5954efc --- /dev/null +++ b/libs/common/src/modules/power-clamp/dtos/power-clamp.dto.ts @@ -0,0 +1,43 @@ +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; + +export class PowerClampDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public deviceUuid: string; + + @IsString() + @IsOptional() + public hour?: string; + + @IsString() + @IsOptional() + public day?: string; + + @IsString() + @IsOptional() + public month?: string; + + @IsString() + @IsNotEmpty() + public energyConsumedKw: string; + + @IsString() + @IsNotEmpty() + public energyConsumedA: string; + + @IsString() + @IsNotEmpty() + public energyConsumedB: string; + + @IsString() + @IsNotEmpty() + public energyConsumedC: string; + + @IsString() + @IsNotEmpty() + public prodType: string; +} diff --git a/libs/common/src/modules/power-clamp/entities/index.ts b/libs/common/src/modules/power-clamp/entities/index.ts new file mode 100644 index 0000000..a7eea95 --- /dev/null +++ b/libs/common/src/modules/power-clamp/entities/index.ts @@ -0,0 +1 @@ +export * from './power-clamp.entity'; diff --git a/libs/common/src/modules/power-clamp/entities/power-clamp.entity.ts b/libs/common/src/modules/power-clamp/entities/power-clamp.entity.ts new file mode 100644 index 0000000..51ea4c5 --- /dev/null +++ b/libs/common/src/modules/power-clamp/entities/power-clamp.entity.ts @@ -0,0 +1,95 @@ +import { Column, Entity, ManyToOne, Unique } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { PowerClampDto } from '../dtos'; +import { DeviceEntity } from '../../device/entities/device.entity'; + +@Entity({ name: 'power-clamp-energy-consumed-hourly' }) +@Unique(['deviceUuid', 'date', 'hour']) +export class PowerClampHourlyEntity extends AbstractEntity { + @Column({ nullable: false }) + public deviceUuid: string; + + @Column({ nullable: false }) + public hour: string; + + @Column({ nullable: false, type: 'date' }) + public date: string; + + @Column({ nullable: true }) + public energyConsumedKw: string; + + @Column({ nullable: false }) + public energyConsumedA: string; + + @Column({ nullable: false }) + public energyConsumedB: string; + + @Column({ nullable: false }) + public energyConsumedC: string; + + @ManyToOne(() => DeviceEntity, (device) => device.powerClampHourly) + device: DeviceEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} +@Entity({ name: 'power-clamp-energy-consumed-daily' }) +@Unique(['deviceUuid', 'date']) +export class PowerClampDailyEntity extends AbstractEntity { + @Column({ nullable: false }) + public deviceUuid: string; + + @Column({ nullable: false, type: 'date' }) + public date: string; + + @Column({ nullable: true }) + public energyConsumedKw: string; + + @Column({ nullable: false }) + public energyConsumedA: string; + + @Column({ nullable: false }) + public energyConsumedB: string; + + @Column({ nullable: false }) + public energyConsumedC: string; + + @ManyToOne(() => DeviceEntity, (device) => device.powerClampHourly) + device: DeviceEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} +@Entity({ name: 'power-clamp-energy-consumed-monthly' }) +@Unique(['deviceUuid', 'month']) +export class PowerClampMonthlyEntity extends AbstractEntity { + @Column({ nullable: false }) + public deviceUuid: string; + + @Column({ nullable: false }) + public month: string; + + @Column({ nullable: true }) + public energyConsumedKw: string; + + @Column({ nullable: false }) + public energyConsumedA: string; + + @Column({ nullable: false }) + public energyConsumedB: string; + + @Column({ nullable: false }) + public energyConsumedC: string; + + @ManyToOne(() => DeviceEntity, (device) => device.powerClampHourly) + device: DeviceEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/power-clamp/power-clamp.repository.module.ts b/libs/common/src/modules/power-clamp/power-clamp.repository.module.ts new file mode 100644 index 0000000..8a87041 --- /dev/null +++ b/libs/common/src/modules/power-clamp/power-clamp.repository.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { PowerClampHourlyEntity } from './entities/power-clamp.entity'; + +@Module({ + providers: [], + exports: [], + controllers: [], + imports: [TypeOrmModule.forFeature([PowerClampHourlyEntity])], +}) +export class PowerClampRepositoryModule {} diff --git a/libs/common/src/modules/power-clamp/repositories/index.ts b/libs/common/src/modules/power-clamp/repositories/index.ts new file mode 100644 index 0000000..9ffe595 --- /dev/null +++ b/libs/common/src/modules/power-clamp/repositories/index.ts @@ -0,0 +1 @@ +export * from './power-clamp.repository'; diff --git a/libs/common/src/modules/power-clamp/repositories/power-clamp.repository.ts b/libs/common/src/modules/power-clamp/repositories/power-clamp.repository.ts new file mode 100644 index 0000000..c3d5c37 --- /dev/null +++ b/libs/common/src/modules/power-clamp/repositories/power-clamp.repository.ts @@ -0,0 +1,28 @@ +import { DataSource, Repository } from 'typeorm'; +import { Injectable } from '@nestjs/common'; +import { + PowerClampDailyEntity, + PowerClampHourlyEntity, + PowerClampMonthlyEntity, +} from '../entities/power-clamp.entity'; + +@Injectable() +export class PowerClampHourlyRepository extends Repository { + constructor(private dataSource: DataSource) { + super(PowerClampHourlyEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class PowerClampDailyRepository extends Repository { + constructor(private dataSource: DataSource) { + super(PowerClampDailyEntity, dataSource.createEntityManager()); + } +} + +@Injectable() +export class PowerClampMonthlyRepository extends Repository { + constructor(private dataSource: DataSource) { + super(PowerClampMonthlyEntity, dataSource.createEntityManager()); + } +} diff --git a/libs/common/src/sql/procedures/fact_energy_consumed/fact_daily_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_energy_consumed/fact_daily_energy_consumed_procedure.sql new file mode 100644 index 0000000..c549891 --- /dev/null +++ b/libs/common/src/sql/procedures/fact_energy_consumed/fact_daily_energy_consumed_procedure.sql @@ -0,0 +1,111 @@ +WITH params AS ( + SELECT + $1::uuid AS device_id, + $2::date AS target_date +), +total_energy AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time) AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumed' + AND log.device_id = params.device_id + AND log.event_time::date = params.target_date + GROUP BY 1,2,3,4,5 +), +energy_phase_A AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time) AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedA' + AND log.device_id = params.device_id + AND log.event_time::date = params.target_date + GROUP BY 1,2,3,4,5 +), +energy_phase_B AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time) AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedB' + AND log.device_id = params.device_id + AND log.event_time::date = params.target_date + GROUP BY 1,2,3,4,5 +), +energy_phase_C AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time) AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedC' + AND log.device_id = params.device_id + AND log.event_time::date = params.target_date + GROUP BY 1,2,3,4,5 +), +final_data AS ( + SELECT + t.device_id, + t.date, + t.event_year::text, + t.event_month, + t.hour, + (t.max_value - t.min_value) AS energy_consumed_kW, + (a.max_value - a.min_value) AS energy_consumed_A, + (b.max_value - b.min_value) AS energy_consumed_B, + (c.max_value - c.min_value) AS energy_consumed_C + FROM total_energy t + JOIN energy_phase_A a ON t.device_id = a.device_id AND t.date = a.date AND t.hour = a.hour + JOIN energy_phase_B b ON t.device_id = b.device_id AND t.date = b.date AND t.hour = b.hour + JOIN energy_phase_C c ON t.device_id = c.device_id AND t.date = c.date AND t.hour = c.hour +) + +-- Final upsert into daily table +INSERT INTO public."power-clamp-energy-consumed-daily" ( + device_uuid, + energy_consumed_kw, + energy_consumed_a, + energy_consumed_b, + energy_consumed_c, + date +) +SELECT + device_id, + SUM(CAST(energy_consumed_kW AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_A AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_B AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_C AS NUMERIC))::VARCHAR, + date +FROM final_data +GROUP BY device_id, date +ON CONFLICT (device_uuid, date) DO UPDATE +SET + energy_consumed_kw = EXCLUDED.energy_consumed_kw, + energy_consumed_a = EXCLUDED.energy_consumed_a, + energy_consumed_b = EXCLUDED.energy_consumed_b, + energy_consumed_c = EXCLUDED.energy_consumed_c; + + + + + diff --git a/libs/common/src/sql/procedures/fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql new file mode 100644 index 0000000..ffefc4f --- /dev/null +++ b/libs/common/src/sql/procedures/fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql @@ -0,0 +1,113 @@ +WITH params AS ( + SELECT + $1::uuid AS device_id, + $2::date AS target_date, + $3::text AS target_hour +), +total_energy AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time)::text AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumed' + AND log.device_id = params.device_id + AND log.event_time::date = params.target_date + AND EXTRACT(HOUR FROM log.event_time)::text = params.target_hour + GROUP BY 1,2,3,4,5 +), +energy_phase_A AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time)::text AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedA' + AND log.device_id = params.device_id + AND log.event_time::date = params.target_date + AND EXTRACT(HOUR FROM log.event_time)::text = params.target_hour + GROUP BY 1,2,3,4,5 +), +energy_phase_B AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time)::text AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedB' + AND log.device_id = params.device_id + AND log.event_time::date = params.target_date + AND EXTRACT(HOUR FROM log.event_time)::text = params.target_hour + GROUP BY 1,2,3,4,5 +), +energy_phase_C AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time)::text AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedC' + AND log.device_id = params.device_id + AND log.event_time::date = params.target_date + AND EXTRACT(HOUR FROM log.event_time)::text = params.target_hour + GROUP BY 1,2,3,4,5 +), +final_data AS ( + SELECT + t.device_id, + t.date, + t.event_year::text, + t.event_month, + t.hour, + (t.max_value - t.min_value) AS energy_consumed_kW, + (a.max_value - a.min_value) AS energy_consumed_A, + (b.max_value - b.min_value) AS energy_consumed_B, + (c.max_value - c.min_value) AS energy_consumed_C + FROM total_energy t + JOIN energy_phase_A a ON t.device_id = a.device_id AND t.date = a.date AND t.hour = a.hour + JOIN energy_phase_B b ON t.device_id = b.device_id AND t.date = b.date AND t.hour = b.hour + JOIN energy_phase_C c ON t.device_id = c.device_id AND t.date = c.date AND t.hour = c.hour +) + +-- UPSERT into hourly table +INSERT INTO public."power-clamp-energy-consumed-hourly" ( + device_uuid, + energy_consumed_kw, + energy_consumed_a, + energy_consumed_b, + energy_consumed_c, + date, + hour +) +SELECT + device_id, + SUM(CAST(energy_consumed_kW AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_A AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_B AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_C AS NUMERIC))::VARCHAR, + date, + hour +FROM final_data +GROUP BY 1,6,7 +ON CONFLICT (device_uuid, date, hour) DO UPDATE +SET + energy_consumed_kw = EXCLUDED.energy_consumed_kw, + energy_consumed_a = EXCLUDED.energy_consumed_a, + energy_consumed_b = EXCLUDED.energy_consumed_b, + energy_consumed_c = EXCLUDED.energy_consumed_c; \ No newline at end of file diff --git a/libs/common/src/sql/procedures/fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql new file mode 100644 index 0000000..0e69d60 --- /dev/null +++ b/libs/common/src/sql/procedures/fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql @@ -0,0 +1,107 @@ +WITH params AS ( + SELECT + $1::uuid AS device_id, + $2::text AS target_month -- Format should match 'MM-YYYY' +), +total_energy AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time) AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumed' + AND log.device_id = params.device_id + AND TO_CHAR(log.event_time, 'MM-YYYY') = params.target_month + GROUP BY 1,2,3,4,5 +), +energy_phase_A AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time) AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedA' + AND log.device_id = params.device_id + AND TO_CHAR(log.event_time, 'MM-YYYY') = params.target_month + GROUP BY 1,2,3,4,5 +), +energy_phase_B AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time) AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedB' + AND log.device_id = params.device_id + AND TO_CHAR(log.event_time, 'MM-YYYY') = params.target_month + GROUP BY 1,2,3,4,5 +), +energy_phase_C AS ( + SELECT + log.device_id, + log.event_time::date AS date, + EXTRACT(HOUR FROM log.event_time) AS hour, + TO_CHAR(log.event_time, 'MM-YYYY') AS event_month, + EXTRACT(YEAR FROM log.event_time)::int AS event_year, + MIN(log.value)::integer AS min_value, + MAX(log.value)::integer AS max_value + FROM "device-status-log" log, params + WHERE log.code = 'EnergyConsumedC' + AND log.device_id = params.device_id + AND TO_CHAR(log.event_time, 'MM-YYYY') = params.target_month + GROUP BY 1,2,3,4,5 +), +final_data AS ( + SELECT + t.device_id, + t.date, + t.event_year::text, + t.event_month, + t.hour, + (t.max_value - t.min_value) AS energy_consumed_kW, + (a.max_value - a.min_value) AS energy_consumed_A, + (b.max_value - b.min_value) AS energy_consumed_B, + (c.max_value - c.min_value) AS energy_consumed_C + FROM total_energy t + JOIN energy_phase_A a ON t.device_id = a.device_id AND t.date = a.date AND t.hour = a.hour + JOIN energy_phase_B b ON t.device_id = b.device_id AND t.date = b.date AND t.hour = b.hour + JOIN energy_phase_C c ON t.device_id = c.device_id AND t.date = c.date AND t.hour = c.hour +) + +-- Final monthly UPSERT +INSERT INTO public."power-clamp-energy-consumed-monthly" ( + device_uuid, + energy_consumed_kw, + energy_consumed_a, + energy_consumed_b, + energy_consumed_c, + month +) +SELECT + device_id, + SUM(CAST(energy_consumed_kW AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_A AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_B AS NUMERIC))::VARCHAR, + SUM(CAST(energy_consumed_C AS NUMERIC))::VARCHAR, + event_month +FROM final_data +GROUP BY device_id, event_month +ON CONFLICT (device_uuid, month) DO UPDATE +SET + energy_consumed_kw = EXCLUDED.energy_consumed_kw, + energy_consumed_a = EXCLUDED.energy_consumed_a, + energy_consumed_b = EXCLUDED.energy_consumed_b, + energy_consumed_c = EXCLUDED.energy_consumed_c; + diff --git a/src/commission-device/commission-device.module.ts b/src/commission-device/commission-device.module.ts index ebf0516..71c93bd 100644 --- a/src/commission-device/commission-device.module.ts +++ b/src/commission-device/commission-device.module.ts @@ -21,6 +21,13 @@ import { import { AutomationRepository } from '@app/common/modules/automation/repositories'; import { CommunityRepository } from '@app/common/modules/community/repositories'; import { SubspaceRepository } from '@app/common/modules/space/repositories/subspace.repository'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], @@ -43,6 +50,11 @@ import { SubspaceRepository } from '@app/common/modules/space/repositories/subsp AutomationRepository, CommunityRepository, SubspaceRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [], }) diff --git a/src/community/community.module.ts b/src/community/community.module.ts index 81bef0d..289ece4 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -56,6 +56,13 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { AutomationRepository } from '@app/common/modules/automation/repositories'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], @@ -102,6 +109,11 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie SceneIconRepository, SceneRepository, AutomationRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [CommunityService, SpacePermissionService], }) diff --git a/src/door-lock/door.lock.module.ts b/src/door-lock/door.lock.module.ts index 2e00893..adbfceb 100644 --- a/src/door-lock/door.lock.module.ts +++ b/src/door-lock/door.lock.module.ts @@ -20,6 +20,13 @@ import { import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { AutomationRepository } from '@app/common/modules/automation/repositories'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], controllers: [DoorLockController], @@ -40,6 +47,11 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; SceneRepository, SceneDeviceRepository, AutomationRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [DoorLockService], }) diff --git a/src/group/group.module.ts b/src/group/group.module.ts index 6b3f0b0..a903373 100644 --- a/src/group/group.module.ts +++ b/src/group/group.module.ts @@ -18,6 +18,13 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { AutomationRepository } from '@app/common/modules/automation/repositories'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], controllers: [GroupController], @@ -37,6 +44,11 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie SceneIconRepository, SceneRepository, AutomationRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [GroupService], }) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 4ce83d4..fdf5251 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -76,6 +76,13 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { AutomationRepository } from '@app/common/modules/automation/repositories'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule, CommunityModule], @@ -140,6 +147,11 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie SceneIconRepository, SceneRepository, AutomationRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [InviteUserService], }) diff --git a/src/power-clamp/controllers/power-clamp.controller.ts b/src/power-clamp/controllers/power-clamp.controller.ts index 621893d..347013c 100644 --- a/src/power-clamp/controllers/power-clamp.controller.ts +++ b/src/power-clamp/controllers/power-clamp.controller.ts @@ -1,9 +1,11 @@ -import { Controller, Get, UseGuards } from '@nestjs/common'; +import { Controller, Get, Param, Query, 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'; +import { GetPowerClampDto } from '../dto/get-power-clamp.dto'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; @ApiTags('Power Clamp Module') @Controller({ @@ -15,12 +17,18 @@ export class PowerClampController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get() + @Get(':powerClampUuid/historical') @ApiOperation({ summary: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_SUMMARY, description: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_DESCRIPTION, }) - async getPowerClampData() { - return await this.powerClampService.getPowerClampData(); + async getPowerClampData( + @Param('powerClampUuid') powerClampUuid: string, + @Query() params: GetPowerClampDto, + ): Promise { + return await this.powerClampService.getPowerClampData( + powerClampUuid, + params, + ); } } diff --git a/src/power-clamp/dto/get-power-clamp.dto.ts b/src/power-clamp/dto/get-power-clamp.dto.ts new file mode 100644 index 0000000..baa6839 --- /dev/null +++ b/src/power-clamp/dto/get-power-clamp.dto.ts @@ -0,0 +1,35 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsDateString, Matches } from 'class-validator'; + +export class GetPowerClampDto { + @ApiPropertyOptional({ + description: 'Input date in ISO format (YYYY-MM-DD) to filter the data', + example: '2025-04-23', + required: false, + }) + @IsOptional() + @IsDateString() + dayDate?: string; + + @ApiPropertyOptional({ + description: 'Month and year in format YYYY-MM', + example: '2025-03', + required: false, + }) + @Matches(/^\d{4}-(0[1-9]|1[0-2])$/, { + message: 'monthDate must be in YYYY-MM format', + }) + @IsOptional() + monthDate?: string; + + @ApiPropertyOptional({ + description: 'Input year in YYYY format to filter the data', + example: '2025', + required: false, + }) + @IsOptional() + @Matches(/^\d{4}$/, { + message: 'Year must be in YYYY format', + }) + year?: string; +} diff --git a/src/power-clamp/power-clamp.module.ts b/src/power-clamp/power-clamp.module.ts index 528d920..3f2a713 100644 --- a/src/power-clamp/power-clamp.module.ts +++ b/src/power-clamp/power-clamp.module.ts @@ -2,11 +2,20 @@ 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'; +import { + PowerClampDailyRepository, + PowerClampHourlyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; @Module({ imports: [ConfigModule], controllers: [PowerClampController], - providers: [PowerClampService, SqlLoaderService], + providers: [ + PowerClampService, + PowerClampDailyRepository, + PowerClampHourlyRepository, + PowerClampMonthlyRepository, + ], exports: [PowerClampService], }) export class PowerClampModule {} diff --git a/src/power-clamp/services/power-clamp.service.ts b/src/power-clamp/services/power-clamp.service.ts index bd03f0c..5a4a8f8 100644 --- a/src/power-clamp/services/power-clamp.service.ts +++ b/src/power-clamp/services/power-clamp.service.ts @@ -1,27 +1,88 @@ -import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; -import { Injectable } from '@nestjs/common'; -import { DataSource } from 'typeorm'; +import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { GetPowerClampDto } from '../dto/get-power-clamp.dto'; +import { + PowerClampDailyRepository, + PowerClampHourlyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @Injectable() export class PowerClampService { constructor( - private readonly sqlLoader: SqlLoaderService, - private readonly dataSource: DataSource, + private readonly powerClampDailyRepository: PowerClampDailyRepository, + private readonly powerClampHourlyRepository: PowerClampHourlyRepository, + private readonly powerClampMonthlyRepository: PowerClampMonthlyRepository, ) {} - async getPowerClampData() { - const sql = this.sqlLoader.loadQuery( - 'fact_daily_energy_consumed', - 'fact_daily_energy_consumed', - ); - return this.dataSource.manager.query(sql); + async getPowerClampData(powerClampUuid: string, params: GetPowerClampDto) { + const { dayDate, monthDate, year } = params; + + try { + if (dayDate) { + const data = await this.powerClampHourlyRepository + .createQueryBuilder('hourly') + .where('hourly.deviceUuid = :deviceUuid', { + deviceUuid: powerClampUuid, + }) + .andWhere('hourly.date = :date', { date: dayDate }) + .orderBy('CAST(hourly.hour AS INTEGER)', 'ASC') + .getMany(); + + return this.buildResponse( + `Power clamp data for day ${dayDate} fetched successfully`, + data, + ); + } + + if (monthDate) { + const data = await this.powerClampDailyRepository + .createQueryBuilder('daily') + .where('daily.deviceUuid = :deviceUuid', { + deviceUuid: powerClampUuid, + }) + .andWhere("TO_CHAR(daily.date, 'YYYY-MM') = :monthDate", { + monthDate, + }) + .orderBy('daily.date', 'ASC') + .getMany(); + + return this.buildResponse( + `Power clamp data for month ${monthDate} fetched successfully`, + data, + ); + } + + if (year) { + const data = await this.powerClampMonthlyRepository + .createQueryBuilder('monthly') + .where('monthly.deviceUuid = :deviceUuid', { + deviceUuid: powerClampUuid, + }) + .andWhere('RIGHT(monthly.month, 4) = :year', { year }) + .orderBy('monthly.month', 'ASC') + .getMany(); + + return this.buildResponse( + `Power clamp data for year ${year} fetched successfully`, + data, + ); + } + + return this.buildResponse(`Power clamp data fetched successfully`, []); + } catch (error) { + throw new HttpException( + 'Error fetching power clamp data', + HttpStatus.INTERNAL_SERVER_ERROR, + ); + } } - async getEnergyConsumed(code: string) { - const sql = this.sqlLoader.loadQuery( - 'energy', - 'energy_consumed_with_params', - ); - return this.dataSource.manager.query(sql, [code]); + private buildResponse(message: string, data: any[]) { + return new SuccessResponseDto({ + message, + data, + statusCode: HttpStatus.OK, + }); } } diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 6c8bef8..e52294d 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -60,6 +60,13 @@ import { SceneRepository, } from '@app/common/modules/scene/repositories'; import { AutomationRepository } from '@app/common/modules/automation/repositories'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { + PowerClampDailyRepository, + PowerClampHourlyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; const CommandHandlers = [CreateOrphanSpaceHandler]; @@ -112,6 +119,11 @@ const CommandHandlers = [CreateOrphanSpaceHandler]; SceneRepository, AutomationRepository, SubspaceModelProductAllocationRepoitory, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [ProjectService, CqrsModule], }) diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index 3f22918..e5b4426 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -58,6 +58,13 @@ import { } from '@app/common/modules/scene/repositories'; import { AutomationRepository } from '@app/common/modules/automation/repositories'; import { CommunityModule } from 'src/community/community.module'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; const CommandHandlers = [ PropogateUpdateSpaceModelHandler, @@ -112,6 +119,11 @@ const CommandHandlers = [ SpaceProductAllocationRepository, SubspaceProductAllocationRepository, SubSpaceService, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [CqrsModule, SpaceModelService], }) diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 3808a0e..13b4ecc 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -83,6 +83,13 @@ import { SubspaceModelProductAllocationService } from 'src/space-model/services/ import { SpaceProductAllocationService } from './services/space-product-allocation.service'; import { SubspaceProductAllocationService } from './services/subspace/subspace-product-allocation.service'; import { SpaceValidationController } from './controllers/space-validation.controller'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; export const CommandHandlers = [DisableSpaceHandler]; @@ -154,6 +161,11 @@ export const CommandHandlers = [DisableSpaceHandler]; SubspaceProductAllocationService, SpaceProductAllocationRepository, SubspaceProductAllocationRepository, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + PowerClampService, + SqlLoaderService, ], exports: [SpaceService], }) diff --git a/src/vistor-password/visitor-password.module.ts b/src/vistor-password/visitor-password.module.ts index 778abd4..061f79d 100644 --- a/src/vistor-password/visitor-password.module.ts +++ b/src/vistor-password/visitor-password.module.ts @@ -22,6 +22,13 @@ import { import { SceneDeviceRepository } from '@app/common/modules/scene-device/repositories'; import { AutomationRepository } from '@app/common/modules/automation/repositories'; import { ProjectRepository } from '@app/common/modules/project/repositiories'; +import { PowerClampService } from '@app/common/helper/services/power.clamp.service'; +import { + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule], controllers: [VisitorPasswordController], @@ -43,6 +50,11 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; SceneDeviceRepository, AutomationRepository, ProjectRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [VisitorPasswordService], })