mirror of
https://github.com/SyncrowIOT/backend.git
synced 2025-07-15 18:27:05 +00:00
feat: enhance PowerClamp service with energy consumption procedures and enums
This commit is contained in:
6
libs/common/src/constants/power.clamp.enargy.enum.ts
Normal file
6
libs/common/src/constants/power.clamp.enargy.enum.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum PowerClampEnergyEnum {
|
||||||
|
ENERGY_CONSUMED = 'EnergyConsumed',
|
||||||
|
ENERGY_CONSUMED_A = 'EnergyConsumedA',
|
||||||
|
ENERGY_CONSUMED_B = 'EnergyConsumedB',
|
||||||
|
ENERGY_CONSUMED_C = 'EnergyConsumedC',
|
||||||
|
}
|
@ -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';
|
||||||
|
@ -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();
|
||||||
|
@ -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[];
|
|
||||||
}
|
}
|
||||||
|
@ -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) {
|
||||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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]);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user