feat: enhance PowerClamp service with energy consumption procedures and enums

This commit is contained in:
faris Aljohari
2025-04-24 03:14:03 +03:00
parent f3fd6646a1
commit 881618a4ee
9 changed files with 87 additions and 126 deletions

View File

@ -0,0 +1,6 @@
export enum PowerClampEnergyEnum {
ENERGY_CONSUMED = 'EnergyConsumed',
ENERGY_CONSUMED_A = 'EnergyConsumedA',
ENERGY_CONSUMED_B = 'EnergyConsumedB',
ENERGY_CONSUMED_C = 'EnergyConsumedC',
}

View File

@ -1 +1,2 @@
export const SQL_QUERIES_PATH = 'libs/common/src/sql/queries'; export const SQL_QUERIES_PATH = 'libs/common/src/sql/queries';
export const SQL_PROCEDURES_PATH = 'libs/common/src/sql/procedures';

View File

@ -19,6 +19,8 @@ import {
} from 'firebase/database'; } from 'firebase/database';
import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories'; import { DeviceStatusLogRepository } from '@app/common/modules/device-status-log/repositories';
import { ProductType } from '@app/common/constants/product-type.enum'; 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() @Injectable()
export class DeviceStatusFirebaseService { export class DeviceStatusFirebaseService {
private tuya: TuyaContext; private tuya: TuyaContext;
@ -26,6 +28,7 @@ export class DeviceStatusFirebaseService {
constructor( constructor(
private readonly configService: ConfigService, private readonly configService: ConfigService,
private readonly deviceRepository: DeviceRepository, private readonly deviceRepository: DeviceRepository,
private readonly powerClampService: PowerClampService,
private deviceStatusLogRepository: DeviceStatusLogRepository, private deviceStatusLogRepository: DeviceStatusLogRepository,
) { ) {
const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY'); const accessKey = this.configService.get<string>('auth-config.ACCESS_KEY');
@ -75,29 +78,12 @@ export class DeviceStatusFirebaseService {
const device = await this.getDeviceByDeviceTuyaUuid( const device = await this.getDeviceByDeviceTuyaUuid(
addDeviceStatusDto.deviceTuyaUuid, 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) { if (device?.uuid) {
return await this.createDeviceStatusFirebase({ return await this.createDeviceStatusFirebase({
deviceUuid: device.uuid, deviceUuid: device.uuid,
...addDeviceStatusDto, ...addDeviceStatusDto,
productType: device.productDevice.prodType,
}); });
} }
// Return null if device not found or no UUID // Return null if device not found or no UUID
@ -235,6 +221,25 @@ export class DeviceStatusFirebaseService {
}); });
await this.deviceStatusLogRepository.save(newLogs); 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 // Return the updated data
const snapshot: DataSnapshot = await get(dataRef); const snapshot: DataSnapshot = await get(dataRef);
return snapshot.val(); return snapshot.val();

View File

@ -1,109 +1,61 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { import { SqlLoaderService } from './sql-loader.service';
PowerClampDailyRepository, import { DataSource } from 'typeorm';
PowerClampHourlyRepository, import { SQL_PROCEDURES_PATH } from '@app/common/constants/sql-query-path';
PowerClampMonthlyRepository,
} from '@app/common/modules/power-clamp/repositories';
@Injectable() @Injectable()
export class PowerClampService { export class PowerClampService {
constructor( constructor(
private readonly powerClampHourlyRepository: PowerClampHourlyRepository, private readonly sqlLoader: SqlLoaderService,
private readonly powerClampDailyRepository: PowerClampDailyRepository, private readonly dataSource: DataSource,
private readonly powerClampMonthlyRepository: PowerClampMonthlyRepository,
) {} ) {}
async insertOrUpdatePowerClamp(
deviceUuid: string, async updateEnergyConsumedHistoricalData(deviceUuid: string): Promise<void> {
logData: any,
): Promise<void> {
try { 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) { } catch (err) {
console.error('Failed to insert or update hourly data', err); console.error('Failed to insert or update energy data:', err);
throw err; throw err;
} }
} }
async insertOrUpdateHourly( private async executeProcedure(
deviceUuid: string, procedureFileName: string,
logData: LogData, params: (string | number | null)[],
): Promise<void> { ): Promise<void> {
try { const query = this.loadQuery(procedureFileName);
const currentDate = new Date().toLocaleDateString('en-CA'); await this.dataSource.query(query, params);
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<any> = {};
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) { private loadQuery(fileName: string): string {
await this.powerClampHourlyRepository.update( return this.sqlLoader.loadQuery(
existingData.uuid, 'fact_energy_consumed',
updateData, fileName,
SQL_PROCEDURES_PATH,
); );
} }
} 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[];
} }

View File

@ -1,4 +1,3 @@
import { SQL_QUERIES_PATH } from '@app/common/constants/sql-query-path';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { readFileSync } from 'fs'; import { readFileSync } from 'fs';
import { join } from 'path'; import { join } from 'path';
@ -8,13 +7,8 @@ export class SqlLoaderService {
private readonly logger = new Logger(SqlLoaderService.name); private readonly logger = new Logger(SqlLoaderService.name);
private readonly sqlRootPath = join(__dirname, '../sql/queries'); private readonly sqlRootPath = join(__dirname, '../sql/queries');
loadQuery(module: string, queryName: string): string { loadQuery(module: string, queryName: string, path: string): string {
const filePath = join( const filePath = join(process.cwd(), path, module, `${queryName}.sql`);
process.cwd(),
SQL_QUERIES_PATH,
module,
`${queryName}.sql`,
);
try { try {
return readFileSync(filePath, 'utf8'); return readFileSync(filePath, 'utf8');
} catch (error) { } catch (error) {

View File

@ -1,7 +1,7 @@
WITH params AS ( WITH params AS (
SELECT SELECT
'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, $1::uuid AS device_id,
'2025-04-23'::date AS target_date $2::date AS target_date
), ),
total_energy AS ( total_energy AS (
SELECT SELECT

View File

@ -1,8 +1,8 @@
WITH params AS ( WITH params AS (
SELECT SELECT
'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, $1::uuid AS device_id,
'2025-04-23'::date AS target_date, $2::date AS target_date,
'14'::text AS target_hour $3::text AS target_hour
), ),
total_energy AS ( total_energy AS (
SELECT SELECT

View File

@ -1,7 +1,7 @@
WITH params AS ( WITH params AS (
SELECT SELECT
'd72f3d5d-02e5-4a9e-a1f7-7ab8c3534910'::uuid AS device_id, $1::uuid AS device_id,
'03-2025'::text AS target_month -- Format should match 'MM-YYYY' $2::text AS target_month -- Format should match 'MM-YYYY'
), ),
total_energy AS ( total_energy AS (
SELECT SELECT

View File

@ -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 { SqlLoaderService } from '@app/common/helper/services/sql-loader.service';
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
@ -13,6 +14,7 @@ export class PowerClampService {
const sql = this.sqlLoader.loadQuery( const sql = this.sqlLoader.loadQuery(
'fact_daily_energy_consumed', 'fact_daily_energy_consumed',
'fact_daily_energy_consumed', 'fact_daily_energy_consumed',
SQL_QUERIES_PATH,
); );
return this.dataSource.manager.query(sql); return this.dataSource.manager.query(sql);
} }
@ -21,6 +23,7 @@ export class PowerClampService {
const sql = this.sqlLoader.loadQuery( const sql = this.sqlLoader.loadQuery(
'energy', 'energy',
'energy_consumed_with_params', 'energy_consumed_with_params',
SQL_QUERIES_PATH,
); );
return this.dataSource.manager.query(sql, [code]); return this.dataSource.manager.query(sql, [code]);
} }