From 72753b6dfb8f764b969a4f09784fce3dbd5e3c64 Mon Sep 17 00:00:00 2001 From: faris Aljohari <83524184+farisaljohari@users.noreply.github.com> Date: Sat, 14 Jun 2025 15:18:20 -0600 Subject: [PATCH] merge dev to main --- libs/common/src/helper/date-format.ts | 38 ++++++++++++ .../src/modules/presence-sensor/dtos/index.ts | 1 + .../dtos/presence-sensor.dto.ts | 27 +++++++++ .../modules/presence-sensor/entities/index.ts | 1 + .../entities/presence-sensor.entity.ts | 58 +++++++++++++++++++ .../presence-sensor/repositories/index.ts | 1 + ...aily_device_energy_consumed_procedure.sql} | 0 ...urly_device_energy_consumed_procedure.sql} | 0 ...thly_device_energy_consumed_procedure.sql} | 0 ..._daily_space_energy_consumed_procedure.sql | 20 +++++++ ...onthly_space_energy_consumed_procedure.sql | 21 +++++++ .../fact_daily_device_energy_consumed.sql} | 0 .../fact_daily_space_energy_consumed.sql | 11 ++++ .../fact_hourly_device_active_energy.sql} | 0 src/power-clamp/dto/power-clamp-params.dto.ts | 19 +++++- src/power-clamp/power-clamp.module.ts | 2 +- .../services/power-clamp.service.ts | 38 +++++++++++- src/space/services/space-device.service.ts | 8 ++- 18 files changed, 239 insertions(+), 6 deletions(-) create mode 100644 libs/common/src/helper/date-format.ts create mode 100644 libs/common/src/modules/presence-sensor/dtos/index.ts create mode 100644 libs/common/src/modules/presence-sensor/dtos/presence-sensor.dto.ts create mode 100644 libs/common/src/modules/presence-sensor/entities/index.ts create mode 100644 libs/common/src/modules/presence-sensor/entities/presence-sensor.entity.ts create mode 100644 libs/common/src/modules/presence-sensor/repositories/index.ts rename libs/common/src/sql/procedures/{fact_energy_consumed/fact_daily_energy_consumed_procedure.sql => fact_device_energy_consumed/fact_daily_device_energy_consumed_procedure.sql} (100%) rename libs/common/src/sql/procedures/{fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql => fact_device_energy_consumed/fact_hourly_device_energy_consumed_procedure.sql} (100%) rename libs/common/src/sql/procedures/{fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql => fact_device_energy_consumed/fact_monthly_device_energy_consumed_procedure.sql} (100%) create mode 100644 libs/common/src/sql/procedures/fact_space_energy_consumed/fact_daily_space_energy_consumed_procedure.sql create mode 100644 libs/common/src/sql/procedures/fact_space_energy_consumed/fact_monthly_space_energy_consumed_procedure.sql rename libs/common/src/sql/queries/{fact_daily_energy_consumed/fact_daily_energy_consumed.sql => fact_daily_device_energy_consumed/fact_daily_device_energy_consumed.sql} (100%) create mode 100644 libs/common/src/sql/queries/fact_daily_space_energy_consumed/fact_daily_space_energy_consumed.sql rename libs/common/src/sql/queries/{fact_hourly_active_energy/fact_hourly_active_energy.sql => fact_hourly_device_active_energy/fact_hourly_device_active_energy.sql} (100%) diff --git a/libs/common/src/helper/date-format.ts b/libs/common/src/helper/date-format.ts new file mode 100644 index 0000000..6366849 --- /dev/null +++ b/libs/common/src/helper/date-format.ts @@ -0,0 +1,38 @@ +export function toDDMMYYYY(dateString?: string | null): string | null { + if (!dateString) return null; + + // Ensure dateString is valid format YYYY-MM-DD + const regex = /^\d{4}-\d{2}-\d{2}$/; + if (!regex.test(dateString)) { + throw new Error( + `Invalid date format: ${dateString}. Expected format is YYYY-MM-DD`, + ); + } + + const [year, month, day] = dateString.split('-'); + return `${day}-${month}-${year}`; +} +export function toMMYYYY(dateString?: string | null): string | null { + if (!dateString) return null; + + // Ensure dateString is valid format YYYY-MM + const regex = /^\d{4}-\d{2}$/; + if (!regex.test(dateString)) { + throw new Error( + `Invalid date format: ${dateString}. Expected format is YYYY-MM`, + ); + } + + const [year, month] = dateString.split('-'); + return `${month}-${year}`; +} +export function filterByMonth(data: any[], monthDate: string) { + const [year, month] = monthDate.split('-').map(Number); + + return data.filter((item) => { + const itemDate = new Date(item.date); + return ( + itemDate.getUTCFullYear() === year && itemDate.getUTCMonth() + 1 === month + ); + }); +} diff --git a/libs/common/src/modules/presence-sensor/dtos/index.ts b/libs/common/src/modules/presence-sensor/dtos/index.ts new file mode 100644 index 0000000..9993c83 --- /dev/null +++ b/libs/common/src/modules/presence-sensor/dtos/index.ts @@ -0,0 +1 @@ +export * from './presence-sensor.dto'; diff --git a/libs/common/src/modules/presence-sensor/dtos/presence-sensor.dto.ts b/libs/common/src/modules/presence-sensor/dtos/presence-sensor.dto.ts new file mode 100644 index 0000000..e37f9db --- /dev/null +++ b/libs/common/src/modules/presence-sensor/dtos/presence-sensor.dto.ts @@ -0,0 +1,27 @@ +import { IsNotEmpty, IsNumber, IsString } from 'class-validator'; + +export class PresenceSensorDto { + @IsString() + @IsNotEmpty() + public uuid: string; + + @IsString() + @IsNotEmpty() + public deviceUuid: string; + + @IsString() + @IsNotEmpty() + public eventDate: string; + + @IsNumber() + @IsNotEmpty() + public CountMotionDetected: number; + + @IsNumber() + @IsNotEmpty() + public CountPresenceDetected: number; + + @IsNumber() + @IsNotEmpty() + public CountTotalPresenceDetected: number; +} diff --git a/libs/common/src/modules/presence-sensor/entities/index.ts b/libs/common/src/modules/presence-sensor/entities/index.ts new file mode 100644 index 0000000..b578244 --- /dev/null +++ b/libs/common/src/modules/presence-sensor/entities/index.ts @@ -0,0 +1 @@ +export * from './presence-sensor.entity'; diff --git a/libs/common/src/modules/presence-sensor/entities/presence-sensor.entity.ts b/libs/common/src/modules/presence-sensor/entities/presence-sensor.entity.ts new file mode 100644 index 0000000..facbf8c --- /dev/null +++ b/libs/common/src/modules/presence-sensor/entities/presence-sensor.entity.ts @@ -0,0 +1,58 @@ +import { Column, Entity, ManyToOne, Unique } from 'typeorm'; +import { AbstractEntity } from '../../abstract/entities/abstract.entity'; +import { PresenceSensorDto } from '../dtos'; +import { DeviceEntity } from '../../device/entities/device.entity'; +import { SpaceEntity } from '../../space/entities/space.entity'; + +@Entity({ name: 'presence-sensor-daily-device-detection' }) +@Unique(['deviceUuid', 'eventDate']) +export class PresenceSensorDailyDeviceEntity extends AbstractEntity { + @Column({ nullable: false }) + public deviceUuid: string; + + @Column({ nullable: false, type: 'date' }) + public eventDate: string; + + @Column({ nullable: false }) + public CountMotionDetected: number; + + @Column({ nullable: false }) + public CountPresenceDetected: number; + + @Column({ nullable: false }) + public CountTotalPresenceDetected: number; + + @ManyToOne(() => DeviceEntity, (device) => device.presenceSensorDaily) + device: DeviceEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} +@Entity({ name: 'presence-sensor-daily-space-detection' }) +@Unique(['spaceUuid', 'eventDate']) +export class PresenceSensorDailySpaceEntity extends AbstractEntity { + @Column({ nullable: false }) + public spaceUuid: string; + + @Column({ nullable: false, type: 'date' }) + public eventDate: string; + + @Column({ nullable: false }) + public CountMotionDetected: number; + + @Column({ nullable: false }) + public CountPresenceDetected: number; + + @Column({ nullable: false }) + public CountTotalPresenceDetected: number; + + @ManyToOne(() => SpaceEntity, (space) => space.presenceSensorDaily) + space: SpaceEntity; + + constructor(partial: Partial) { + super(); + Object.assign(this, partial); + } +} diff --git a/libs/common/src/modules/presence-sensor/repositories/index.ts b/libs/common/src/modules/presence-sensor/repositories/index.ts new file mode 100644 index 0000000..8b64ee8 --- /dev/null +++ b/libs/common/src/modules/presence-sensor/repositories/index.ts @@ -0,0 +1 @@ +export * from './presence-sensor.repository'; diff --git a/libs/common/src/sql/procedures/fact_energy_consumed/fact_daily_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_device_energy_consumed/fact_daily_device_energy_consumed_procedure.sql similarity index 100% rename from libs/common/src/sql/procedures/fact_energy_consumed/fact_daily_energy_consumed_procedure.sql rename to libs/common/src/sql/procedures/fact_device_energy_consumed/fact_daily_device_energy_consumed_procedure.sql diff --git a/libs/common/src/sql/procedures/fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_device_energy_consumed/fact_hourly_device_energy_consumed_procedure.sql similarity index 100% rename from libs/common/src/sql/procedures/fact_energy_consumed/fact_hourly_energy_consumed_procedure.sql rename to libs/common/src/sql/procedures/fact_device_energy_consumed/fact_hourly_device_energy_consumed_procedure.sql diff --git a/libs/common/src/sql/procedures/fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_device_energy_consumed/fact_monthly_device_energy_consumed_procedure.sql similarity index 100% rename from libs/common/src/sql/procedures/fact_energy_consumed/fact_monthly_energy_consumed_procedure.sql rename to libs/common/src/sql/procedures/fact_device_energy_consumed/fact_monthly_device_energy_consumed_procedure.sql diff --git a/libs/common/src/sql/procedures/fact_space_energy_consumed/fact_daily_space_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_space_energy_consumed/fact_daily_space_energy_consumed_procedure.sql new file mode 100644 index 0000000..7cd49a5 --- /dev/null +++ b/libs/common/src/sql/procedures/fact_space_energy_consumed/fact_daily_space_energy_consumed_procedure.sql @@ -0,0 +1,20 @@ +WITH params AS ( + SELECT + TO_DATE(NULLIF($1, ''), 'MM-YYYY') AS month, + string_to_array(NULLIF($2, ''), ',') AS device_ids +) + +SELECT + A.date, + SUM(A.energy_consumed_kW::numeric) AS total_energy_consumed_KW, + SUM(A.energy_consumed_A::numeric) AS total_energy_consumed_A, + SUM(A.energy_consumed_B::numeric) AS total_energy_consumed_B, + SUM(A.energy_consumed_C::numeric) AS total_energy_consumed_C +FROM public."power-clamp-energy-consumed-daily" AS A +JOIN public.device AS B + ON A.device_uuid::TEXT = B."uuid"::TEXT +JOIN params P ON TRUE +WHERE B."uuid"::TEXT = ANY(P.device_ids) + AND (P.month IS NULL OR date_trunc('month', A.date)= P.month) +GROUP BY A.date +ORDER BY A.date; diff --git a/libs/common/src/sql/procedures/fact_space_energy_consumed/fact_monthly_space_energy_consumed_procedure.sql b/libs/common/src/sql/procedures/fact_space_energy_consumed/fact_monthly_space_energy_consumed_procedure.sql new file mode 100644 index 0000000..15c5765 --- /dev/null +++ b/libs/common/src/sql/procedures/fact_space_energy_consumed/fact_monthly_space_energy_consumed_procedure.sql @@ -0,0 +1,21 @@ +WITH params AS ( + SELECT + TO_DATE(NULLIF($1, ''), 'DD-MM-YYYY') AS start_date, + TO_DATE(NULLIF($2, ''), 'DD-MM-YYYY') AS end_date, + string_to_array(NULLIF($3, ''), ',') AS device_ids +) + +SELECT TO_CHAR(A.date, 'MM-YYYY') AS month, + SUM(A.energy_consumed_kW::numeric) AS total_energy_consumed_KW, + SUM(A.energy_consumed_A::numeric) AS total_energy_consumed_A, + SUM(A.energy_consumed_B::numeric) AS total_energy_consumed_B, + SUM(A.energy_consumed_C::numeric) AS total_energy_consumed_C +FROM public."power-clamp-energy-consumed-daily" AS A +JOIN public.device AS B + ON A.device_uuid::TEXT = B."uuid"::TEXT +JOIN params P ON TRUE +WHERE B."uuid"::TEXT = ANY(P.device_ids) + AND (P.start_date IS NULL OR A.date >= P.start_date) + AND (P.end_date IS NULL OR A.date <= P.end_date) +GROUP BY 1 + diff --git a/libs/common/src/sql/queries/fact_daily_energy_consumed/fact_daily_energy_consumed.sql b/libs/common/src/sql/queries/fact_daily_device_energy_consumed/fact_daily_device_energy_consumed.sql similarity index 100% rename from libs/common/src/sql/queries/fact_daily_energy_consumed/fact_daily_energy_consumed.sql rename to libs/common/src/sql/queries/fact_daily_device_energy_consumed/fact_daily_device_energy_consumed.sql diff --git a/libs/common/src/sql/queries/fact_daily_space_energy_consumed/fact_daily_space_energy_consumed.sql b/libs/common/src/sql/queries/fact_daily_space_energy_consumed/fact_daily_space_energy_consumed.sql new file mode 100644 index 0000000..d5a9910 --- /dev/null +++ b/libs/common/src/sql/queries/fact_daily_space_energy_consumed/fact_daily_space_energy_consumed.sql @@ -0,0 +1,11 @@ +SELECT + B.space_device_uuid AS space_id, + A."date", + SUM(A.energy_consumed_kW::numeric) AS total_energy_consumed_KW, + SUM(A.energy_consumed_A::numeric) AS total_energy_consumed_A, + SUM(A.energy_consumed_B::numeric) AS total_energy_consumed_B, + SUM(A.energy_consumed_C::numeric) AS total_energy_consumed_C +FROM public."power-clamp-energy-consumed-daily" AS A -- I want to change the source table in the future. +JOIN public.device AS B + ON A.device_uuid::TEXT = B."uuid"::TEXT +GROUP BY 1, 2; \ No newline at end of file diff --git a/libs/common/src/sql/queries/fact_hourly_active_energy/fact_hourly_active_energy.sql b/libs/common/src/sql/queries/fact_hourly_device_active_energy/fact_hourly_device_active_energy.sql similarity index 100% rename from libs/common/src/sql/queries/fact_hourly_active_energy/fact_hourly_active_energy.sql rename to libs/common/src/sql/queries/fact_hourly_device_active_energy/fact_hourly_device_active_energy.sql diff --git a/src/power-clamp/dto/power-clamp-params.dto.ts b/src/power-clamp/dto/power-clamp-params.dto.ts index 42f8e11..7250540 100644 --- a/src/power-clamp/dto/power-clamp-params.dto.ts +++ b/src/power-clamp/dto/power-clamp-params.dto.ts @@ -1,6 +1,23 @@ -import { IsUUID } from 'class-validator'; +import { IsNotEmpty, IsOptional, IsUUID, ValidateIf } from 'class-validator'; export class PowerClampParamsDto { @IsUUID('4', { message: 'Invalid UUID format' }) powerClampUuid: string; } +export class SpaceParamsDto { + @IsUUID('4', { message: 'Invalid UUID format' }) + spaceUuid: string; +} +export class ResourceParamsDto { + @IsUUID('4', { message: 'Invalid UUID format' }) + @IsOptional() + spaceUuid?: string; + + @IsUUID('4', { message: 'Invalid UUID format' }) + @IsOptional() + communityUuid?: string; + + @ValidateIf((o) => !o.spaceUuid && !o.communityUuid) + @IsNotEmpty({ message: 'Either spaceUuid or communityUuid must be provided' }) + requireEither?: never; // This ensures at least one of them is provided +} diff --git a/src/power-clamp/power-clamp.module.ts b/src/power-clamp/power-clamp.module.ts index 13595d8..6a36393 100644 --- a/src/power-clamp/power-clamp.module.ts +++ b/src/power-clamp/power-clamp.module.ts @@ -65,7 +65,7 @@ import { AqiDataService } from '@app/common/helper/services/aqi.data.service'; imports: [ConfigModule], controllers: [PowerClampController], providers: [ - PowerClampService, + PowerClamp, PowerClampDailyRepository, PowerClampHourlyRepository, PowerClampMonthlyRepository, diff --git a/src/power-clamp/services/power-clamp.service.ts b/src/power-clamp/services/power-clamp.service.ts index 9360737..50a9461 100644 --- a/src/power-clamp/services/power-clamp.service.ts +++ b/src/power-clamp/services/power-clamp.service.ts @@ -1,13 +1,32 @@ -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; -import { GetPowerClampDto } from '../dto/get-power-clamp.dto'; +import { + BadRequestException, + HttpException, + HttpStatus, + Injectable, +} from '@nestjs/common'; +import { + GetPowerClampBySpaceDto, + 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'; -import { PowerClampParamsDto } from '../dto/power-clamp-params.dto'; +import { + PowerClampParamsDto, + ResourceParamsDto, +} from '../dto/power-clamp-params.dto'; import { DeviceRepository } from '@app/common/modules/device/repositories'; +import { SpaceDeviceService } from 'src/space/services'; +import { SqlLoaderService } from '@app/common/helper/services/sql-loader.service'; +import { DataSource } from 'typeorm'; +import { SQL_PROCEDURES_PATH } from '@app/common/constants/sql-query-path'; +import { ProductType } from '@app/common/constants/product-type.enum'; +import { CommunityService } from 'src/community/services'; +import { BaseResponseDto } from '@app/common/dto/base.response.dto'; +import { filterByMonth, toMMYYYY } from '@app/common/helper/date-format'; @Injectable() export class PowerClampService { @@ -210,4 +229,17 @@ export class PowerClampService { statusCode: HttpStatus.OK, }); } + private async executeProcedure( + procedureFileName: string, + params: (string | number | null)[], + ): Promise { + const query = this.loadQuery( + 'fact_space_energy_consumed', + procedureFileName, + ); + return await this.dataSource.query(query, params); + } + private loadQuery(folderName: string, fileName: string): string { + return this.sqlLoader.loadQuery(folderName, fileName, SQL_PROCEDURES_PATH); + } } diff --git a/src/space/services/space-device.service.ts b/src/space/services/space-device.service.ts index 2148d23..8f8a2cc 100644 --- a/src/space/services/space-device.service.ts +++ b/src/space/services/space-device.service.ts @@ -1,6 +1,11 @@ import { TuyaService } from '@app/common/integrations/tuya/services/tuya.service'; -import { HttpException, HttpStatus, Injectable } from '@nestjs/common'; +import { + HttpException, + HttpStatus, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { GetSpaceParam } from '../dtos'; import { BaseResponseDto } from '@app/common/dto/base.response.dto'; import { SuccessResponseDto } from '@app/common/dto/success.response.dto'; @@ -21,6 +26,7 @@ export class SpaceDeviceService { private readonly tuyaService: TuyaService, private readonly validationService: ValidationService, private readonly deviceService: DeviceService, + private readonly spaceRepository: SpaceRepository, ) {} async listDevicesInSpace(