From 8228ccc29329c7e05d3c0aa2b267bba12afb96f4 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:08:51 +0300 Subject: [PATCH 01/10] feat: add power clamp entities, DTOs, and repository for energy consumption tracking --- libs/common/src/database/database.module.ts | 8 ++ .../services/devices-status.service.ts | 14 +++ .../modules/device/entities/device.entity.ts | 4 +- .../src/modules/power-clamp/dtos/index.ts | 1 + .../power-clamp/dtos/power-clamp.dto.ts | 43 +++++++++ .../src/modules/power-clamp/entities/index.ts | 1 + .../entities/power-clamp.entity.ts | 92 +++++++++++++++++++ .../power-clamp.repository.module.ts | 11 +++ .../modules/power-clamp/repositories/index.ts | 1 + .../repositories/power-clamp.repository.ts | 28 ++++++ 10 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 libs/common/src/modules/power-clamp/dtos/index.ts create mode 100644 libs/common/src/modules/power-clamp/dtos/power-clamp.dto.ts create mode 100644 libs/common/src/modules/power-clamp/entities/index.ts create mode 100644 libs/common/src/modules/power-clamp/entities/power-clamp.entity.ts create mode 100644 libs/common/src/modules/power-clamp/power-clamp.repository.module.ts create mode 100644 libs/common/src/modules/power-clamp/repositories/index.ts create mode 100644 libs/common/src/modules/power-clamp/repositories/power-clamp.repository.ts diff --git a/libs/common/src/database/database.module.ts b/libs/common/src/database/database.module.ts index a25cf35..c2f944e 100644 --- a/libs/common/src/database/database.module.ts +++ b/libs/common/src/database/database.module.ts @@ -43,6 +43,11 @@ import { SubspaceProductAllocationEntity } from '../modules/space/entities/subsp import { SubspaceEntity } from '../modules/space/entities/subspace/subspace.entity'; import { TagEntity } from '../modules/space/entities/tag.entity'; import { ClientEntity } from '../modules/client/entities'; +import { + PowerClampDailyEntity, + PowerClampHourlyEntity, + PowerClampMonthlyEntity, +} from '../modules/power-clamp/entities/power-clamp.entity'; @Module({ imports: [ TypeOrmModule.forRootAsync({ @@ -95,6 +100,9 @@ import { ClientEntity } from '../modules/client/entities'; 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/services/devices-status.service.ts b/libs/common/src/firebase/devices-status/services/devices-status.service.ts index 9ef06a0..23db19e 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,7 @@ import { runTransaction, } from 'firebase/database'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; +import { ProductType } from '@app/common/constants/product-type.enum'; @Injectable() export class DeviceStatusFirebaseService { private tuya: TuyaContext; @@ -74,6 +75,19 @@ export class DeviceStatusFirebaseService { const device = await this.getDeviceByDeviceTuyaUuid( addDeviceStatusDto.deviceTuyaUuid, ); + if (device.productDevice.prodType === ProductType.PC) { + const energyStatus = addDeviceStatusDto.status.find( + (status) => + status.code === 'EnergyConsumed' || + status.code === 'EnergyConsumedA' || + status.code === 'EnergyConsumedB' || + status.code === 'EnergyConsumedC', + ); + + if (energyStatus) { + console.log(device.productDevice.prodType, addDeviceStatusDto.status); + } + } if (device?.uuid) { return await this.createDeviceStatusFirebase({ 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..150379d --- /dev/null +++ b/libs/common/src/modules/power-clamp/entities/power-clamp.entity.ts @@ -0,0 +1,92 @@ +import { Column, Entity, ManyToOne } 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' }) +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' }) +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' }) +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()); + } +} From fde34dcf9c9912244d5bcbe0a8d91ce4748d6079 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:13:53 +0300 Subject: [PATCH 02/10] fix: update log output in energy status check for better debugging --- .../firebase/devices-status/services/devices-status.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 23db19e..cd744ca 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 @@ -85,7 +85,7 @@ export class DeviceStatusFirebaseService { ); if (energyStatus) { - console.log(device.productDevice.prodType, addDeviceStatusDto.status); + console.log(device.productDevice.prodType, addDeviceStatusDto.log); } } From 531f93d42c81e78e1d0425077985d95fd2d654cd Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Wed, 23 Apr 2025 16:52:54 +0300 Subject: [PATCH 03/10] fix: enhance log output in device status to include UUID and timestamp --- .../devices-status/services/devices-status.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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 cd744ca..4f6b94b 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 @@ -85,7 +85,12 @@ export class DeviceStatusFirebaseService { ); if (energyStatus) { - console.log(device.productDevice.prodType, addDeviceStatusDto.log); + console.log( + device.productDevice.prodType, + device.uuid, + addDeviceStatusDto.log, + new Date().toLocaleDateString('en-CA'), // Format as YYYY-MM-DD + ); } } From 7f43ef5de5c6755b4c41f64dd9a90093568128dd Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:14:58 +0300 Subject: [PATCH 04/10] feat: integrate PowerClamp service and repositories across multiple modules --- .../firebase/devices-status/devices-status.module.ts | 10 ++++++++++ .../modules/power-clamp/entities/power-clamp.entity.ts | 5 ++++- src/commission-device/commission-device.module.ts | 10 ++++++++++ src/community/community.module.ts | 10 ++++++++++ src/door-lock/door.lock.module.ts | 10 ++++++++++ src/group/group.module.ts | 10 ++++++++++ src/invite-user/invite-user.module.ts | 10 ++++++++++ src/project/project.module.ts | 10 ++++++++++ src/space-model/space-model.module.ts | 10 ++++++++++ src/space/space.module.ts | 10 ++++++++++ src/vistor-password/visitor-password.module.ts | 10 ++++++++++ 11 files changed, 104 insertions(+), 1 deletion(-) 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..544c419 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,22 @@ 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'; @Module({ providers: [ DeviceStatusFirebaseService, DeviceRepository, DeviceStatusLogRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], controllers: [DeviceStatusFirebaseController], exports: [DeviceStatusFirebaseService, DeviceStatusLogRepository], 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 index 150379d..51ea4c5 100644 --- a/libs/common/src/modules/power-clamp/entities/power-clamp.entity.ts +++ b/libs/common/src/modules/power-clamp/entities/power-clamp.entity.ts @@ -1,9 +1,10 @@ -import { Column, Entity, ManyToOne } from 'typeorm'; +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; @@ -35,6 +36,7 @@ export class PowerClampHourlyEntity extends AbstractEntity { } } @Entity({ name: 'power-clamp-energy-consumed-daily' }) +@Unique(['deviceUuid', 'date']) export class PowerClampDailyEntity extends AbstractEntity { @Column({ nullable: false }) public deviceUuid: string; @@ -63,6 +65,7 @@ export class PowerClampDailyEntity extends AbstractEntity { } } @Entity({ name: 'power-clamp-energy-consumed-monthly' }) +@Unique(['deviceUuid', 'month']) export class PowerClampMonthlyEntity extends AbstractEntity { @Column({ nullable: false }) public deviceUuid: string; diff --git a/src/commission-device/commission-device.module.ts b/src/commission-device/commission-device.module.ts index ebf0516..203eb2c 100644 --- a/src/commission-device/commission-device.module.ts +++ b/src/commission-device/commission-device.module.ts @@ -21,6 +21,12 @@ 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'; @Module({ imports: [ConfigModule, SpaceRepositoryModule], @@ -43,6 +49,10 @@ import { SubspaceRepository } from '@app/common/modules/space/repositories/subsp AutomationRepository, CommunityRepository, SubspaceRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], exports: [], }) diff --git a/src/community/community.module.ts b/src/community/community.module.ts index 81bef0d..b32d130 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -56,6 +56,12 @@ 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'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], @@ -102,6 +108,10 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie SceneIconRepository, SceneRepository, AutomationRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], exports: [CommunityService, SpacePermissionService], }) diff --git a/src/door-lock/door.lock.module.ts b/src/door-lock/door.lock.module.ts index 2e00893..0451ae4 100644 --- a/src/door-lock/door.lock.module.ts +++ b/src/door-lock/door.lock.module.ts @@ -20,6 +20,12 @@ 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'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], controllers: [DoorLockController], @@ -40,6 +46,10 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; SceneRepository, SceneDeviceRepository, AutomationRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], exports: [DoorLockService], }) diff --git a/src/group/group.module.ts b/src/group/group.module.ts index 6b3f0b0..02d1ab4 100644 --- a/src/group/group.module.ts +++ b/src/group/group.module.ts @@ -18,6 +18,12 @@ 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'; @Module({ imports: [ConfigModule, DeviceRepositoryModule], controllers: [GroupController], @@ -37,6 +43,10 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie SceneIconRepository, SceneRepository, AutomationRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], exports: [GroupService], }) diff --git a/src/invite-user/invite-user.module.ts b/src/invite-user/invite-user.module.ts index 4ce83d4..87caf92 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -76,6 +76,12 @@ 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'; @Module({ imports: [ConfigModule, InviteUserRepositoryModule, CommunityModule], @@ -140,6 +146,10 @@ import { AutomationRepository } from '@app/common/modules/automation/repositorie SceneIconRepository, SceneRepository, AutomationRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], exports: [InviteUserService], }) diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 6c8bef8..15625ab 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -60,6 +60,12 @@ 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'; const CommandHandlers = [CreateOrphanSpaceHandler]; @@ -112,6 +118,10 @@ const CommandHandlers = [CreateOrphanSpaceHandler]; SceneRepository, AutomationRepository, SubspaceModelProductAllocationRepoitory, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], exports: [ProjectService, CqrsModule], }) diff --git a/src/space-model/space-model.module.ts b/src/space-model/space-model.module.ts index 3f22918..80db39b 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -58,6 +58,12 @@ 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'; const CommandHandlers = [ PropogateUpdateSpaceModelHandler, @@ -112,6 +118,10 @@ const CommandHandlers = [ SpaceProductAllocationRepository, SubspaceProductAllocationRepository, SubSpaceService, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], exports: [CqrsModule, SpaceModelService], }) diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 3808a0e..2e3f8a1 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -83,6 +83,12 @@ 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'; export const CommandHandlers = [DisableSpaceHandler]; @@ -154,6 +160,10 @@ export const CommandHandlers = [DisableSpaceHandler]; SubspaceProductAllocationService, SpaceProductAllocationRepository, SubspaceProductAllocationRepository, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, + PowerClampService, ], exports: [SpaceService], }) diff --git a/src/vistor-password/visitor-password.module.ts b/src/vistor-password/visitor-password.module.ts index 778abd4..e16adf6 100644 --- a/src/vistor-password/visitor-password.module.ts +++ b/src/vistor-password/visitor-password.module.ts @@ -22,6 +22,12 @@ 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'; @Module({ imports: [ConfigModule, DeviceRepositoryModule, DoorLockModule], controllers: [VisitorPasswordController], @@ -43,6 +49,10 @@ import { ProjectRepository } from '@app/common/modules/project/repositiories'; SceneDeviceRepository, AutomationRepository, ProjectRepository, + PowerClampService, + PowerClampHourlyRepository, + PowerClampDailyRepository, + PowerClampMonthlyRepository, ], exports: [VisitorPasswordService], }) From 92f908c0fb9ad69958f02e1991e41aadddfde6a9 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:15:45 +0300 Subject: [PATCH 05/10] feat: implement PowerClampService for managing hourly energy consumption data --- .../helper/services/power.clamp.service.ts | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 libs/common/src/helper/services/power.clamp.service.ts 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..258a40b --- /dev/null +++ b/libs/common/src/helper/services/power.clamp.service.ts @@ -0,0 +1,109 @@ +import { Injectable } from '@nestjs/common'; +import { + PowerClampDailyRepository, + PowerClampHourlyRepository, + PowerClampMonthlyRepository, +} from '@app/common/modules/power-clamp/repositories'; + +@Injectable() +export class PowerClampService { + constructor( + private readonly powerClampHourlyRepository: PowerClampHourlyRepository, + private readonly powerClampDailyRepository: PowerClampDailyRepository, + private readonly powerClampMonthlyRepository: PowerClampMonthlyRepository, + ) {} + async insertOrUpdatePowerClamp( + deviceUuid: string, + logData: any, + ): Promise { + try { + await this.insertOrUpdateHourly(deviceUuid, logData); + } catch (err) { + console.error('Failed to insert or update hourly data', err); + throw err; + } + } + + async insertOrUpdateHourly( + deviceUuid: string, + logData: LogData, + ): Promise { + try { + const currentDate = new Date().toLocaleDateString('en-CA'); + const currentHour = new Date().getHours().toString(); + + // First try to update existing record + const existingData = await this.powerClampHourlyRepository.findOne({ + where: { + deviceUuid, + date: currentDate, + hour: currentHour, + }, + }); + + if (existingData) { + // Create update object only with values that exist in logData + const updateData: Partial = {}; + + const hasProperty = (code: string) => + logData.properties.some((p) => p.code === code); + const getValue = (code: string) => { + const prop = logData.properties.find((p) => p.code === code); + return prop ? Number(prop.value) : undefined; + }; + + if (hasProperty('EnergyConsumedA')) { + updateData.energyConsumedA = String(getValue('EnergyConsumedA')); + } + if (hasProperty('EnergyConsumedB')) { + updateData.energyConsumedB = String(getValue('EnergyConsumedB')); + } + if (hasProperty('EnergyConsumedC')) { + updateData.energyConsumedC = String(getValue('EnergyConsumedC')); + } + if (hasProperty('EnergyConsumed')) { + updateData.energyConsumedKw = String(getValue('EnergyConsumed')); + } + + if (Object.keys(updateData).length > 0) { + await this.powerClampHourlyRepository.update( + existingData.uuid, + updateData, + ); + } + } else { + // Insert new record with all required fields + const getValue = (code: string) => { + const prop = logData.properties.find((p) => p.code === code); + return prop ? Number(prop.value) : 0; // Default to 0 for required fields + }; + + await this.powerClampHourlyRepository.insert({ + deviceUuid, + date: currentDate, + hour: currentHour, + energyConsumedA: String(getValue('EnergyConsumedA')), + energyConsumedB: String(getValue('EnergyConsumedB')), + energyConsumedC: String(getValue('EnergyConsumedC')), + energyConsumedKw: String(getValue('EnergyConsumed')), + }); + } + } catch (err) { + console.error('Failed to insert or update hourly data', err); + throw err; + } + } +} +interface EnergyProperties { + code: string; + dpId: number; + time: number; + value: string | number; +} + +interface LogData { + devId: string; + dataId: string; + productId: string; + properties: EnergyProperties[]; +} From 3c5b97f2f94d268adef936564d2b4bc461059379 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:22:13 +0300 Subject: [PATCH 06/10] feat: add SQL procedures for daily, hourly, and monthly energy consumption data --- .../fact_daily_energy_consumed_procedure.sql | 111 +++++++++++++++++ .../fact_hourly_energy_consumed_procedure.sql | 113 ++++++++++++++++++ ...fact_monthly_energy_consumed_procedure.sql | 107 +++++++++++++++++ 3 files changed, 331 insertions(+) create mode 100644 libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_daily_energy_consumed_procedure.sql create mode 100644 libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_hourly_energy_consumed_procedure.sql create mode 100644 libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_monthly_energy_consumed_procedure.sql diff --git a/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_daily_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_daily_energy_consumed_procedure.sql new file mode 100644 index 0000000..82725d0 --- /dev/null +++ b/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_daily_energy_consumed_procedure.sql @@ -0,0 +1,111 @@ +WITH params AS ( + SELECT + 'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, + '2025-04-23'::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_daily_energy_consumed/fact_hourly_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_hourly_energy_consumed_procedure.sql new file mode 100644 index 0000000..ff17747 --- /dev/null +++ b/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_hourly_energy_consumed_procedure.sql @@ -0,0 +1,113 @@ +WITH params AS ( + SELECT + 'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, + '2025-04-23'::date AS target_date, + '14'::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_daily_energy_consumed/fact_monthly_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_monthly_energy_consumed_procedure.sql new file mode 100644 index 0000000..cdf6134 --- /dev/null +++ b/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_monthly_energy_consumed_procedure.sql @@ -0,0 +1,107 @@ +WITH params AS ( + SELECT + 'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, + '03-2025'::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; + From f3fd6646a1c6e24a5287b3a449fc215f3570512f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 24 Apr 2025 02:31:35 +0300 Subject: [PATCH 07/10] feat: add SqlLoaderService to multiple modules for improved SQL data handling --- .../common/src/firebase/devices-status/devices-status.module.ts | 2 ++ src/commission-device/commission-device.module.ts | 2 ++ src/community/community.module.ts | 2 ++ src/door-lock/door.lock.module.ts | 2 ++ src/group/group.module.ts | 2 ++ src/invite-user/invite-user.module.ts | 2 ++ src/project/project.module.ts | 2 ++ src/space-model/space-model.module.ts | 2 ++ src/space/space.module.ts | 2 ++ src/vistor-password/visitor-password.module.ts | 2 ++ 10 files changed, 20 insertions(+) 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 544c419..217a4d8 100644 --- a/libs/common/src/firebase/devices-status/devices-status.module.ts +++ b/libs/common/src/firebase/devices-status/devices-status.module.ts @@ -9,6 +9,7 @@ import { PowerClampDailyRepository, PowerClampMonthlyRepository, } from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ providers: [ @@ -19,6 +20,7 @@ import { PowerClampHourlyRepository, PowerClampDailyRepository, PowerClampMonthlyRepository, + SqlLoaderService, ], controllers: [DeviceStatusFirebaseController], exports: [DeviceStatusFirebaseService, DeviceStatusLogRepository], diff --git a/src/commission-device/commission-device.module.ts b/src/commission-device/commission-device.module.ts index 203eb2c..71c93bd 100644 --- a/src/commission-device/commission-device.module.ts +++ b/src/commission-device/commission-device.module.ts @@ -27,6 +27,7 @@ import { 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], @@ -53,6 +54,7 @@ import { PowerClampService } from '@app/common/helper/services/power.clamp.servi PowerClampHourlyRepository, PowerClampDailyRepository, PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [], }) diff --git a/src/community/community.module.ts b/src/community/community.module.ts index b32d130..289ece4 100644 --- a/src/community/community.module.ts +++ b/src/community/community.module.ts @@ -62,6 +62,7 @@ import { PowerClampDailyRepository, PowerClampMonthlyRepository, } from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; @Module({ imports: [ConfigModule, SpaceRepositoryModule, UserRepositoryModule], @@ -112,6 +113,7 @@ import { 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 0451ae4..adbfceb 100644 --- a/src/door-lock/door.lock.module.ts +++ b/src/door-lock/door.lock.module.ts @@ -26,6 +26,7 @@ import { 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], @@ -50,6 +51,7 @@ import { PowerClampHourlyRepository, PowerClampDailyRepository, PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [DoorLockService], }) diff --git a/src/group/group.module.ts b/src/group/group.module.ts index 02d1ab4..a903373 100644 --- a/src/group/group.module.ts +++ b/src/group/group.module.ts @@ -24,6 +24,7 @@ import { 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], @@ -47,6 +48,7 @@ import { 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 87caf92..fdf5251 100644 --- a/src/invite-user/invite-user.module.ts +++ b/src/invite-user/invite-user.module.ts @@ -82,6 +82,7 @@ import { 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], @@ -150,6 +151,7 @@ import { PowerClampService } from '@app/common/helper/services/power.clamp.servi PowerClampHourlyRepository, PowerClampDailyRepository, PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [InviteUserService], }) diff --git a/src/project/project.module.ts b/src/project/project.module.ts index 15625ab..e52294d 100644 --- a/src/project/project.module.ts +++ b/src/project/project.module.ts @@ -66,6 +66,7 @@ import { PowerClampHourlyRepository, PowerClampMonthlyRepository, } from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; const CommandHandlers = [CreateOrphanSpaceHandler]; @@ -122,6 +123,7 @@ const CommandHandlers = [CreateOrphanSpaceHandler]; 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 80db39b..e5b4426 100644 --- a/src/space-model/space-model.module.ts +++ b/src/space-model/space-model.module.ts @@ -64,6 +64,7 @@ import { PowerClampDailyRepository, PowerClampMonthlyRepository, } from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; const CommandHandlers = [ PropogateUpdateSpaceModelHandler, @@ -122,6 +123,7 @@ const CommandHandlers = [ PowerClampHourlyRepository, PowerClampDailyRepository, PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [CqrsModule, SpaceModelService], }) diff --git a/src/space/space.module.ts b/src/space/space.module.ts index 2e3f8a1..13b4ecc 100644 --- a/src/space/space.module.ts +++ b/src/space/space.module.ts @@ -89,6 +89,7 @@ import { PowerClampDailyRepository, PowerClampMonthlyRepository, } from '@app/common/modules/power-clamp/repositories'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; export const CommandHandlers = [DisableSpaceHandler]; @@ -164,6 +165,7 @@ export const CommandHandlers = [DisableSpaceHandler]; 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 e16adf6..061f79d 100644 --- a/src/vistor-password/visitor-password.module.ts +++ b/src/vistor-password/visitor-password.module.ts @@ -28,6 +28,7 @@ import { 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], @@ -53,6 +54,7 @@ import { PowerClampHourlyRepository, PowerClampDailyRepository, PowerClampMonthlyRepository, + SqlLoaderService, ], exports: [VisitorPasswordService], }) From 881618a4ee9de32f96e581d2be1dadfff6e8bb1f Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 24 Apr 2025 03:14:03 +0300 Subject: [PATCH 08/10] feat: enhance PowerClamp service with energy consumption procedures and enums --- .../src/constants/power.clamp.enargy.enum.ts | 6 + libs/common/src/constants/sql-query-path.ts | 1 + .../services/devices-status.service.ts | 41 +++--- .../helper/services/power.clamp.service.ts | 138 ++++++------------ .../src/helper/services/sql-loader.service.ts | 10 +- .../fact_daily_energy_consumed_procedure.sql | 4 +- .../fact_hourly_energy_consumed_procedure.sql | 6 +- ...fact_monthly_energy_consumed_procedure.sql | 4 +- .../services/power-clamp.service.ts | 3 + 9 files changed, 87 insertions(+), 126 deletions(-) create mode 100644 libs/common/src/constants/power.clamp.enargy.enum.ts rename libs/common/src/sql/procedures/{fact_daily_energy_consumed => fact_energy_consumed}/fact_daily_energy_consumed_procedure.sql (97%) rename libs/common/src/sql/procedures/{fact_daily_energy_consumed => fact_energy_consumed}/fact_hourly_energy_consumed_procedure.sql (96%) rename libs/common/src/sql/procedures/{fact_daily_energy_consumed => fact_energy_consumed}/fact_monthly_energy_consumed_procedure.sql (96%) 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/firebase/devices-status/services/devices-status.service.ts b/libs/common/src/firebase/devices-status/services/devices-status.service.ts index 4f6b94b..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 @@ -19,6 +19,8 @@ import { } 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; @@ -26,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'); @@ -75,29 +78,12 @@ export class DeviceStatusFirebaseService { const device = await this.getDeviceByDeviceTuyaUuid( addDeviceStatusDto.deviceTuyaUuid, ); - if (device.productDevice.prodType === ProductType.PC) { - const energyStatus = addDeviceStatusDto.status.find( - (status) => - status.code === 'EnergyConsumed' || - status.code === 'EnergyConsumedA' || - status.code === 'EnergyConsumedB' || - status.code === 'EnergyConsumedC', - ); - - if (energyStatus) { - console.log( - device.productDevice.prodType, - device.uuid, - addDeviceStatusDto.log, - new Date().toLocaleDateString('en-CA'), // Format as YYYY-MM-DD - ); - } - } if (device?.uuid) { return await this.createDeviceStatusFirebase({ deviceUuid: device.uuid, ...addDeviceStatusDto, + productType: device.productDevice.prodType, }); } // Return null if device not found or no UUID @@ -235,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 index 258a40b..b0e852f 100644 --- a/libs/common/src/helper/services/power.clamp.service.ts +++ b/libs/common/src/helper/services/power.clamp.service.ts @@ -1,109 +1,61 @@ import { Injectable } from '@nestjs/common'; -import { - PowerClampDailyRepository, - PowerClampHourlyRepository, - PowerClampMonthlyRepository, -} from '@app/common/modules/power-clamp/repositories'; +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 powerClampHourlyRepository: PowerClampHourlyRepository, - private readonly powerClampDailyRepository: PowerClampDailyRepository, - private readonly powerClampMonthlyRepository: PowerClampMonthlyRepository, + private readonly sqlLoader: SqlLoaderService, + private readonly dataSource: DataSource, ) {} - async insertOrUpdatePowerClamp( - deviceUuid: string, - logData: any, - ): Promise { + + async updateEnergyConsumedHistoricalData(deviceUuid: string): Promise { try { - await this.insertOrUpdateHourly(deviceUuid, logData); + 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 hourly data', err); + console.error('Failed to insert or update energy data:', err); throw err; } } - async insertOrUpdateHourly( - deviceUuid: string, - logData: LogData, + private async executeProcedure( + procedureFileName: string, + params: (string | number | null)[], ): Promise { - try { - const currentDate = new Date().toLocaleDateString('en-CA'); - const currentHour = new Date().getHours().toString(); + const query = this.loadQuery(procedureFileName); + await this.dataSource.query(query, params); + } - // First try to update existing record - const existingData = await this.powerClampHourlyRepository.findOne({ - where: { - deviceUuid, - date: currentDate, - hour: currentHour, - }, - }); - - if (existingData) { - // Create update object only with values that exist in logData - const updateData: Partial = {}; - - const hasProperty = (code: string) => - logData.properties.some((p) => p.code === code); - const getValue = (code: string) => { - const prop = logData.properties.find((p) => p.code === code); - return prop ? Number(prop.value) : undefined; - }; - - if (hasProperty('EnergyConsumedA')) { - updateData.energyConsumedA = String(getValue('EnergyConsumedA')); - } - if (hasProperty('EnergyConsumedB')) { - updateData.energyConsumedB = String(getValue('EnergyConsumedB')); - } - if (hasProperty('EnergyConsumedC')) { - updateData.energyConsumedC = String(getValue('EnergyConsumedC')); - } - if (hasProperty('EnergyConsumed')) { - updateData.energyConsumedKw = String(getValue('EnergyConsumed')); - } - - if (Object.keys(updateData).length > 0) { - await this.powerClampHourlyRepository.update( - existingData.uuid, - updateData, - ); - } - } else { - // Insert new record with all required fields - const getValue = (code: string) => { - const prop = logData.properties.find((p) => p.code === code); - return prop ? Number(prop.value) : 0; // Default to 0 for required fields - }; - - await this.powerClampHourlyRepository.insert({ - deviceUuid, - date: currentDate, - hour: currentHour, - energyConsumedA: String(getValue('EnergyConsumedA')), - energyConsumedB: String(getValue('EnergyConsumedB')), - energyConsumedC: String(getValue('EnergyConsumedC')), - energyConsumedKw: String(getValue('EnergyConsumed')), - }); - } - } catch (err) { - console.error('Failed to insert or update hourly data', err); - throw err; - } + private loadQuery(fileName: string): string { + return this.sqlLoader.loadQuery( + 'fact_energy_consumed', + fileName, + SQL_PROCEDURES_PATH, + ); } } -interface EnergyProperties { - code: string; - dpId: number; - time: number; - value: string | number; -} - -interface LogData { - devId: string; - dataId: string; - productId: string; - properties: EnergyProperties[]; -} 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/sql/procedures/fact_daily_energy_consumed/fact_daily_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_energy_consumed/fact_daily_energy_consumed_procedure.sql similarity index 97% rename from libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_daily_energy_consumed_procedure.sql rename to libs/common/src/sql/procedures/fact_energy_consumed/fact_daily_energy_consumed_procedure.sql index 82725d0..c549891 100644 --- a/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_daily_energy_consumed_procedure.sql +++ b/libs/common/src/sql/procedures/fact_energy_consumed/fact_daily_energy_consumed_procedure.sql @@ -1,7 +1,7 @@ WITH params AS ( SELECT - 'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, - '2025-04-23'::date AS target_date + $1::uuid AS device_id, + $2::date AS target_date ), total_energy AS ( SELECT diff --git a/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_hourly_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql similarity index 96% rename from libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_hourly_energy_consumed_procedure.sql rename to libs/common/src/sql/procedures/fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql index ff17747..ffefc4f 100644 --- a/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_hourly_energy_consumed_procedure.sql +++ b/libs/common/src/sql/procedures/fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql @@ -1,8 +1,8 @@ WITH params AS ( SELECT - 'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, - '2025-04-23'::date AS target_date, - '14'::text AS target_hour + $1::uuid AS device_id, + $2::date AS target_date, + $3::text AS target_hour ), total_energy AS ( SELECT diff --git a/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_monthly_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql similarity index 96% rename from libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_monthly_energy_consumed_procedure.sql rename to libs/common/src/sql/procedures/fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql index cdf6134..0e69d60 100644 --- a/libs/common/src/sql/procedures/fact_daily_energy_consumed/fact_monthly_energy_consumed_procedure.sql +++ b/libs/common/src/sql/procedures/fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql @@ -1,7 +1,7 @@ WITH params AS ( SELECT - 'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, - '03-2025'::text AS target_month -- Format should match 'MM-YYYY' + $1::uuid AS device_id, + $2::text AS target_month -- Format should match 'MM-YYYY' ), total_energy AS ( SELECT diff --git a/src/power-clamp/services/power-clamp.service.ts b/src/power-clamp/services/power-clamp.service.ts index bd03f0c..406cb95 100644 --- a/src/power-clamp/services/power-clamp.service.ts +++ b/src/power-clamp/services/power-clamp.service.ts @@ -1,3 +1,4 @@ +import { SQL_QUERIES_PATH } from '@app/common/constants/sql-query-path'; import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; import { Injectable } from '@nestjs/common'; import { DataSource } from 'typeorm'; @@ -13,6 +14,7 @@ export class PowerClampService { const sql = this.sqlLoader.loadQuery( 'fact_daily_energy_consumed', 'fact_daily_energy_consumed', + SQL_QUERIES_PATH, ); return this.dataSource.manager.query(sql); } @@ -21,6 +23,7 @@ export class PowerClampService { const sql = this.sqlLoader.loadQuery( 'energy', 'energy_consumed_with_params', + SQL_QUERIES_PATH, ); return this.dataSource.manager.query(sql, [code]); } From 0ee26698ff1a76aafca753d835fc5703f3fb026e Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 24 Apr 2025 12:46:24 +0300 Subject: [PATCH 09/10] feat: enhance PowerClamp service and controller to support filtering by date, month, and year --- .../controllers/power-clamp.controller.ts | 16 ++- src/power-clamp/dto/get-power-clamp.dto.ts | 35 +++++++ src/power-clamp/power-clamp.module.ts | 13 ++- .../services/power-clamp.service.ts | 98 +++++++++++++++---- 4 files changed, 136 insertions(+), 26 deletions(-) create mode 100644 src/power-clamp/dto/get-power-clamp.dto.ts diff --git a/src/power-clamp/controllers/power-clamp.controller.ts b/src/power-clamp/controllers/power-clamp.controller.ts index 621893d..3be79ed 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') @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 406cb95..5a4a8f8 100644 --- a/src/power-clamp/services/power-clamp.service.ts +++ b/src/power-clamp/services/power-clamp.service.ts @@ -1,30 +1,88 @@ -import { SQL_QUERIES_PATH } from '@app/common/constants/sql-query-path'; -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', - SQL_QUERIES_PATH, - ); - 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', - SQL_QUERIES_PATH, - ); - return this.dataSource.manager.query(sql, [code]); + private buildResponse(message: string, data: any[]) { + return new SuccessResponseDto({ + message, + data, + statusCode: HttpStatus.OK, + }); } } From 72e65882b1d250ebb107a7683c57564e389fc3e1 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Thu, 24 Apr 2025 15:08:54 +0300 Subject: [PATCH 10/10] feat: update PowerClamp routes to support historical data retrieval --- libs/common/src/constants/controller-route.ts | 5 +++-- src/power-clamp/controllers/power-clamp.controller.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) 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/src/power-clamp/controllers/power-clamp.controller.ts b/src/power-clamp/controllers/power-clamp.controller.ts index 3be79ed..347013c 100644 --- a/src/power-clamp/controllers/power-clamp.controller.ts +++ b/src/power-clamp/controllers/power-clamp.controller.ts @@ -17,7 +17,7 @@ export class PowerClampController { @ApiBearerAuth() @UseGuards(JwtAuthGuard) - @Get(':powerClampUuid') + @Get(':powerClampUuid/historical') @ApiOperation({ summary: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_SUMMARY, description: ControllerRoute.PowerClamp.ACTIONS.GET_ENERGY_DESCRIPTION,