2026-04-22 10:40:19 +08:00
# include "pqdif_thread_processor.h"
# include <algorithm>
# include <chrono>
# include <thread>
# include <cctype>
# include <cmath>
# include <ctime>
# include <deque>
# include <iostream>
# include <mutex>
# include <sstream>
# include <string>
# include <vector>
2026-04-29 13:35:49 +08:00
# include <set>
2026-04-22 10:40:19 +08:00
# include <experimental/filesystem>
# include <fstream>
# include <iomanip>
# include <cstdio>
2026-04-29 13:35:49 +08:00
# include <cstdlib>
# include <limits>
# include <utility>
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
// PQDIF 解析库
2026-04-22 10:40:19 +08:00
# include "pqdif/PQDIF.h"
# include "pqdif/include/pqdif_ph.h"
# include "pqdif/include/pqdif_id.h"
2026-04-29 13:35:49 +08:00
# include "pqdif/include/pqdif_lg.h"
# include "pqdif_semantic_ids.h"
2026-04-22 10:40:19 +08:00
namespace fs = std : : experimental : : filesystem ;
namespace {
2026-04-29 13:35:49 +08:00
constexpr int kScanIntervalSec = 60 ;
constexpr int kBackupLimit = 4800 ;
constexpr int kMaxPqdifFilesPerScan = 1 ;
constexpr size_t kParsedCacheLimit = 128 ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
const char * kPqdRootDir = " download " ;
2026-04-22 10:40:19 +08:00
const char * kDoneRootDir = " download_done " ;
const char * kFailRootDir = " download_fail " ;
std : : deque < ParsedPqdifFile > g_parsed_cache ;
std : : mutex g_parsed_cache_mutex ;
2026-04-29 13:35:49 +08:00
// PQDIF 统计桶 Base64 文件级“生成队列”。
// 对象用途:解析线程在完成一个 PQDIF 文件的桶数据组装后,会把一个
// PqdifStatBase64FileBatch 放入这个队列。
// 数据粒度:一个队列元素 = 一个 PQDIF 文件批次;批次内部再按
// “时间点 -> Max/Min/Avg/P95 子记录”保存,避免不同 PQDIF 文件的数据混合。
constexpr size_t kPqdifStatBase64QueueLimit = 128 ;
std : : deque < PqdifStatBase64FileBatch > g_pqdif_stat_base64_queue ;
std : : mutex g_pqdif_stat_base64_mutex ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
// PQDIF 统计桶 Base64 文件级“待后续处理队列”。
// 对象用途: RunPqdifScanLoop() 每轮循环末尾会从生成队列取出最多一个
// PqdifStatBase64FileBatch, 并移动到这个队列中, 后续入库/上传/推送逻辑
// 可以从这里取数据。
// 设计原因:避免在扫描解析队列上直接做耗时业务处理,同时保证取出的文件批次
// 不会因为局部变量析构而丢失。
std : : deque < PqdifStatBase64FileBatch > g_pqdif_stat_base64_ready_queue ;
std : : mutex g_pqdif_stat_base64_ready_mutex ;
2026-04-22 10:40:19 +08:00
std : : string guid_to_string ( const GUID & g )
{
char buf [ 64 ] = { 0 } ;
std : : snprintf ( buf , sizeof ( buf ) ,
" %08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x " ,
static_cast < unsigned int > ( g . Data1 ) ,
static_cast < unsigned int > ( g . Data2 ) ,
static_cast < unsigned int > ( g . Data3 ) ,
static_cast < unsigned int > ( g . Data4 [ 0 ] ) ,
static_cast < unsigned int > ( g . Data4 [ 1 ] ) ,
static_cast < unsigned int > ( g . Data4 [ 2 ] ) ,
static_cast < unsigned int > ( g . Data4 [ 3 ] ) ,
static_cast < unsigned int > ( g . Data4 [ 4 ] ) ,
static_cast < unsigned int > ( g . Data4 [ 5 ] ) ,
static_cast < unsigned int > ( g . Data4 [ 6 ] ) ,
static_cast < unsigned int > ( g . Data4 [ 7 ] ) ) ;
return std : : string ( buf ) ;
}
2026-04-29 13:35:49 +08:00
std : : string safe_tag_name ( const GUID & tag )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
const char * name = theInfo . GetNameOfTag ( tag ) ;
if ( name ! = nullptr & & name [ 0 ] ! = ' \0 ' )
return std : : string ( name ) ;
return guid_to_string ( tag ) ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
PqdifGuidValue make_guid_value ( const GUID & guid )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
PqdifGuidValue out ;
out . value = guid ;
out . symbolic_name = safe_tag_name ( guid ) ;
return out ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
std : : string format_time_text ( time_t ts )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
if ( ts = = 0 )
return std : : string ( ) ;
std : : tm tm_value { } ;
# if defined(_WIN32)
localtime_s ( & tm_value , & ts ) ;
# else
std : : tm * ptm = std : : localtime ( & ts ) ;
if ( ptm = = nullptr )
return std : : string ( ) ;
tm_value = * ptm ;
# endif
char buf [ 32 ] = { 0 } ;
if ( std : : strftime ( buf , sizeof ( buf ) , " %Y-%m-%d %H:%M:%S " , & tm_value ) = = 0 )
return std : : string ( ) ;
return std : : string ( buf ) ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
PqdifTimestampValue make_timestamp_value ( CPQDIF & file_convert , const TIMESTAMPPQDIF & ts )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
PqdifTimestampValue out ;
out . day = ts . day ;
out . second = ts . sec ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
if ( ts . day ! = 0 | | std : : fabs ( ts . sec ) > 1e-12 )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
DATE dt = static_cast < DATE > ( ts . day ) + ( ts . sec / static_cast < double > ( SECONDS_PER_DAY ) ) ;
file_convert . GetTime ( dt , & out . unix_time ) ;
out . text = format_time_text ( out . unix_time ) ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
return out ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
PqdifRecordHeaderInfo build_record_header_info ( CPQDIFRecord * record , long record_index )
{
PqdifRecordHeaderInfo info ;
info . record_index = record_index ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
if ( record = = nullptr )
return info ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
LINKABS4 pos = 0 ;
LINKABS4 next_pos = 0 ;
SIZE4 size_header = 0 ;
SIZE4 size_data = 0 ;
UINT checksum = 0 ;
GUID record_tag { } ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
record - > HeaderGetPos ( pos ) ;
record - > HeaderGetTag ( record_tag ) ;
record - > HeaderGetSize ( size_header , size_data ) ;
record - > HeaderGetPosNextRecord ( next_pos ) ;
record - > HeaderGetChecksum ( checksum ) ;
info . file_position = static_cast < long > ( pos ) ;
info . record_type = make_guid_value ( record_tag ) ;
info . header_size = static_cast < long > ( size_header ) ;
info . data_size = static_cast < long > ( size_data ) ;
info . next_record_position = static_cast < long > ( next_pos ) ;
info . checksum = static_cast < unsigned int > ( checksum ) ;
return info ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
std : : string trim_trailing_nulls ( const std : : string & value )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
std : : string out = value ;
while ( ! out . empty ( ) & & out . back ( ) = = ' \0 ' )
out . pop_back ( ) ;
return out ;
2026-04-22 10:40:19 +08:00
}
bool dump_file_basic_info ( const std : : string & file_path , std : : string & err )
{
std : : error_code ec ;
fs : : path p ( file_path ) ;
if ( ! fs : : exists ( p , ec ) )
{
err = " file not exists " ;
return false ;
}
if ( ! fs : : is_regular_file ( p , ec ) )
{
err = " not a regular file " ;
return false ;
}
auto file_size = fs : : file_size ( p , ec ) ;
if ( ec )
{
err = " cannot get file size " ;
return false ;
}
std : : ifstream ifs ( file_path , std : : ios : : binary ) ;
if ( ! ifs . is_open ( ) )
{
err = " ifstream open failed " ;
return false ;
}
std : : cout < < " [PQDIF] file basic info: path= " < < file_path
< < " , size= " < < file_size < < std : : endl ;
unsigned char buf [ 32 ] = { 0 } ;
ifs . read ( reinterpret_cast < char * > ( buf ) , sizeof ( buf ) ) ;
std : : streamsize n = ifs . gcount ( ) ;
std : : ostringstream oss ;
oss < < " [PQDIF] first " < < n < < " bytes: " ;
for ( std : : streamsize i = 0 ; i < n ; + + i )
{
oss < < std : : hex < < std : : setw ( 2 ) < < std : : setfill ( ' 0 ' )
< < static_cast < int > ( buf [ i ] ) < < " " ;
}
std : : cout < < oss . str ( ) < < std : : endl ;
return true ;
}
std : : string to_upper_copy ( std : : string s )
{
std : : transform ( s . begin ( ) , s . end ( ) , s . begin ( ) , [ ] ( unsigned char c ) {
return static_cast < char > ( std : : toupper ( c ) ) ;
} ) ;
return s ;
}
std : : string trim_copy ( const std : : string & s )
{
size_t beg = 0 ;
while ( beg < s . size ( ) & & std : : isspace ( static_cast < unsigned char > ( s [ beg ] ) ) )
+ + beg ;
size_t end = s . size ( ) ;
while ( end > beg & & std : : isspace ( static_cast < unsigned char > ( s [ end - 1 ] ) ) )
- - end ;
return s . substr ( beg , end - beg ) ;
}
std : : string normalize_key ( const std : : string & src )
{
std : : string out ;
out . reserve ( src . size ( ) ) ;
for ( char c : src )
{
unsigned char uc = static_cast < unsigned char > ( c ) ;
if ( std : : isalnum ( uc ) | | c = = ' [ ' | | c = = ' ] ' )
out . push_back ( static_cast < char > ( std : : toupper ( uc ) ) ) ;
}
return out ;
}
bool is_pqdif_file ( const fs : : path & path )
{
if ( ! fs : : is_regular_file ( path ) )
return false ;
const std : : string ext = to_upper_copy ( path . extension ( ) . string ( ) ) ;
return ext = = " .PQD " | | ext = = " .PQDIF " ;
}
bool ensure_dir ( const fs : : path & dir )
{
std : : error_code ec ;
if ( fs : : exists ( dir , ec ) )
return true ;
return fs : : create_directories ( dir , ec ) | | fs : : exists ( dir , ec ) ;
}
bool move_file_with_fallback ( const fs : : path & src , const fs : : path & dst )
{
std : : error_code ec ;
ensure_dir ( dst . parent_path ( ) ) ;
fs : : rename ( src , dst , ec ) ;
if ( ! ec )
return true ;
ec . clear ( ) ;
fs : : copy_file ( src , dst , fs : : copy_options : : overwrite_existing , ec ) ;
if ( ec )
return false ;
ec . clear ( ) ;
fs : : remove ( src , ec ) ;
return ! ec ;
}
void cleanup_backup_dir ( const fs : : path & dir , int limit )
{
if ( limit < 0 )
return ;
std : : error_code ec ;
if ( ! fs : : exists ( dir , ec ) | | ! fs : : is_directory ( dir , ec ) )
return ;
std : : vector < fs : : path > files ;
for ( fs : : directory_iterator it ( dir , ec ) , end ; ! ec & & it ! = end ; it . increment ( ec ) )
{
if ( ! ec & & is_pqdif_file ( it - > path ( ) ) )
files . push_back ( it - > path ( ) ) ;
}
if ( files . size ( ) < = static_cast < size_t > ( limit ) )
return ;
std : : sort ( files . begin ( ) , files . end ( ) , [ ] ( const fs : : path & a , const fs : : path & b ) {
std : : error_code ea , eb ;
return fs : : last_write_time ( a , ea ) < fs : : last_write_time ( b , eb ) ;
} ) ;
const size_t remove_count = files . size ( ) - static_cast < size_t > ( limit ) ;
for ( size_t i = 0 ; i < remove_count ; + + i )
{
std : : error_code remove_ec ;
fs : : remove ( files [ i ] , remove_ec ) ;
}
}
bool push_parsed_result_to_cache ( ParsedPqdifFile & & parsed )
{
std : : lock_guard < std : : mutex > guard ( g_parsed_cache_mutex ) ;
if ( g_parsed_cache . size ( ) > = kParsedCacheLimit )
{
std : : cout < < " [PQDIF] cache full, drop oldest: "
< < g_parsed_cache . front ( ) . source_file < < std : : endl ;
g_parsed_cache . pop_front ( ) ;
}
g_parsed_cache . emplace_back ( std : : move ( parsed ) ) ;
return true ;
}
2026-04-29 13:35:49 +08:00
bool read_scalar_raw ( CPQDIF_E_Collection * collection , const GUID & tag , long & physical_type , PQDIFValue & value )
{
if ( collection = = nullptr )
return false ;
CPQDIF_Element * element = collection - > GetElement ( tag , ID_ELEMENT_TYPE_SCALAR ) ;
if ( element = = nullptr )
return false ;
CPQDIF_E_Scalar * scalar = static_cast < CPQDIF_E_Scalar * > ( element ) ;
return scalar - > GetValue ( physical_type , value ) ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
bool read_string_tag ( CPQDIF_E_Collection * collection , const GUID & tag , std : : string & out )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
if ( collection = = nullptr )
return false ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
CPQDIF_Element * element = collection - > GetElement ( tag , ID_ELEMENT_TYPE_VECTOR ) ;
if ( element = = nullptr )
return false ;
CPQDIF_E_Vector * vector = static_cast < CPQDIF_E_Vector * > ( element ) ;
std : : string text ;
if ( ! vector - > GetValues ( text ) )
2026-04-22 10:40:19 +08:00
return false ;
2026-04-29 13:35:49 +08:00
out = trim_trailing_nulls ( text ) ;
return true ;
}
bool read_uint_tag ( CPQDIF_E_Collection * collection , const GUID & tag , unsigned int & out )
{
long physical_type = - 1 ;
PQDIFValue value { } ;
if ( ! read_scalar_raw ( collection , tag , physical_type , value ) )
return false ;
switch ( physical_type )
{
case ID_PHYS_TYPE_UNS_INTEGER1 : out = static_cast < unsigned int > ( value . uint1 ) ; return true ;
case ID_PHYS_TYPE_UNS_INTEGER2 : out = static_cast < unsigned int > ( value . uint2 ) ; return true ;
case ID_PHYS_TYPE_UNS_INTEGER4 : out = static_cast < unsigned int > ( value . uint4 ) ; return true ;
case ID_PHYS_TYPE_INTEGER1 : out = static_cast < unsigned int > ( value . int1 ) ; return true ;
case ID_PHYS_TYPE_INTEGER2 : out = static_cast < unsigned int > ( value . int2 ) ; return true ;
case ID_PHYS_TYPE_INTEGER4 : out = static_cast < unsigned int > ( value . int4 ) ; return true ;
default : return false ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
bool read_int_tag ( CPQDIF_E_Collection * collection , const GUID & tag , int & out )
{
long physical_type = - 1 ;
PQDIFValue value { } ;
if ( ! read_scalar_raw ( collection , tag , physical_type , value ) )
return false ;
switch ( physical_type )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
case ID_PHYS_TYPE_INTEGER1 : out = static_cast < int > ( value . int1 ) ; return true ;
case ID_PHYS_TYPE_INTEGER2 : out = static_cast < int > ( value . int2 ) ; return true ;
case ID_PHYS_TYPE_INTEGER4 : out = static_cast < int > ( value . int4 ) ; return true ;
case ID_PHYS_TYPE_UNS_INTEGER1 : out = static_cast < int > ( value . uint1 ) ; return true ;
case ID_PHYS_TYPE_UNS_INTEGER2 : out = static_cast < int > ( value . uint2 ) ; return true ;
case ID_PHYS_TYPE_UNS_INTEGER4 : out = static_cast < int > ( value . uint4 ) ; return true ;
default : return false ;
}
}
bool read_bool_tag ( CPQDIF_E_Collection * collection , const GUID & tag , bool & out )
{
long physical_type = - 1 ;
PQDIFValue value { } ;
if ( ! read_scalar_raw ( collection , tag , physical_type , value ) )
2026-04-22 10:40:19 +08:00
return false ;
2026-04-29 13:35:49 +08:00
switch ( physical_type )
{
case ID_PHYS_TYPE_BOOLEAN1 : out = ( value . bool1 ! = 0 ) ; return true ;
case ID_PHYS_TYPE_BOOLEAN2 : out = ( value . bool2 ! = 0 ) ; return true ;
case ID_PHYS_TYPE_BOOLEAN4 : out = ( value . bool4 ! = 0 ) ; return true ;
default : return false ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
bool read_double_tag ( CPQDIF_E_Collection * collection , const GUID & tag , double & out )
{
long physical_type = - 1 ;
PQDIFValue value { } ;
if ( ! read_scalar_raw ( collection , tag , physical_type , value ) )
return false ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
switch ( physical_type )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
case ID_PHYS_TYPE_REAL4 : out = static_cast < double > ( value . real4 ) ; return true ;
case ID_PHYS_TYPE_REAL8 : out = value . real8 ; return true ;
case ID_PHYS_TYPE_INTEGER1 : out = static_cast < double > ( value . int1 ) ; return true ;
case ID_PHYS_TYPE_INTEGER2 : out = static_cast < double > ( value . int2 ) ; return true ;
case ID_PHYS_TYPE_INTEGER4 : out = static_cast < double > ( value . int4 ) ; return true ;
case ID_PHYS_TYPE_UNS_INTEGER1 : out = static_cast < double > ( value . uint1 ) ; return true ;
case ID_PHYS_TYPE_UNS_INTEGER2 : out = static_cast < double > ( value . uint2 ) ; return true ;
case ID_PHYS_TYPE_UNS_INTEGER4 : out = static_cast < double > ( value . uint4 ) ; return true ;
default : return false ;
}
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
bool read_guid_tag ( CPQDIF_E_Collection * collection , const GUID & tag , PqdifGuidValue & out )
{
long physical_type = - 1 ;
PQDIFValue value { } ;
if ( ! read_scalar_raw ( collection , tag , physical_type , value ) )
return false ;
if ( physical_type ! = ID_PHYS_TYPE_GUID )
return false ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
out = make_guid_value ( value . guid ) ;
return true ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
bool read_timestamp_tag ( CPQDIF_E_Collection * collection , const GUID & tag , CPQDIF & file_convert , PqdifTimestampValue & out )
{
long physical_type = - 1 ;
PQDIFValue value { } ;
if ( ! read_scalar_raw ( collection , tag , physical_type , value ) )
return false ;
if ( physical_type ! = ID_PHYS_TYPE_TIMESTAMPPQDIF )
return false ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
out = make_timestamp_value ( file_convert , value . ts ) ;
return true ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
std : : vector < unsigned int > read_vector_uint_values ( CPQDIF_E_Collection * collection , const GUID & tag )
{
std : : vector < unsigned int > out ;
if ( collection = = nullptr )
return out ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
CPQDIF_Element * element = collection - > GetElement ( tag , ID_ELEMENT_TYPE_VECTOR ) ;
if ( element = = nullptr )
return out ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
CPQDIF_E_Vector * vector = static_cast < CPQDIF_E_Vector * > ( element ) ;
long count = 0 ;
vector - > GetCount ( count ) ;
for ( long i = 0 ; i < count ; + + i )
{
PQDIFValue value { } ;
if ( ! vector - > GetValue ( i , value ) )
2026-04-22 10:40:19 +08:00
continue ;
2026-04-29 13:35:49 +08:00
switch ( vector - > GetPhysicalType ( ) )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
case ID_PHYS_TYPE_UNS_INTEGER1 : out . push_back ( static_cast < unsigned int > ( value . uint1 ) ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER2 : out . push_back ( static_cast < unsigned int > ( value . uint2 ) ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER4 : out . push_back ( static_cast < unsigned int > ( value . uint4 ) ) ; break ;
case ID_PHYS_TYPE_INTEGER1 : out . push_back ( static_cast < unsigned int > ( value . int1 ) ) ; break ;
case ID_PHYS_TYPE_INTEGER2 : out . push_back ( static_cast < unsigned int > ( value . int2 ) ) ; break ;
case ID_PHYS_TYPE_INTEGER4 : out . push_back ( static_cast < unsigned int > ( value . int4 ) ) ; break ;
default : break ;
}
}
return out ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
std : : vector < int > read_vector_int_values ( CPQDIF_E_Collection * collection , const GUID & tag )
{
std : : vector < int > out ;
if ( collection = = nullptr )
return out ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
CPQDIF_Element * element = collection - > GetElement ( tag , ID_ELEMENT_TYPE_VECTOR ) ;
if ( element = = nullptr )
return out ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
CPQDIF_E_Vector * vector = static_cast < CPQDIF_E_Vector * > ( element ) ;
long count = 0 ;
vector - > GetCount ( count ) ;
for ( long i = 0 ; i < count ; + + i )
{
PQDIFValue value { } ;
if ( ! vector - > GetValue ( i , value ) )
continue ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
switch ( vector - > GetPhysicalType ( ) )
{
case ID_PHYS_TYPE_INTEGER1 : out . push_back ( static_cast < int > ( value . int1 ) ) ; break ;
case ID_PHYS_TYPE_INTEGER2 : out . push_back ( static_cast < int > ( value . int2 ) ) ; break ;
case ID_PHYS_TYPE_INTEGER4 : out . push_back ( static_cast < int > ( value . int4 ) ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER1 : out . push_back ( static_cast < int > ( value . uint1 ) ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER2 : out . push_back ( static_cast < int > ( value . uint2 ) ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER4 : out . push_back ( static_cast < int > ( value . uint4 ) ) ; break ;
default : break ;
}
}
return out ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
PqdifValueArray extract_vector_values ( CPQDIF_E_Vector * vector , CPQDIF & file_convert )
{
PqdifValueArray out ;
if ( vector = = nullptr )
return out ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
out . physical_type = vector - > GetPhysicalType ( ) ;
vector - > GetCount ( out . count ) ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
if ( out . physical_type = = ID_PHYS_TYPE_CHAR1 | | out . physical_type = = ID_PHYS_TYPE_CHAR2 )
{
std : : string text ;
if ( vector - > GetValues ( text ) )
out . text_values . push_back ( trim_trailing_nulls ( text ) ) ;
return out ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
for ( long i = 0 ; i < out . count ; + + i )
{
PQDIFValue value { } ;
if ( ! vector - > GetValue ( i , value ) )
continue ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
switch ( out . physical_type )
{
case ID_PHYS_TYPE_BOOLEAN1 : out . bool_values . push_back ( value . bool1 ! = 0 ) ; break ;
case ID_PHYS_TYPE_BOOLEAN2 : out . bool_values . push_back ( value . bool2 ! = 0 ) ; break ;
case ID_PHYS_TYPE_BOOLEAN4 : out . bool_values . push_back ( value . bool4 ! = 0 ) ; break ;
case ID_PHYS_TYPE_INTEGER1 : out . int_values . push_back ( static_cast < long long > ( value . int1 ) ) ; break ;
case ID_PHYS_TYPE_INTEGER2 : out . int_values . push_back ( static_cast < long long > ( value . int2 ) ) ; break ;
case ID_PHYS_TYPE_INTEGER4 : out . int_values . push_back ( static_cast < long long > ( value . int4 ) ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER1 : out . uint_values . push_back ( static_cast < unsigned long long > ( value . uint1 ) ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER2 : out . uint_values . push_back ( static_cast < unsigned long long > ( value . uint2 ) ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER4 : out . uint_values . push_back ( static_cast < unsigned long long > ( value . uint4 ) ) ; break ;
case ID_PHYS_TYPE_REAL4 : out . real_values . push_back ( static_cast < double > ( value . real4 ) ) ; break ;
case ID_PHYS_TYPE_REAL8 : out . real_values . push_back ( value . real8 ) ; break ;
case ID_PHYS_TYPE_COMPLEX8 : out . complex_values . push_back ( std : : complex < double > ( value . complex8 . real , value . complex8 . image ) ) ; break ;
case ID_PHYS_TYPE_COMPLEX16 : out . complex_values . push_back ( std : : complex < double > ( value . complex16 . real , value . complex16 . image ) ) ; break ;
case ID_PHYS_TYPE_TIMESTAMPPQDIF : out . timestamp_values . push_back ( make_timestamp_value ( file_convert , value . ts ) ) ; break ;
case ID_PHYS_TYPE_GUID : out . guid_values . push_back ( make_guid_value ( value . guid ) ) ; break ;
default : break ;
2026-04-22 10:40:19 +08:00
}
}
2026-04-29 13:35:49 +08:00
return out ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
std : : string scalar_value_to_text ( long physical_type , const PQDIFValue & value , CPQDIF & file_convert )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
std : : ostringstream oss ;
switch ( physical_type )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
case ID_PHYS_TYPE_BOOLEAN1 : oss < < ( value . bool1 ? " true " : " false " ) ; break ;
case ID_PHYS_TYPE_BOOLEAN2 : oss < < ( value . bool2 ? " true " : " false " ) ; break ;
case ID_PHYS_TYPE_BOOLEAN4 : oss < < ( value . bool4 ? " true " : " false " ) ; break ;
case ID_PHYS_TYPE_INTEGER1 : oss < < static_cast < int > ( value . int1 ) ; break ;
case ID_PHYS_TYPE_INTEGER2 : oss < < value . int2 ; break ;
case ID_PHYS_TYPE_INTEGER4 : oss < < value . int4 ; break ;
case ID_PHYS_TYPE_UNS_INTEGER1 : oss < < static_cast < unsigned int > ( value . uint1 ) ; break ;
case ID_PHYS_TYPE_UNS_INTEGER2 : oss < < value . uint2 ; break ;
case ID_PHYS_TYPE_UNS_INTEGER4 : oss < < value . uint4 ; break ;
case ID_PHYS_TYPE_REAL4 : oss < < value . real4 ; break ;
case ID_PHYS_TYPE_REAL8 : oss < < value . real8 ; break ;
case ID_PHYS_TYPE_COMPLEX8 : oss < < value . complex8 . real < < " + " < < value . complex8 . image < < " j " ; break ;
case ID_PHYS_TYPE_COMPLEX16 : oss < < value . complex16 . real < < " + " < < value . complex16 . image < < " j " ; break ;
case ID_PHYS_TYPE_TIMESTAMPPQDIF :
{
PqdifTimestampValue ts = make_timestamp_value ( file_convert , value . ts ) ;
oss < < ts . text ;
break ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
case ID_PHYS_TYPE_GUID : oss < < safe_tag_name ( value . guid ) < < " ( " < < guid_to_string ( value . guid ) < < " ) " ; break ;
default : oss < < " physical_type= " < < physical_type ; break ;
}
return oss . str ( ) ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
std : : string element_to_text ( CPQDIF_Element * element , CPQDIF & file_convert )
{
if ( element = = nullptr )
return std : : string ( ) ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
std : : ostringstream oss ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
if ( element - > GetElementType ( ) = = ID_ELEMENT_TYPE_SCALAR )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
CPQDIF_E_Scalar * scalar = static_cast < CPQDIF_E_Scalar * > ( element ) ;
long physical_type = - 1 ;
PQDIFValue value { } ;
if ( scalar - > GetValue ( physical_type , value ) )
return scalar_value_to_text ( physical_type , value , file_convert ) ;
return std : : string ( ) ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
if ( element - > GetElementType ( ) = = ID_ELEMENT_TYPE_VECTOR )
{
CPQDIF_E_Vector * vector = static_cast < CPQDIF_E_Vector * > ( element ) ;
PqdifValueArray values = extract_vector_values ( vector , file_convert ) ;
oss < < " vector(type= " < < values . physical_type < < " , count= " < < values . count < < " ) " ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
size_t shown = 0 ;
auto append_preview = [ & ] ( const std : : string & text ) {
if ( shown = = 0 )
oss < < " [ " ;
else
oss < < " , " ;
oss < < text ;
+ + shown ;
if ( shown = = 5 )
return false ;
return true ;
} ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
for ( size_t i = 0 ; i < values . real_values . size ( ) & & shown < 5 ; + + i )
append_preview ( std : : to_string ( values . real_values [ i ] ) ) ;
for ( size_t i = 0 ; i < values . int_values . size ( ) & & shown < 5 ; + + i )
append_preview ( std : : to_string ( values . int_values [ i ] ) ) ;
for ( size_t i = 0 ; i < values . uint_values . size ( ) & & shown < 5 ; + + i )
append_preview ( std : : to_string ( values . uint_values [ i ] ) ) ;
for ( size_t i = 0 ; i < values . bool_values . size ( ) & & shown < 5 ; + + i )
append_preview ( values . bool_values [ i ] ? " true " : " false " ) ;
for ( size_t i = 0 ; i < values . text_values . size ( ) & & shown < 5 ; + + i )
append_preview ( values . text_values [ i ] ) ;
for ( size_t i = 0 ; i < values . timestamp_values . size ( ) & & shown < 5 ; + + i )
append_preview ( values . timestamp_values [ i ] . text ) ;
for ( size_t i = 0 ; i < values . guid_values . size ( ) & & shown < 5 ; + + i )
append_preview ( values . guid_values [ i ] . symbolic_name ) ;
if ( shown > 0 )
oss < < ( values . count > 5 ? " , ...] " : " ] " ) ;
return oss . str ( ) ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
if ( element - > GetElementType ( ) = = ID_ELEMENT_TYPE_COLLECTION )
{
CPQDIF_E_Collection * collection = static_cast < CPQDIF_E_Collection * > ( element ) ;
oss < < " collection(count= " < < collection - > GetCount ( ) < < " ) " ;
return oss . str ( ) ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
return std : : string ( ) ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
void collect_extra_tags ( CPQDIF_E_Collection * collection ,
const std : : set < std : : string > & known_tag_names ,
CPQDIF & file_convert ,
PqdifExtraTagMap & out )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
out . clear ( ) ;
if ( collection = = nullptr )
2026-04-22 10:40:19 +08:00
return ;
2026-04-29 13:35:49 +08:00
const long count = collection - > GetCount ( ) ;
for ( long i = 0 ; i < count ; + + i )
2026-04-22 10:40:19 +08:00
{
2026-04-29 13:35:49 +08:00
CPQDIF_Element * element = collection - > GetElement ( i ) ;
if ( element = = nullptr )
2026-04-22 10:40:19 +08:00
continue ;
2026-04-29 13:35:49 +08:00
const std : : string tag_name = safe_tag_name ( element - > GetTag ( ) ) ;
if ( known_tag_names . find ( tag_name ) ! = known_tag_names . end ( ) )
2026-04-22 10:40:19 +08:00
continue ;
2026-04-29 13:35:49 +08:00
out [ tag_name ] = element_to_text ( element , file_convert ) ;
}
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
void dump_logical_summary ( const ParsedPqdifFile & parsed_file )
{
std : : cout < < " ========== PQDIF LOGICAL SUMMARY ========== " < < std : : endl ;
std : : cout < < " file= " < < parsed_file . source_file
< < " , records= " < < parsed_file . logical_file . record_headers . size ( )
< < " , containers= " < < parsed_file . logical_file . containers . size ( )
< < " , data_sources= " < < parsed_file . logical_file . data_sources . size ( )
< < " , settings= " < < parsed_file . logical_file . monitor_settings . size ( )
< < " , observations= " < < parsed_file . logical_file . observations . size ( )
< < std : : endl ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
if ( ! parsed_file . logical_file . observations . empty ( ) )
{
const PqdifObservationRecord & obs = parsed_file . logical_file . observations . front ( ) ;
std : : cout < < " first_observation= " < < obs . observation_name
< < " , start= " < < obs . time_start . text
< < " , channels= " < < obs . channel_instances . size ( )
< < std : : endl ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
std : : cout < < " ========================================== " < < std : : endl ;
2026-04-22 10:40:19 +08:00
}
2026-04-29 13:35:49 +08:00
/// @brief PQDIF 日志级别。
/// @details
/// 后续新增打印时请按级别输出:
/// - Core: 默认核心摘要, 必须短, 适合平时测试;
/// - Info: 处理流程、fallback 命中情况、文件移动等;
/// - Debug: observation 列表、疑似通道、指标来源排查;
/// - Trace: 逐通道/逐序列/全量 bucket 明细,日志量很大。
enum class PqdifLogLevel
{
Core = 0 ,
Info = 1 ,
Debug = 2 ,
Trace = 3
} ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
bool pqdif_env_truthy ( const char * v )
{
if ( v = = nullptr )
return false ;
std : : string s ( v ) ;
for ( char & ch : s )
ch = static_cast < char > ( std : : toupper ( static_cast < unsigned char > ( ch ) ) ) ;
return s = = " 1 " | | s = = " TRUE " | | s = = " YES " | | s = = " ON " | |
s = = " DEBUG " | | s = = " DETAIL " | | s = = " TRACE " | | s = = " VERBOSE " ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
PqdifLogLevel pqdif_parse_log_level ( const char * v )
{
if ( v = = nullptr | | * v = = ' \0 ' )
return PqdifLogLevel : : Core ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
std : : string s ( v ) ;
for ( char & ch : s )
ch = static_cast < char > ( std : : toupper ( static_cast < unsigned char > ( ch ) ) ) ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
if ( s = = " 0 " | | s = = " CORE " | | s = = " QUIET " ) return PqdifLogLevel : : Core ;
if ( s = = " 1 " | | s = = " INFO " ) return PqdifLogLevel : : Info ;
if ( s = = " 2 " | | s = = " DEBUG " | | s = = " DETAIL " ) return PqdifLogLevel : : Debug ;
if ( s = = " 3 " | | s = = " TRACE " | | s = = " VERBOSE " ) return PqdifLogLevel : : Trace ;
return pqdif_env_truthy ( v ) ? PqdifLogLevel : : Debug : PqdifLogLevel : : Core ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
PqdifLogLevel pqdif_current_log_level ( )
{
static int cached = - 1 ;
if ( cached > = 0 )
return static_cast < PqdifLogLevel > ( cached ) ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
PqdifLogLevel level = pqdif_parse_log_level ( std : : getenv ( " PQDIF_LOG_LEVEL " ) ) ;
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
// 向后兼容旧开关。
if ( level = = PqdifLogLevel : : Core )
{
if ( pqdif_env_truthy ( std : : getenv ( " PQDIF_DETAIL_LOG " ) ) )
level = PqdifLogLevel : : Debug ;
if ( pqdif_env_truthy ( std : : getenv ( " PQDIF_VERBOSE " ) ) )
level = PqdifLogLevel : : Trace ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
cached = static_cast < int > ( level ) ;
return level ;
}
2026-04-22 10:40:19 +08:00
2026-04-29 13:35:49 +08:00
bool pqdif_log_enabled ( PqdifLogLevel level )
{
return static_cast < int > ( pqdif_current_log_level ( ) ) > = static_cast < int > ( level ) ;
}
/// @brief 是否开启详细调试日志。保留旧函数名,便于已有打印逻辑复用。
bool pqdif_is_detail_log_enabled ( )
{
return pqdif_log_enabled ( PqdifLogLevel : : Debug ) ;
}
bool pqdif_is_trace_log_enabled ( )
{
return pqdif_log_enabled ( PqdifLogLevel : : Trace ) ;
}
std : : string short_guid_name ( const PqdifGuidValue & g )
{
if ( ! g . symbolic_name . empty ( ) )
return g . symbolic_name ;
return guid_to_string ( g . value ) ;
}
std : : string preview_value_array ( const PqdifValueArray & v )
{
std : : ostringstream oss ;
oss < < " physical_type= " < < v . physical_type
< < " , count= " < < v . count ;
auto append_real_preview = [ & ] ( const std : : vector < double > & arr , const char * name )
{
if ( arr . empty ( ) )
return ;
oss < < " , " < < name < < " =[ " ;
for ( size_t i = 0 ; i < arr . size ( ) & & i < 3 ; + + i )
{
if ( i > 0 ) oss < < " , " ;
oss < < arr [ i ] ;
}
if ( arr . size ( ) > 3 ) oss < < " , ... " ;
oss < < " ] " ;
} ;
auto append_int_preview = [ & ] ( const std : : vector < long long > & arr , const char * name )
{
if ( arr . empty ( ) )
return ;
oss < < " , " < < name < < " =[ " ;
for ( size_t i = 0 ; i < arr . size ( ) & & i < 3 ; + + i )
{
if ( i > 0 ) oss < < " , " ;
oss < < arr [ i ] ;
}
if ( arr . size ( ) > 3 ) oss < < " , ... " ;
oss < < " ] " ;
} ;
auto append_uint_preview = [ & ] ( const std : : vector < unsigned long long > & arr , const char * name )
{
if ( arr . empty ( ) )
return ;
oss < < " , " < < name < < " =[ " ;
for ( size_t i = 0 ; i < arr . size ( ) & & i < 3 ; + + i )
{
if ( i > 0 ) oss < < " , " ;
oss < < arr [ i ] ;
}
if ( arr . size ( ) > 3 ) oss < < " , ... " ;
oss < < " ] " ;
} ;
auto append_time_preview = [ & ] ( const std : : vector < PqdifTimestampValue > & arr , const char * name )
{
if ( arr . empty ( ) )
return ;
oss < < " , " < < name < < " =[ " ;
for ( size_t i = 0 ; i < arr . size ( ) & & i < 3 ; + + i )
{
if ( i > 0 ) oss < < " , " ;
oss < < arr [ i ] . text ;
}
if ( arr . size ( ) > 3 ) oss < < " , ... " ;
oss < < " ] " ;
} ;
append_real_preview ( v . real_values , " real " ) ;
append_int_preview ( v . int_values , " int " ) ;
append_uint_preview ( v . uint_values , " uint " ) ;
append_time_preview ( v . timestamp_values , " time " ) ;
if ( ! v . text_values . empty ( ) )
{
oss < < " , text=[ " ;
for ( size_t i = 0 ; i < v . text_values . size ( ) & & i < 2 ; + + i )
{
if ( i > 0 ) oss < < " , " ;
oss < < " \" " < < v . text_values [ i ] < < " \" " ;
}
if ( v . text_values . size ( ) > 2 ) oss < < " , ... " ;
oss < < " ] " ;
}
return oss . str ( ) ;
}
void dump_semantic_probe ( const ParsedPqdifFile & parsed_file )
{
const auto & lf = parsed_file . logical_file ;
const auto & reg = pqdif_sem : : GetGuidSemanticRegistry ( ) ;
std : : cout < < " ========== PQDIF SEMANTIC PROBE ========== " < < std : : endl ;
std : : cout < < " file= " < < parsed_file . source_file < < std : : endl ;
std : : cout < < " data_sources= " < < lf . data_sources . size ( )
< < " , observations= " < < lf . observations . size ( )
< < std : : endl ;
// --------------------------------------------------------------------
// 1) 打印第一条数据源的前几个通道定义
// --------------------------------------------------------------------
if ( ! lf . data_sources . empty ( ) )
{
const auto & ds = lf . data_sources . front ( ) ;
std : : cout < < " [DEF] data_source[0] "
< < " name= " < < ds . name
< < " , model= " < < ds . instrument_model_name
< < " , channels= " < < ds . channel_definitions . size ( )
< < std : : endl ;
const size_t ch_limit = std : : min < size_t > ( ds . channel_definitions . size ( ) , 3 ) ;
for ( size_t i = 0 ; i < ch_limit ; + + i )
{
const auto & ch = ds . channel_definitions [ i ] ;
std : : cout < < " [CH-DEF " < < i < < " ] "
< < " name= " < < ch . channel_name
< < " , phase= " < < pqdif_sem : : FindPhaseName ( ch . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( ch . quantity_measured_id )
< < " , qty_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityType ,
ch . quantity_type_id . value ,
short_guid_name ( ch . quantity_type_id ) . c_str ( ) )
< < " , series_defs= " < < ch . series_definitions . size ( )
< < std : : endl ;
const size_t sd_limit = std : : min < size_t > ( ch . series_definitions . size ( ) , 5 ) ;
for ( size_t j = 0 ; j < sd_limit ; + + j )
{
const auto & sd = ch . series_definitions [ j ] ;
std : : cout < < " [SER-DEF " < < j < < " ] "
< < " value_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : ValueType ,
sd . value_type_id . value ,
short_guid_name ( sd . value_type_id ) . c_str ( ) )
< < " , characteristic= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityCharacteristic ,
sd . quantity_characteristic_id . value ,
short_guid_name ( sd . quantity_characteristic_id ) . c_str ( ) )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( sd . quantity_units_id )
< < " , storage= " < < pqdif_sem : : FindStorageMethodFlagsName ( sd . storage_method_id )
< < " , nominal= " < < sd . nominal_quantity
< < std : : endl ;
}
}
}
// --------------------------------------------------------------------
// 2) 打印第一条 observation 的前几个通道实例
// --------------------------------------------------------------------
if ( ! lf . observations . empty ( ) )
{
const auto & obs = lf . observations . front ( ) ;
std : : cout < < " [OBS] observation[0] "
< < " name= " < < obs . observation_name
< < " , start= " < < obs . time_start . text
< < " , trigger= " < < pqdif_sem : : FindTriggerMethodName ( obs . trigger_method_id )
< < " , channels= " < < obs . channel_instances . size ( )
< < std : : endl ;
const size_t ch_limit = std : : min < size_t > ( obs . channel_instances . size ( ) , 3 ) ;
for ( size_t i = 0 ; i < ch_limit ; + + i )
{
const auto & ch = obs . channel_instances [ i ] ;
std : : cout < < " [CH-INS " < < i < < " ] "
< < " name= " < < ch . channel_name
< < " , phase= " < < pqdif_sem : : FindPhaseName ( ch . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( ch . quantity_measured_id )
< < " , qty_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityType ,
ch . quantity_type_id . value ,
short_guid_name ( ch . quantity_type_id ) . c_str ( ) )
< < " , freq= " < < ch . channel_frequency
< < " , group= " < < ch . channel_group_id
< < " , series_instances= " < < ch . series_instances . size ( )
< < std : : endl ;
const size_t si_limit = std : : min < size_t > ( ch . series_instances . size ( ) , 5 ) ;
for ( size_t j = 0 ; j < si_limit ; + + j )
{
const auto & si = ch . series_instances [ j ] ;
std : : cout < < " [SER-INS " < < j < < " ] "
< < " value_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : ValueType ,
si . value_type_id . value ,
short_guid_name ( si . value_type_id ) . c_str ( ) )
< < " , characteristic= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityCharacteristic ,
si . quantity_characteristic_id . value ,
short_guid_name ( si . quantity_characteristic_id ) . c_str ( ) )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( si . quantity_units_id )
< < " , storage=N/A "
< < " , " < < preview_value_array ( si . values )
< < " , share_ch= " < < si . share_channel_index
< < " , share_ser= " < < si . share_series_index
< < " , scale= " < < si . scale
< < " , offset= " < < si . offset
< < std : : endl ;
}
}
}
std : : cout < < " ========================================== " < < std : : endl ;
}
long max_series_value_count_in_channel ( const PqdifChannelInstance & ch )
{
long max_count = 0 ;
for ( const auto & si : ch . series_instances )
max_count = std : : max < long > ( max_count , si . values . count ) ;
return max_count ;
}
long max_series_value_count_in_observation ( const PqdifObservationRecord & obs )
{
long max_count = 0 ;
for ( const auto & ch : obs . channel_instances )
max_count = std : : max < long > ( max_count , max_series_value_count_in_channel ( ch ) ) ;
return max_count ;
}
const PqdifChannelDefinition * find_channel_definition (
const PqdifLogicalFile & lf ,
const PqdifObservationRecord & obs ,
const PqdifChannelInstance & ch )
{
if ( obs . related_data_source_index < 0 | |
obs . related_data_source_index > = static_cast < int > ( lf . data_sources . size ( ) ) )
return nullptr ;
const auto & ds = lf . data_sources [ static_cast < size_t > ( obs . related_data_source_index ) ] ;
if ( ch . channel_def_index < 0 | |
ch . channel_def_index > = static_cast < int > ( ds . channel_definitions . size ( ) ) )
return nullptr ;
return & ds . channel_definitions [ static_cast < size_t > ( ch . channel_def_index ) ] ;
}
bool is_integer_in_range ( double value , int min_value , int max_value )
{
if ( ! std : : isfinite ( value ) )
return false ;
const double rounded = std : : floor ( value + 0.5 ) ;
if ( std : : fabs ( value - rounded ) > 1e-6 )
return false ;
const int n = static_cast < int > ( rounded ) ;
return n > = min_value & & n < = max_value ;
}
std : : string harmonic_order_token ( int order )
{
char buf [ 8 ] = { 0 } ;
std : : snprintf ( buf , sizeof ( buf ) , " %02d " , order ) ;
return std : : string ( buf ) ;
}
bool text_looks_like_harmonic ( const std : : string & text )
{
const std : : string key = normalize_key ( text ) ;
if ( key . empty ( ) )
return false ;
if ( key . find ( " HARM " ) ! = std : : string : : npos | |
key . find ( " HARMONIC " ) ! = std : : string : : npos | |
key . find ( " THD " ) ! = std : : string : : npos | |
key . find ( " IHD " ) ! = std : : string : : npos | |
key . find ( " VHD " ) ! = std : : string : : npos | |
key . find ( " HRMS " ) ! = std : : string : : npos )
return true ;
for ( int order = 2 ; order < = 50 ; + + order )
{
const std : : string two = harmonic_order_token ( order ) ;
const std : : string plain = std : : to_string ( order ) ;
const std : : string patterns [ ] = {
" H " + two , " H " + plain ,
" HD " + two , " HD " + plain ,
" VH " + two , " VH " + plain ,
" UH " + two , " UH " + plain ,
" IH " + two , " IH " + plain
} ;
for ( const auto & pat : patterns )
{
if ( key . find ( pat ) ! = std : : string : : npos )
return true ;
}
}
return false ;
}
bool extra_tags_look_like_harmonic ( const PqdifExtraTagMap & tags )
{
for ( const auto & kv : tags )
{
if ( text_looks_like_harmonic ( kv . first ) | | text_looks_like_harmonic ( kv . second ) )
return true ;
}
return false ;
}
bool series_definition_looks_like_harmonic ( const PqdifSeriesDefinition & sd )
{
if ( is_integer_in_range ( sd . nominal_quantity , 2 , 50 ) )
return true ;
if ( text_looks_like_harmonic ( sd . value_type_name ) | |
text_looks_like_harmonic ( sd . value_type_id . symbolic_name ) | |
text_looks_like_harmonic ( sd . quantity_characteristic_id . symbolic_name ) | |
extra_tags_look_like_harmonic ( sd . extra_tags ) )
return true ;
return false ;
}
bool series_instance_looks_like_harmonic ( const PqdifSeriesInstance & si )
{
if ( is_integer_in_range ( si . series_base_quantity , 2 , 50 ) | |
is_integer_in_range ( si . nominal_quantity , 2 , 50 ) )
return true ;
if ( text_looks_like_harmonic ( si . value_type_id . symbolic_name ) | |
text_looks_like_harmonic ( si . quantity_characteristic_id . symbolic_name ) | |
extra_tags_look_like_harmonic ( si . extra_tags ) )
return true ;
return false ;
}
bool channel_definition_looks_like_harmonic ( const PqdifChannelDefinition & ch )
{
if ( text_looks_like_harmonic ( ch . channel_name ) | |
text_looks_like_harmonic ( ch . quantity_name ) | |
text_looks_like_harmonic ( ch . group_name ) | |
text_looks_like_harmonic ( ch . other_channel_identifier ) | |
text_looks_like_harmonic ( ch . quantity_type_id . symbolic_name ) | |
extra_tags_look_like_harmonic ( ch . extra_tags ) )
return true ;
for ( const auto & sd : ch . series_definitions )
{
if ( series_definition_looks_like_harmonic ( sd ) )
return true ;
}
return false ;
}
bool channel_instance_looks_like_harmonic (
const PqdifChannelInstance & ch ,
const PqdifChannelDefinition * def )
{
if ( text_looks_like_harmonic ( ch . channel_name ) | |
text_looks_like_harmonic ( ch . quantity_type_id . symbolic_name ) | |
extra_tags_look_like_harmonic ( ch . extra_tags ) )
return true ;
if ( def ! = nullptr & & channel_definition_looks_like_harmonic ( * def ) )
return true ;
for ( const auto & si : ch . series_instances )
{
if ( series_instance_looks_like_harmonic ( si ) )
return true ;
}
return false ;
}
void dump_observation_list ( const ParsedPqdifFile & parsed_file )
{
const auto & lf = parsed_file . logical_file ;
std : : cout < < " ========== PQDIF OBSERVATION LIST ========== " < < std : : endl ;
std : : cout < < " file= " < < parsed_file . source_file
< < " , observation_count= " < < lf . observations . size ( )
< < std : : endl ;
for ( size_t i = 0 ; i < lf . observations . size ( ) ; + + i )
{
const auto & obs = lf . observations [ i ] ;
std : : cout < < " [OBS " < < i < < " ] "
< < " index= " < < obs . observation_index
< < " , record= " < < obs . record_index
< < " , name= " < < obs . observation_name
< < " , start= " < < obs . time_start . text
< < " , create= " < < obs . time_create . text
< < " , trigger= " < < pqdif_sem : : FindTriggerMethodName ( obs . trigger_method_id )
< < " , triggered= " < < obs . time_triggered . text
< < " , channels= " < < obs . channel_instances . size ( )
< < " , max_series_count= " < < max_series_value_count_in_observation ( obs )
< < " , related_ds= " < < obs . related_data_source_index
< < " , related_settings= " < < obs . related_settings_index
< < std : : endl ;
const size_t preview_count = std : : min < size_t > ( obs . channel_instances . size ( ) , 10 ) ;
if ( preview_count > 0 )
{
std : : cout < < " channel_preview= " ;
for ( size_t j = 0 ; j < preview_count ; + + j )
{
const auto & ch = obs . channel_instances [ j ] ;
if ( j > 0 )
std : : cout < < " | " ;
std : : cout < < " # " < < ch . channel_instance_index < < " : " < < ch . channel_name ;
}
if ( obs . channel_instances . size ( ) > preview_count )
std : : cout < < " | ... " ;
std : : cout < < std : : endl ;
}
}
std : : cout < < " ============================================ " < < std : : endl ;
}
void dump_harmonic_channel_probe ( const ParsedPqdifFile & parsed_file )
{
const auto & lf = parsed_file . logical_file ;
const auto & reg = pqdif_sem : : GetGuidSemanticRegistry ( ) ;
std : : cout < < " ========== PQDIF HARMONIC CHANNEL PROBE ========== " < < std : : endl ;
std : : cout < < " file= " < < parsed_file . source_file < < std : : endl ;
std : : cout < < " rule=print channel definitions/instances whose name, GUID name, extra tags, "
< < " series_base_quantity or nominal_quantity looks like harmonic order 2-50 "
< < std : : endl ;
size_t def_hits = 0 ;
for ( size_t ds_idx = 0 ; ds_idx < lf . data_sources . size ( ) ; + + ds_idx )
{
const auto & ds = lf . data_sources [ ds_idx ] ;
for ( size_t ch_idx = 0 ; ch_idx < ds . channel_definitions . size ( ) ; + + ch_idx )
{
const auto & ch = ds . channel_definitions [ ch_idx ] ;
if ( ! channel_definition_looks_like_harmonic ( ch ) )
continue ;
+ + def_hits ;
std : : cout < < " [HARM-DEF " < < def_hits < < " ] "
< < " ds= " < < ds_idx
< < " , ch_def= " < < ch . channel_def_index
< < " , name= " < < ch . channel_name
< < " , phase= " < < pqdif_sem : : FindPhaseName ( ch . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( ch . quantity_measured_id )
< < " , qty_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityType ,
ch . quantity_type_id . value ,
short_guid_name ( ch . quantity_type_id ) . c_str ( ) )
< < " , physical_ch= " < < ch . physical_channel
< < " , primary_series= " < < ch . primary_series_index
< < " , series_defs= " < < ch . series_definitions . size ( )
< < std : : endl ;
const size_t sd_limit = std : : min < size_t > ( ch . series_definitions . size ( ) , 8 ) ;
for ( size_t j = 0 ; j < sd_limit ; + + j )
{
const auto & sd = ch . series_definitions [ j ] ;
std : : cout < < " [HARM-SER-DEF " < < j < < " ] "
< < " value_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : ValueType ,
sd . value_type_id . value ,
short_guid_name ( sd . value_type_id ) . c_str ( ) )
< < " , characteristic= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityCharacteristic ,
sd . quantity_characteristic_id . value ,
short_guid_name ( sd . quantity_characteristic_id ) . c_str ( ) )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( sd . quantity_units_id )
< < " , storage= " < < pqdif_sem : : FindStorageMethodFlagsName ( sd . storage_method_id )
< < " , nominal= " < < sd . nominal_quantity
< < " , percentile= " < < sd . prob_percentile
< < std : : endl ;
}
}
}
size_t inst_hits = 0 ;
for ( size_t obs_idx = 0 ; obs_idx < lf . observations . size ( ) ; + + obs_idx )
{
const auto & obs = lf . observations [ obs_idx ] ;
for ( size_t ch_idx = 0 ; ch_idx < obs . channel_instances . size ( ) ; + + ch_idx )
{
const auto & ch = obs . channel_instances [ ch_idx ] ;
const PqdifChannelDefinition * def = find_channel_definition ( lf , obs , ch ) ;
if ( ! channel_instance_looks_like_harmonic ( ch , def ) )
continue ;
+ + inst_hits ;
std : : cout < < " [HARM-INS " < < inst_hits < < " ] "
< < " obs= " < < obs_idx
< < " , obs_name= " < < obs . observation_name
< < " , obs_start= " < < obs . time_start . text
< < " , ch= " < < ch . channel_instance_index
< < " , ch_def= " < < ch . channel_def_index
< < " , name= " < < ch . channel_name
< < " , def_name= " < < ( def ! = nullptr ? def - > channel_name : std : : string ( ) )
< < " , phase= " < < pqdif_sem : : FindPhaseName ( ch . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( ch . quantity_measured_id )
< < " , qty_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityType ,
ch . quantity_type_id . value ,
short_guid_name ( ch . quantity_type_id ) . c_str ( ) )
< < " , group= " < < ch . channel_group_id
< < " , freq= " < < ch . channel_frequency
< < " , primary_series= " < < ch . primary_series_index
< < " , series_instances= " < < ch . series_instances . size ( )
< < std : : endl ;
const size_t si_limit = std : : min < size_t > ( ch . series_instances . size ( ) , 8 ) ;
for ( size_t j = 0 ; j < si_limit ; + + j )
{
const auto & si = ch . series_instances [ j ] ;
std : : cout < < " [HARM-SER-INS " < < j < < " ] "
< < " value_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : ValueType ,
si . value_type_id . value ,
short_guid_name ( si . value_type_id ) . c_str ( ) )
< < " , characteristic= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityCharacteristic ,
si . quantity_characteristic_id . value ,
short_guid_name ( si . quantity_characteristic_id ) . c_str ( ) )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( si . quantity_units_id )
< < " , base_q= " < < si . series_base_quantity
< < " , nominal= " < < si . nominal_quantity
< < " , scale= " < < si . scale
< < " , offset= " < < si . offset
< < " , share_ch= " < < si . share_channel_index
< < " , share_ser= " < < si . share_series_index
< < " , " < < preview_value_array ( si . values )
< < std : : endl ;
}
}
}
if ( def_hits = = 0 & & inst_hits = = 0 )
{
std : : cout < < " [HARMONIC PROBE RESULT] no suspected harmonic channel found in definitions or observations "
< < std : : endl ;
}
else
{
std : : cout < < " [HARMONIC PROBE RESULT] definition_candidates= " < < def_hits
< < " , observation_channel_candidates= " < < inst_hits
< < std : : endl ;
}
std : : cout < < " ================================================= " < < std : : endl ;
}
int stat_make_channel_spectrum_order_hint (
const PqdifObservationRecord & obs ,
const PqdifChannelInstance & ch ,
int & block_offset ,
int & block_size ) ;
bool pqdif_probe_guid_is_harmonic_characteristic ( const GUID & g )
{
return PQDIF_IsEqualGUID ( g , ID_QC_HRMS ) | |
PQDIF_IsEqualGUID ( g , ID_QC_SPECTRA ) | |
PQDIF_IsEqualGUID ( g , ID_QC_SPECTRA_HGROUP ) ;
}
bool pqdif_probe_guid_is_interharmonic_characteristic ( const GUID & g )
{
return PQDIF_IsEqualGUID ( g , ID_QC_SPECTRA_IGROUP ) ;
}
bool pqdif_probe_guid_is_phase_angle_value_type ( const GUID & g )
{
return PQDIF_IsEqualGUID ( g , ID_SERIES_VALUE_TYPE_PHASEANGLE ) | |
PQDIF_IsEqualGUID ( g , ID_SERIES_VALUE_TYPE_PHASEANGLE_MIN ) | |
PQDIF_IsEqualGUID ( g , ID_SERIES_VALUE_TYPE_PHASEANGLE_MAX ) | |
PQDIF_IsEqualGUID ( g , ID_SERIES_VALUE_TYPE_PHASEANGLE_AVG ) ;
}
bool pqdif_probe_text_looks_like_interharmonic ( const std : : string & text )
{
const std : : string key = normalize_key ( text ) ;
return key . find ( " INTERHARM " ) ! = std : : string : : npos | |
key . find ( " INTERHARMONIC " ) ! = std : : string : : npos | |
key . find ( " IHARM " ) ! = std : : string : : npos | |
key . find ( " IGROUP " ) ! = std : : string : : npos | |
key . find ( " IHGROUP " ) ! = std : : string : : npos ;
}
bool pqdif_probe_text_looks_like_angle ( const std : : string & text )
{
const std : : string key = normalize_key ( text ) ;
return key . find ( " ANGLE " ) ! = std : : string : : npos | |
key . find ( " PHASEANGLE " ) ! = std : : string : : npos | |
key . find ( " PANGLE " ) ! = std : : string : : npos | |
key . find ( " HANGLE " ) ! = std : : string : : npos ;
}
bool pqdif_probe_phase_is_line_voltage ( unsigned int phase_id , const std : : string & name )
{
if ( phase_id = = ID_PHASE_AB | | phase_id = = ID_PHASE_BC | | phase_id = = ID_PHASE_CA )
return true ;
const std : : string key = normalize_key ( name ) ;
return key . find ( " UAB " ) ! = std : : string : : npos | | key . find ( " VAB " ) ! = std : : string : : npos | |
key . find ( " UBC " ) ! = std : : string : : npos | | key . find ( " VBC " ) ! = std : : string : : npos | |
key . find ( " UCA " ) ! = std : : string : : npos | | key . find ( " VCA " ) ! = std : : string : : npos | |
key . find ( " AB " ) ! = std : : string : : npos | | key . find ( " BC " ) ! = std : : string : : npos | | key . find ( " CA " ) ! = std : : string : : npos ;
}
bool pqdif_probe_series_def_is_dynamic_candidate ( const PqdifSeriesDefinition & sd )
{
return pqdif_probe_guid_is_harmonic_characteristic ( sd . quantity_characteristic_id . value ) | |
pqdif_probe_guid_is_interharmonic_characteristic ( sd . quantity_characteristic_id . value ) | |
pqdif_probe_guid_is_phase_angle_value_type ( sd . value_type_id . value ) | |
sd . quantity_units_id = = ID_QU_DEGREES | |
text_looks_like_harmonic ( sd . quantity_characteristic_id . symbolic_name ) | |
pqdif_probe_text_looks_like_interharmonic ( sd . quantity_characteristic_id . symbolic_name ) | |
pqdif_probe_text_looks_like_angle ( sd . value_type_id . symbolic_name ) ;
}
bool pqdif_probe_series_ins_is_dynamic_candidate ( const PqdifSeriesInstance & si )
{
return pqdif_probe_guid_is_harmonic_characteristic ( si . quantity_characteristic_id . value ) | |
pqdif_probe_guid_is_interharmonic_characteristic ( si . quantity_characteristic_id . value ) | |
pqdif_probe_guid_is_phase_angle_value_type ( si . value_type_id . value ) | |
si . quantity_units_id = = ID_QU_DEGREES | |
is_integer_in_range ( si . series_base_quantity , 2 , 50 ) | |
is_integer_in_range ( si . nominal_quantity , 2 , 50 ) | |
text_looks_like_harmonic ( si . quantity_characteristic_id . symbolic_name ) | |
pqdif_probe_text_looks_like_interharmonic ( si . quantity_characteristic_id . symbolic_name ) | |
pqdif_probe_text_looks_like_angle ( si . value_type_id . symbolic_name ) ;
}
void dump_dynamic_spectrum_candidate_probe ( const ParsedPqdifFile & parsed_file )
{
const auto & lf = parsed_file . logical_file ;
const auto & reg = pqdif_sem : : GetGuidSemanticRegistry ( ) ;
std : : cout < < " ========== PQDIF DYNAMIC SPECTRUM CANDIDATE PROBE ========== " < < std : : endl ;
std : : cout < < " file= " < < parsed_file . source_file < < std : : endl ;
std : : cout < < " rule=DEBUG level: print all channel definitions/instances that look like harmonic, angle, interharmonic, spectra, HRMS, HGroup or IGroup " < < std : : endl ;
size_t def_hits = 0 ;
size_t def_line_like = 0 ;
size_t def_inter_like = 0 ;
for ( size_t ds_idx = 0 ; ds_idx < lf . data_sources . size ( ) ; + + ds_idx )
{
const auto & ds = lf . data_sources [ ds_idx ] ;
for ( size_t ch_idx = 0 ; ch_idx < ds . channel_definitions . size ( ) ; + + ch_idx )
{
const auto & ch = ds . channel_definitions [ ch_idx ] ;
bool candidate = channel_definition_looks_like_harmonic ( ch ) | |
pqdif_probe_text_looks_like_interharmonic ( ch . channel_name ) | |
pqdif_probe_text_looks_like_angle ( ch . channel_name ) ;
for ( const auto & sd : ch . series_definitions )
candidate = candidate | | pqdif_probe_series_def_is_dynamic_candidate ( sd ) ;
if ( ! candidate )
continue ;
const bool line_like = pqdif_probe_phase_is_line_voltage ( ch . phase_id , ch . channel_name ) ;
bool inter_like = pqdif_probe_text_looks_like_interharmonic ( ch . channel_name ) ;
bool angle_like = pqdif_probe_text_looks_like_angle ( ch . channel_name ) ;
bool harmonic_like = text_looks_like_harmonic ( ch . channel_name ) ;
for ( const auto & sd : ch . series_definitions )
{
inter_like = inter_like | | pqdif_probe_guid_is_interharmonic_characteristic ( sd . quantity_characteristic_id . value ) | |
pqdif_probe_text_looks_like_interharmonic ( sd . quantity_characteristic_id . symbolic_name ) ;
angle_like = angle_like | | pqdif_probe_guid_is_phase_angle_value_type ( sd . value_type_id . value ) | |
sd . quantity_units_id = = ID_QU_DEGREES | | pqdif_probe_text_looks_like_angle ( sd . value_type_id . symbolic_name ) ;
harmonic_like = harmonic_like | | pqdif_probe_guid_is_harmonic_characteristic ( sd . quantity_characteristic_id . value ) | |
text_looks_like_harmonic ( sd . quantity_characteristic_id . symbolic_name ) ;
}
+ + def_hits ;
if ( line_like ) + + def_line_like ;
if ( inter_like ) + + def_inter_like ;
std : : cout < < " [DYN-DEF " < < def_hits < < " ] "
< < " ds= " < < ds_idx
< < " , ch_def= " < < ch . channel_def_index
< < " , name= " < < ch . channel_name
< < " , phase= " < < pqdif_sem : : FindPhaseName ( ch . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( ch . quantity_measured_id )
< < " , line_like= " < < ( line_like ? " true " : " false " )
< < " , harmonic_like= " < < ( harmonic_like ? " true " : " false " )
< < " , interharmonic_like= " < < ( inter_like ? " true " : " false " )
< < " , angle_like= " < < ( angle_like ? " true " : " false " )
< < " , series_defs= " < < ch . series_definitions . size ( )
< < std : : endl ;
for ( size_t j = 0 ; j < ch . series_definitions . size ( ) ; + + j )
{
const auto & sd = ch . series_definitions [ j ] ;
if ( ! pqdif_probe_series_def_is_dynamic_candidate ( sd ) & & ! pqdif_is_trace_log_enabled ( ) )
continue ;
std : : cout < < " [DYN-SER-DEF " < < j < < " ] "
< < " value_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : ValueType ,
sd . value_type_id . value ,
short_guid_name ( sd . value_type_id ) . c_str ( ) )
< < " , characteristic= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityCharacteristic ,
sd . quantity_characteristic_id . value ,
short_guid_name ( sd . quantity_characteristic_id ) . c_str ( ) )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( sd . quantity_units_id )
< < " , nominal= " < < sd . nominal_quantity
< < " , percentile= " < < sd . prob_percentile
< < std : : endl ;
}
}
}
size_t inst_hits = 0 ;
size_t inst_line_like = 0 ;
size_t inst_inter_like = 0 ;
for ( size_t obs_idx = 0 ; obs_idx < lf . observations . size ( ) ; + + obs_idx )
{
const auto & obs = lf . observations [ obs_idx ] ;
for ( size_t ch_idx = 0 ; ch_idx < obs . channel_instances . size ( ) ; + + ch_idx )
{
const auto & ch = obs . channel_instances [ ch_idx ] ;
const PqdifChannelDefinition * def = find_channel_definition ( lf , obs , ch ) ;
bool candidate = channel_instance_looks_like_harmonic ( ch , def ) | |
pqdif_probe_text_looks_like_interharmonic ( ch . channel_name ) | |
pqdif_probe_text_looks_like_angle ( ch . channel_name ) ;
for ( const auto & si : ch . series_instances )
candidate = candidate | | pqdif_probe_series_ins_is_dynamic_candidate ( si ) ;
if ( ! candidate )
continue ;
const bool line_like = pqdif_probe_phase_is_line_voltage ( ch . phase_id , ch . channel_name ) ;
bool inter_like = pqdif_probe_text_looks_like_interharmonic ( ch . channel_name ) ;
bool angle_like = pqdif_probe_text_looks_like_angle ( ch . channel_name ) ;
bool harmonic_like = text_looks_like_harmonic ( ch . channel_name ) ;
for ( const auto & si : ch . series_instances )
{
inter_like = inter_like | | pqdif_probe_guid_is_interharmonic_characteristic ( si . quantity_characteristic_id . value ) | |
pqdif_probe_text_looks_like_interharmonic ( si . quantity_characteristic_id . symbolic_name ) ;
angle_like = angle_like | | pqdif_probe_guid_is_phase_angle_value_type ( si . value_type_id . value ) | |
si . quantity_units_id = = ID_QU_DEGREES | | pqdif_probe_text_looks_like_angle ( si . value_type_id . symbolic_name ) ;
harmonic_like = harmonic_like | | pqdif_probe_guid_is_harmonic_characteristic ( si . quantity_characteristic_id . value ) | |
text_looks_like_harmonic ( si . quantity_characteristic_id . symbolic_name ) ;
}
int hint_offset = - 1 ;
int hint_size = 0 ;
const int hint_order = stat_make_channel_spectrum_order_hint ( obs , ch , hint_offset , hint_size ) ;
+ + inst_hits ;
if ( line_like ) + + inst_line_like ;
if ( inter_like ) + + inst_inter_like ;
std : : cout < < " [DYN-INS " < < inst_hits < < " ] "
< < " obs= " < < obs_idx
< < " , obs_name= " < < obs . observation_name
< < " , ch= " < < ch . channel_instance_index
< < " , ch_def= " < < ch . channel_def_index
< < " , name= " < < ch . channel_name
< < " , def_name= " < < ( def ! = nullptr ? def - > channel_name : std : : string ( ) )
< < " , phase= " < < pqdif_sem : : FindPhaseName ( ch . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( ch . quantity_measured_id )
< < " , group= " < < ch . channel_group_id
< < " , order_hint= " < < hint_order
< < " , hint_offset= " < < hint_offset
< < " , hint_block_size= " < < hint_size
< < " , line_like= " < < ( line_like ? " true " : " false " )
< < " , harmonic_like= " < < ( harmonic_like ? " true " : " false " )
< < " , interharmonic_like= " < < ( inter_like ? " true " : " false " )
< < " , angle_like= " < < ( angle_like ? " true " : " false " )
< < " , series_instances= " < < ch . series_instances . size ( )
< < std : : endl ;
for ( size_t j = 0 ; j < ch . series_instances . size ( ) ; + + j )
{
const auto & si = ch . series_instances [ j ] ;
if ( ! pqdif_probe_series_ins_is_dynamic_candidate ( si ) & & ! pqdif_is_trace_log_enabled ( ) )
continue ;
std : : cout < < " [DYN-SER-INS " < < j < < " ] "
< < " value_type= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : ValueType ,
si . value_type_id . value ,
short_guid_name ( si . value_type_id ) . c_str ( ) )
< < " , characteristic= " < < reg . FindName (
pqdif_sem : : GuidSemanticField : : QuantityCharacteristic ,
si . quantity_characteristic_id . value ,
short_guid_name ( si . quantity_characteristic_id ) . c_str ( ) )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( si . quantity_units_id )
< < " , base_q= " < < si . series_base_quantity
< < " , nominal= " < < si . nominal_quantity
< < " , scale= " < < si . scale
< < " , offset= " < < si . offset
< < " , share_ch= " < < si . share_channel_index
< < " , share_ser= " < < si . share_series_index
< < " , " < < preview_value_array ( si . values )
< < std : : endl ;
}
}
}
std : : cout < < " [DYNAMIC SPECTRUM PROBE RESULT] definition_candidates= " < < def_hits
< < " , definition_line_like= " < < def_line_like
< < " , definition_interharmonic_like= " < < def_inter_like
< < " , observation_channel_candidates= " < < inst_hits
< < " , observation_line_like= " < < inst_line_like
< < " , observation_interharmonic_like= " < < inst_inter_like
< < std : : endl ;
std : : cout < < " ========================================================== " < < std : : endl ;
}
void dump_monitor_settings_probe ( const ParsedPqdifFile & parsed_file )
{
const auto & lf = parsed_file . logical_file ;
std : : cout < < " ========== PQDIF MONITOR SETTINGS ========== " < < std : : endl ;
std : : cout < < " settings_count= " < < lf . monitor_settings . size ( ) < < std : : endl ;
for ( size_t i = 0 ; i < lf . monitor_settings . size ( ) & & i < 3 ; + + i )
{
const auto & s = lf . monitor_settings [ i ] ;
std : : cout < < " [SET " < < i < < " ] "
< < " effective= " < < s . effective_time . text
< < " , nominal_freq= " < < s . nominal_frequency
< < " , nominal_voltage= " < < s . nominal_voltage
< < " , physical_connection= " < < s . physical_connection
< < " , is_pcc= " < < ( s . is_pcc ? " true " : " false " )
< < " , use_transducer= " < < ( s . use_transducer ? " true " : " false " )
< < std : : endl ;
}
std : : cout < < " =========================================== " < < std : : endl ;
}
std : : string stat_two_digit ( int n ) ;
int stat_dynamic_metric_base ( StatMetricId id )
{
return static_cast < int > ( id ) ;
}
enum class StatDynamicMetricGroup
{
None = 0 ,
VoltageHarmonic ,
LineVoltageHarmonic ,
CurrentHarmonic ,
VoltageHarmonicAngle ,
LineVoltageHarmonicAngle ,
CurrentHarmonicAngle ,
HarmonicActivePower ,
HarmonicReactivePower ,
HarmonicApparentPower ,
VoltageHarmonicRatio ,
LineVoltageHarmonicRatio ,
CurrentHarmonicRatio ,
VoltageInterharmonic ,
LineVoltageInterharmonic ,
CurrentInterharmonic
} ;
struct StatDynamicMetricRange
{
StatDynamicMetricGroup group ;
int base ;
int phase_index ;
const char * phase_label ;
const char * prefix ;
bool interharmonic ;
} ;
const std : : vector < StatDynamicMetricRange > & stat_dynamic_metric_ranges ( )
{
static const std : : vector < StatDynamicMetricRange > ranges = {
{ StatDynamicMetricGroup : : VoltageHarmonic , static_cast < int > ( StatMetricId : : VoltageHarmonicUaBase ) , 0 , " a " , " U " , false } ,
{ StatDynamicMetricGroup : : VoltageHarmonic , static_cast < int > ( StatMetricId : : VoltageHarmonicUbBase ) , 1 , " b " , " U " , false } ,
{ StatDynamicMetricGroup : : VoltageHarmonic , static_cast < int > ( StatMetricId : : VoltageHarmonicUcBase ) , 2 , " c " , " U " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonic , static_cast < int > ( StatMetricId : : LineVoltageHarmonicUabBase ) , 0 , " ab " , " U " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonic , static_cast < int > ( StatMetricId : : LineVoltageHarmonicUbcBase ) , 1 , " bc " , " U " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonic , static_cast < int > ( StatMetricId : : LineVoltageHarmonicUcaBase ) , 2 , " ca " , " U " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonic , static_cast < int > ( StatMetricId : : CurrentHarmonicIaBase ) , 0 , " a " , " I " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonic , static_cast < int > ( StatMetricId : : CurrentHarmonicIbBase ) , 1 , " b " , " I " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonic , static_cast < int > ( StatMetricId : : CurrentHarmonicIcBase ) , 2 , " c " , " I " , false } ,
{ StatDynamicMetricGroup : : VoltageHarmonicAngle , static_cast < int > ( StatMetricId : : VoltageHarmonicAngleUaBase ) , 0 , " a " , " U " , false } ,
{ StatDynamicMetricGroup : : VoltageHarmonicAngle , static_cast < int > ( StatMetricId : : VoltageHarmonicAngleUbBase ) , 1 , " b " , " U " , false } ,
{ StatDynamicMetricGroup : : VoltageHarmonicAngle , static_cast < int > ( StatMetricId : : VoltageHarmonicAngleUcBase ) , 2 , " c " , " U " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonicAngle , static_cast < int > ( StatMetricId : : LineVoltageHarmonicAngleUabBase ) , 0 , " ab " , " U " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonicAngle , static_cast < int > ( StatMetricId : : LineVoltageHarmonicAngleUbcBase ) , 1 , " bc " , " U " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonicAngle , static_cast < int > ( StatMetricId : : LineVoltageHarmonicAngleUcaBase ) , 2 , " ca " , " U " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonicAngle , static_cast < int > ( StatMetricId : : CurrentHarmonicAngleIaBase ) , 0 , " a " , " I " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonicAngle , static_cast < int > ( StatMetricId : : CurrentHarmonicAngleIbBase ) , 1 , " b " , " I " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonicAngle , static_cast < int > ( StatMetricId : : CurrentHarmonicAngleIcBase ) , 2 , " c " , " I " , false } ,
{ StatDynamicMetricGroup : : HarmonicActivePower , static_cast < int > ( StatMetricId : : HarmonicActivePowerPaBase ) , 0 , " a " , " P " , false } ,
{ StatDynamicMetricGroup : : HarmonicActivePower , static_cast < int > ( StatMetricId : : HarmonicActivePowerPbBase ) , 1 , " b " , " P " , false } ,
{ StatDynamicMetricGroup : : HarmonicActivePower , static_cast < int > ( StatMetricId : : HarmonicActivePowerPcBase ) , 2 , " c " , " P " , false } ,
{ StatDynamicMetricGroup : : HarmonicActivePower , static_cast < int > ( StatMetricId : : HarmonicActivePowerTotalBase ) , 3 , " total " , " P " , false } ,
{ StatDynamicMetricGroup : : HarmonicReactivePower , static_cast < int > ( StatMetricId : : HarmonicReactivePowerQaBase ) , 0 , " a " , " Q " , false } ,
{ StatDynamicMetricGroup : : HarmonicReactivePower , static_cast < int > ( StatMetricId : : HarmonicReactivePowerQbBase ) , 1 , " b " , " Q " , false } ,
{ StatDynamicMetricGroup : : HarmonicReactivePower , static_cast < int > ( StatMetricId : : HarmonicReactivePowerQcBase ) , 2 , " c " , " Q " , false } ,
{ StatDynamicMetricGroup : : HarmonicReactivePower , static_cast < int > ( StatMetricId : : HarmonicReactivePowerTotalBase ) , 3 , " total " , " Q " , false } ,
{ StatDynamicMetricGroup : : HarmonicApparentPower , static_cast < int > ( StatMetricId : : HarmonicApparentPowerSaBase ) , 0 , " a " , " S " , false } ,
{ StatDynamicMetricGroup : : HarmonicApparentPower , static_cast < int > ( StatMetricId : : HarmonicApparentPowerSbBase ) , 1 , " b " , " S " , false } ,
{ StatDynamicMetricGroup : : HarmonicApparentPower , static_cast < int > ( StatMetricId : : HarmonicApparentPowerScBase ) , 2 , " c " , " S " , false } ,
{ StatDynamicMetricGroup : : HarmonicApparentPower , static_cast < int > ( StatMetricId : : HarmonicApparentPowerTotalBase ) , 3 , " total " , " S " , false } ,
{ StatDynamicMetricGroup : : VoltageHarmonicRatio , static_cast < int > ( StatMetricId : : VoltageHarmonicRatioUaBase ) , 0 , " a " , " U " , false } ,
{ StatDynamicMetricGroup : : VoltageHarmonicRatio , static_cast < int > ( StatMetricId : : VoltageHarmonicRatioUbBase ) , 1 , " b " , " U " , false } ,
{ StatDynamicMetricGroup : : VoltageHarmonicRatio , static_cast < int > ( StatMetricId : : VoltageHarmonicRatioUcBase ) , 2 , " c " , " U " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonicRatio , static_cast < int > ( StatMetricId : : CurrentHarmonicRatioIaBase ) , 0 , " a " , " I " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonicRatio , static_cast < int > ( StatMetricId : : CurrentHarmonicRatioIbBase ) , 1 , " b " , " I " , false } ,
{ StatDynamicMetricGroup : : CurrentHarmonicRatio , static_cast < int > ( StatMetricId : : CurrentHarmonicRatioIcBase ) , 2 , " c " , " I " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonicRatio , static_cast < int > ( StatMetricId : : LineVoltageHarmonicRatioUabBase ) , 0 , " ab " , " U " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonicRatio , static_cast < int > ( StatMetricId : : LineVoltageHarmonicRatioUbcBase ) , 1 , " bc " , " U " , false } ,
{ StatDynamicMetricGroup : : LineVoltageHarmonicRatio , static_cast < int > ( StatMetricId : : LineVoltageHarmonicRatioUcaBase ) , 2 , " ca " , " U " , false } ,
{ StatDynamicMetricGroup : : VoltageInterharmonic , static_cast < int > ( StatMetricId : : VoltageInterharmonicUaBase ) , 0 , " a " , " U " , true } ,
{ StatDynamicMetricGroup : : VoltageInterharmonic , static_cast < int > ( StatMetricId : : VoltageInterharmonicUbBase ) , 1 , " b " , " U " , true } ,
{ StatDynamicMetricGroup : : VoltageInterharmonic , static_cast < int > ( StatMetricId : : VoltageInterharmonicUcBase ) , 2 , " c " , " U " , true } ,
{ StatDynamicMetricGroup : : LineVoltageInterharmonic , static_cast < int > ( StatMetricId : : LineVoltageInterharmonicUabBase ) , 0 , " ab " , " U " , true } ,
{ StatDynamicMetricGroup : : LineVoltageInterharmonic , static_cast < int > ( StatMetricId : : LineVoltageInterharmonicUbcBase ) , 1 , " bc " , " U " , true } ,
{ StatDynamicMetricGroup : : LineVoltageInterharmonic , static_cast < int > ( StatMetricId : : LineVoltageInterharmonicUcaBase ) , 2 , " ca " , " U " , true } ,
{ StatDynamicMetricGroup : : CurrentInterharmonic , static_cast < int > ( StatMetricId : : CurrentInterharmonicIaBase ) , 0 , " a " , " I " , true } ,
{ StatDynamicMetricGroup : : CurrentInterharmonic , static_cast < int > ( StatMetricId : : CurrentInterharmonicIbBase ) , 1 , " b " , " I " , true } ,
{ StatDynamicMetricGroup : : CurrentInterharmonic , static_cast < int > ( StatMetricId : : CurrentInterharmonicIcBase ) , 2 , " c " , " I " , true }
} ;
return ranges ;
}
int stat_dynamic_metric_min_offset ( bool interharmonic )
{
return interharmonic ? 0 : 2 ;
}
int stat_dynamic_metric_max_offset ( bool interharmonic )
{
return interharmonic ? 49 : 50 ;
}
const StatDynamicMetricRange * stat_find_dynamic_metric_range ( StatMetricId id )
{
const int v = static_cast < int > ( id ) ;
for ( const auto & r : stat_dynamic_metric_ranges ( ) )
{
const int min_offset = stat_dynamic_metric_min_offset ( r . interharmonic ) ;
const int max_offset = stat_dynamic_metric_max_offset ( r . interharmonic ) ;
if ( v > = r . base + min_offset & & v < = r . base + max_offset )
return & r ;
}
return nullptr ;
}
bool stat_is_dynamic_metric ( StatMetricId id )
{
return stat_find_dynamic_metric_range ( id ) ! = nullptr ;
}
bool stat_is_dynamic_metric_group ( StatMetricId id , StatDynamicMetricGroup group )
{
const StatDynamicMetricRange * r = stat_find_dynamic_metric_range ( id ) ;
return r ! = nullptr & & r - > group = = group ;
}
int stat_dynamic_metric_order_or_slot ( StatMetricId id )
{
const StatDynamicMetricRange * r = stat_find_dynamic_metric_range ( id ) ;
if ( r = = nullptr )
return - 1 ;
return static_cast < int > ( id ) - r - > base ;
}
double stat_dynamic_metric_order_value ( StatMetricId id )
{
const StatDynamicMetricRange * r = stat_find_dynamic_metric_range ( id ) ;
if ( r = = nullptr )
return - 1.0 ;
const int offset = stat_dynamic_metric_order_or_slot ( id ) ;
return r - > interharmonic ? ( static_cast < double > ( offset ) + 0.5 ) : static_cast < double > ( offset ) ;
}
StatMetricId stat_dynamic_metric_id ( StatDynamicMetricGroup group , int phase_index , int offset )
{
for ( const auto & r : stat_dynamic_metric_ranges ( ) )
{
if ( r . group ! = group | | r . phase_index ! = phase_index )
continue ;
const int min_offset = stat_dynamic_metric_min_offset ( r . interharmonic ) ;
const int max_offset = stat_dynamic_metric_max_offset ( r . interharmonic ) ;
if ( offset < min_offset | | offset > max_offset )
return StatMetricId : : Unknown ;
return static_cast < StatMetricId > ( r . base + offset ) ;
}
return StatMetricId : : Unknown ;
}
std : : string stat_format_interharmonic_slot ( int slot )
{
char buf [ 16 ] = { 0 } ;
std : : snprintf ( buf , sizeof ( buf ) , " %02dp5 " , slot ) ;
return std : : string ( buf ) ;
}
std : : string stat_dynamic_metric_name ( StatMetricId id )
{
const StatDynamicMetricRange * r = stat_find_dynamic_metric_range ( id ) ;
if ( r = = nullptr )
return " Unknown " ;
const int offset = stat_dynamic_metric_order_or_slot ( id ) ;
std : : string phase ( r - > phase_label ) ;
if ( ! phase . empty ( ) )
phase [ 0 ] = static_cast < char > ( std : : toupper ( static_cast < unsigned char > ( phase [ 0 ] ) ) ) ;
std : : ostringstream os ;
os < < r - > prefix < < phase ;
switch ( r - > group )
{
case StatDynamicMetricGroup : : VoltageHarmonic :
case StatDynamicMetricGroup : : LineVoltageHarmonic :
case StatDynamicMetricGroup : : CurrentHarmonic :
os < < " Harm " < < stat_two_digit ( offset ) ;
break ;
case StatDynamicMetricGroup : : VoltageHarmonicAngle :
case StatDynamicMetricGroup : : LineVoltageHarmonicAngle :
case StatDynamicMetricGroup : : CurrentHarmonicAngle :
os < < " HarmAng " < < stat_two_digit ( offset ) ;
break ;
case StatDynamicMetricGroup : : HarmonicActivePower :
case StatDynamicMetricGroup : : HarmonicReactivePower :
case StatDynamicMetricGroup : : HarmonicApparentPower :
os < < " Harm " < < stat_two_digit ( offset ) ;
break ;
case StatDynamicMetricGroup : : VoltageHarmonicRatio :
case StatDynamicMetricGroup : : LineVoltageHarmonicRatio :
case StatDynamicMetricGroup : : CurrentHarmonicRatio :
os < < " HarmRatio " < < stat_two_digit ( offset ) ;
break ;
case StatDynamicMetricGroup : : VoltageInterharmonic :
case StatDynamicMetricGroup : : LineVoltageInterharmonic :
case StatDynamicMetricGroup : : CurrentInterharmonic :
os < < " InterHarm " < < stat_format_interharmonic_slot ( offset ) ;
break ;
default :
os < < " Dynamic " < < offset ;
break ;
}
return os . str ( ) ;
}
std : : vector < StatMetricId > stat_dynamic_metric_order_for_group ( StatDynamicMetricGroup group )
{
std : : vector < StatMetricId > out ;
for ( const auto & r : stat_dynamic_metric_ranges ( ) )
{
if ( r . group ! = group )
continue ;
const int min_offset = stat_dynamic_metric_min_offset ( r . interharmonic ) ;
const int max_offset = stat_dynamic_metric_max_offset ( r . interharmonic ) ;
for ( int offset = min_offset ; offset < = max_offset ; + + offset )
out . push_back ( static_cast < StatMetricId > ( r . base + offset ) ) ;
}
return out ;
}
std : : vector < StatMetricId > stat_all_dynamic_metric_order ( )
{
std : : vector < StatMetricId > out ;
const std : : vector < StatDynamicMetricGroup > groups = {
StatDynamicMetricGroup : : VoltageHarmonic ,
StatDynamicMetricGroup : : LineVoltageHarmonic ,
StatDynamicMetricGroup : : CurrentHarmonic ,
StatDynamicMetricGroup : : VoltageHarmonicAngle ,
StatDynamicMetricGroup : : LineVoltageHarmonicAngle ,
StatDynamicMetricGroup : : CurrentHarmonicAngle ,
StatDynamicMetricGroup : : HarmonicActivePower ,
StatDynamicMetricGroup : : HarmonicReactivePower ,
StatDynamicMetricGroup : : HarmonicApparentPower ,
StatDynamicMetricGroup : : VoltageHarmonicRatio ,
StatDynamicMetricGroup : : LineVoltageHarmonicRatio ,
StatDynamicMetricGroup : : CurrentHarmonicRatio ,
StatDynamicMetricGroup : : VoltageInterharmonic ,
StatDynamicMetricGroup : : LineVoltageInterharmonic ,
StatDynamicMetricGroup : : CurrentInterharmonic
} ;
for ( auto g : groups )
{
auto part = stat_dynamic_metric_order_for_group ( g ) ;
out . insert ( out . end ( ) , part . begin ( ) , part . end ( ) ) ;
}
return out ;
}
/// @brief 兼容旧调用:判断三相电压 2-50 次谐波 RMS。
bool stat_is_voltage_harmonic_metric ( StatMetricId id )
{
return stat_is_dynamic_metric_group ( id , StatDynamicMetricGroup : : VoltageHarmonic ) ;
}
int stat_voltage_harmonic_order ( StatMetricId id )
{
if ( ! stat_is_voltage_harmonic_metric ( id ) )
return - 1 ;
return stat_dynamic_metric_order_or_slot ( id ) ;
}
int stat_voltage_harmonic_phase_index ( StatMetricId id )
{
const StatDynamicMetricRange * r = stat_find_dynamic_metric_range ( id ) ;
if ( r = = nullptr | | r - > group ! = StatDynamicMetricGroup : : VoltageHarmonic )
return - 1 ;
return r - > phase_index ;
}
StatMetricId stat_voltage_harmonic_metric_id ( int phase_index , int order )
{
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : VoltageHarmonic , phase_index , order ) ;
}
std : : string stat_voltage_harmonic_metric_name ( StatMetricId id )
{
return stat_dynamic_metric_name ( id ) ;
}
/// @brief 将业务指标 ID 转为可读字符串。
/// @param id 业务指标枚举。
/// @return 指标名称字符串,便于调试日志输出。
std : : string stat_metric_name ( StatMetricId id )
{
if ( stat_is_dynamic_metric ( id ) )
return stat_dynamic_metric_name ( id ) ;
switch ( id )
{
case StatMetricId : : UaRms : return " UaRms " ;
case StatMetricId : : UbRms : return " UbRms " ;
case StatMetricId : : UcRms : return " UcRms " ;
case StatMetricId : : IaRms : return " IaRms " ;
case StatMetricId : : IbRms : return " IbRms " ;
case StatMetricId : : IcRms : return " IcRms " ;
case StatMetricId : : UabRms : return " UabRms " ;
case StatMetricId : : UbcRms : return " UbcRms " ;
case StatMetricId : : UcaRms : return " UcaRms " ;
case StatMetricId : : UaDeviation : return " UaDeviation " ;
case StatMetricId : : UbDeviation : return " UbDeviation " ;
case StatMetricId : : UcDeviation : return " UcDeviation " ;
case StatMetricId : : UabDeviation : return " UabDeviation " ;
case StatMetricId : : UbcDeviation : return " UbcDeviation " ;
case StatMetricId : : UcaDeviation : return " UcaDeviation " ;
case StatMetricId : : Frequency : return " Frequency " ;
case StatMetricId : : FrequencyDeviation : return " FrequencyDeviation " ;
case StatMetricId : : UZeroSeq : return " UZeroSeq " ;
case StatMetricId : : UNegSeq : return " UNegSeq " ;
case StatMetricId : : UPosSeq : return " UPosSeq " ;
case StatMetricId : : UNegSeqUnbalance : return " UNegSeqUnbalance " ;
case StatMetricId : : IZeroSeq : return " IZeroSeq " ;
case StatMetricId : : INegSeq : return " INegSeq " ;
case StatMetricId : : IPosSeq : return " IPosSeq " ;
case StatMetricId : : INegSeqUnbalance : return " INegSeqUnbalance " ;
case StatMetricId : : PaPower : return " PaPower " ;
case StatMetricId : : PbPower : return " PbPower " ;
case StatMetricId : : PcPower : return " PcPower " ;
case StatMetricId : : PTotalPower : return " PTotalPower " ;
case StatMetricId : : QaPower : return " QaPower " ;
case StatMetricId : : QbPower : return " QbPower " ;
case StatMetricId : : QcPower : return " QcPower " ;
case StatMetricId : : QTotalPower : return " QTotalPower " ;
case StatMetricId : : SaPower : return " SaPower " ;
case StatMetricId : : SbPower : return " SbPower " ;
case StatMetricId : : ScPower : return " ScPower " ;
case StatMetricId : : STotalPower : return " STotalPower " ;
case StatMetricId : : PFa : return " PFa " ;
case StatMetricId : : PFb : return " PFb " ;
case StatMetricId : : PFc : return " PFc " ;
case StatMetricId : : PFTotal : return " PFTotal " ;
case StatMetricId : : FundPFa : return " FundPFa " ;
case StatMetricId : : FundPFb : return " FundPFb " ;
case StatMetricId : : FundPFc : return " FundPFc " ;
case StatMetricId : : FundPFTotal : return " FundPFTotal " ;
case StatMetricId : : UaDvc : return " UaDvc " ;
case StatMetricId : : UbDvc : return " UbDvc " ;
case StatMetricId : : UcDvc : return " UcDvc " ;
case StatMetricId : : UabDvc : return " UabDvc " ;
case StatMetricId : : UbcDvc : return " UbcDvc " ;
case StatMetricId : : UcaDvc : return " UcaDvc " ;
case StatMetricId : : UaPst : return " UaPst " ;
case StatMetricId : : UbPst : return " UbPst " ;
case StatMetricId : : UcPst : return " UcPst " ;
case StatMetricId : : UabPst : return " UabPst " ;
case StatMetricId : : UbcPst : return " UbcPst " ;
case StatMetricId : : UcaPst : return " UcaPst " ;
case StatMetricId : : UaPlt : return " UaPlt " ;
case StatMetricId : : UbPlt : return " UbPlt " ;
case StatMetricId : : UcPlt : return " UcPlt " ;
case StatMetricId : : UabPlt : return " UabPlt " ;
case StatMetricId : : UbcPlt : return " UbcPlt " ;
case StatMetricId : : UcaPlt : return " UcaPlt " ;
case StatMetricId : : PaFundPower : return " PaFundPower " ;
case StatMetricId : : PbFundPower : return " PbFundPower " ;
case StatMetricId : : PcFundPower : return " PcFundPower " ;
case StatMetricId : : PTotalFundPower : return " PTotalFundPower " ;
case StatMetricId : : QaFundPower : return " QaFundPower " ;
case StatMetricId : : QbFundPower : return " QbFundPower " ;
case StatMetricId : : QcFundPower : return " QcFundPower " ;
case StatMetricId : : QTotalFundPower : return " QTotalFundPower " ;
case StatMetricId : : SaFundPower : return " SaFundPower " ;
case StatMetricId : : SbFundPower : return " SbFundPower " ;
case StatMetricId : : ScFundPower : return " ScFundPower " ;
case StatMetricId : : STotalFundPower : return " STotalFundPower " ;
case StatMetricId : : UaFundRms : return " UaFundRms " ;
case StatMetricId : : UbFundRms : return " UbFundRms " ;
case StatMetricId : : UcFundRms : return " UcFundRms " ;
case StatMetricId : : UaFundAngle : return " UaFundAngle " ;
case StatMetricId : : UbFundAngle : return " UbFundAngle " ;
case StatMetricId : : UcFundAngle : return " UcFundAngle " ;
case StatMetricId : : IaFundRms : return " IaFundRms " ;
case StatMetricId : : IbFundRms : return " IbFundRms " ;
case StatMetricId : : IcFundRms : return " IcFundRms " ;
case StatMetricId : : IaFundAngle : return " IaFundAngle " ;
case StatMetricId : : IbFundAngle : return " IbFundAngle " ;
case StatMetricId : : IcFundAngle : return " IcFundAngle " ;
case StatMetricId : : UabFundRms : return " UabFundRms " ;
case StatMetricId : : UbcFundRms : return " UbcFundRms " ;
case StatMetricId : : UcaFundRms : return " UcaFundRms " ;
case StatMetricId : : UabFundAngle : return " UabFundAngle " ;
case StatMetricId : : UbcFundAngle : return " UbcFundAngle " ;
case StatMetricId : : UcaFundAngle : return " UcaFundAngle " ;
case StatMetricId : : UaThd : return " UaThd " ;
case StatMetricId : : UbThd : return " UbThd " ;
case StatMetricId : : UcThd : return " UcThd " ;
case StatMetricId : : IaThd : return " IaThd " ;
case StatMetricId : : IbThd : return " IbThd " ;
case StatMetricId : : IcThd : return " IcThd " ;
case StatMetricId : : UabThd : return " UabThd " ;
case StatMetricId : : UbcThd : return " UbcThd " ;
case StatMetricId : : UcaThd : return " UcaThd " ;
default : return " Unknown " ;
}
}
/// @brief 将统计值类型转为可读字符串。
/// @param kind 统计值类型。
/// @return 统计值类型名称。
std : : string stat_value_kind_name ( StatValueKind kind )
{
switch ( kind )
{
case StatValueKind : : Min : return " Min " ;
case StatValueKind : : Max : return " Max " ;
case StatValueKind : : Avg : return " Avg " ;
case StatValueKind : : P95 : return " P95 " ;
default : return " Unknown " ;
}
}
/// @brief 将接线方式枚举转为可读字符串。
/// @param kind 接线方式枚举。
/// @return 可读字符串。
std : : string stat_connection_kind_name ( ParsedConnectionKind kind )
{
switch ( kind )
{
case ParsedConnectionKind : : Wye : return " Wye " ;
case ParsedConnectionKind : : Delta : return " Delta " ;
default : return " Unknown " ;
}
}
/// @brief 将指标质量状态转为可读字符串。
/// @param q 指标质量状态。
/// @return 可读质量状态名称。
std : : string stat_metric_quality_name ( StatMetricQuality q )
{
switch ( q )
{
case StatMetricQuality : : Normal : return " OK " ;
case StatMetricQuality : : AllZero : return " ALL_ZERO " ;
case StatMetricQuality : : DuplicateSource : return " DUPLICATE_SOURCE " ;
case StatMetricQuality : : SuspiciousRange : return " SUSPICIOUS_RANGE " ;
case StatMetricQuality : : Missing : return " MISSING " ;
default : return " UNKNOWN " ;
}
}
/// @brief 非谐波核心指标打印顺序。
/// @details 平时核心日志只打印这些指标,避免 2-50 次谐波全部展开导致日志过大。
const std : : vector < StatMetricId > & stat_core_metric_print_order ( )
{
static const std : : vector < StatMetricId > order = {
StatMetricId : : UaRms ,
StatMetricId : : UbRms ,
StatMetricId : : UcRms ,
StatMetricId : : IaRms ,
StatMetricId : : IbRms ,
StatMetricId : : IcRms ,
StatMetricId : : UabRms ,
StatMetricId : : UbcRms ,
StatMetricId : : UcaRms ,
StatMetricId : : UaDeviation ,
StatMetricId : : UbDeviation ,
StatMetricId : : UcDeviation ,
StatMetricId : : Frequency ,
StatMetricId : : FrequencyDeviation ,
StatMetricId : : UZeroSeq ,
StatMetricId : : UNegSeq ,
StatMetricId : : UPosSeq ,
StatMetricId : : UNegSeqUnbalance ,
StatMetricId : : IZeroSeq ,
StatMetricId : : INegSeq ,
StatMetricId : : IPosSeq ,
StatMetricId : : INegSeqUnbalance ,
StatMetricId : : PaPower ,
StatMetricId : : PbPower ,
StatMetricId : : PcPower ,
StatMetricId : : PTotalPower ,
StatMetricId : : QaPower ,
StatMetricId : : QbPower ,
StatMetricId : : QcPower ,
StatMetricId : : QTotalPower ,
StatMetricId : : SaPower ,
StatMetricId : : SbPower ,
StatMetricId : : ScPower ,
StatMetricId : : STotalPower ,
StatMetricId : : PFa ,
StatMetricId : : PFb ,
StatMetricId : : PFc ,
StatMetricId : : PFTotal ,
StatMetricId : : FundPFa ,
StatMetricId : : FundPFb ,
StatMetricId : : FundPFc ,
StatMetricId : : FundPFTotal ,
StatMetricId : : UaDvc ,
StatMetricId : : UbDvc ,
StatMetricId : : UcDvc ,
StatMetricId : : UabDvc ,
StatMetricId : : UbcDvc ,
StatMetricId : : UcaDvc ,
StatMetricId : : UaPst ,
StatMetricId : : UbPst ,
StatMetricId : : UcPst ,
StatMetricId : : UabPst ,
StatMetricId : : UbcPst ,
StatMetricId : : UcaPst ,
StatMetricId : : UaPlt ,
StatMetricId : : UbPlt ,
StatMetricId : : UcPlt ,
StatMetricId : : UabPlt ,
StatMetricId : : UbcPlt ,
StatMetricId : : UcaPlt ,
StatMetricId : : PaFundPower ,
StatMetricId : : PbFundPower ,
StatMetricId : : PcFundPower ,
StatMetricId : : PTotalFundPower ,
StatMetricId : : QaFundPower ,
StatMetricId : : QbFundPower ,
StatMetricId : : QcFundPower ,
StatMetricId : : QTotalFundPower ,
StatMetricId : : SaFundPower ,
StatMetricId : : SbFundPower ,
StatMetricId : : ScFundPower ,
StatMetricId : : STotalFundPower ,
StatMetricId : : UaFundRms ,
StatMetricId : : UbFundRms ,
StatMetricId : : UcFundRms ,
StatMetricId : : UaFundAngle ,
StatMetricId : : UbFundAngle ,
StatMetricId : : UcFundAngle ,
StatMetricId : : IaFundRms ,
StatMetricId : : IbFundRms ,
StatMetricId : : IcFundRms ,
StatMetricId : : IaFundAngle ,
StatMetricId : : IbFundAngle ,
StatMetricId : : IcFundAngle ,
StatMetricId : : UabFundRms ,
StatMetricId : : UbcFundRms ,
StatMetricId : : UcaFundRms ,
StatMetricId : : UabFundAngle ,
StatMetricId : : UbcFundAngle ,
StatMetricId : : UcaFundAngle ,
StatMetricId : : UaThd ,
StatMetricId : : UbThd ,
StatMetricId : : UcThd ,
StatMetricId : : IaThd ,
StatMetricId : : IbThd ,
StatMetricId : : IcThd ,
StatMetricId : : UabThd ,
StatMetricId : : UbcThd ,
StatMetricId : : UcaThd
} ;
return order ;
}
/// @brief 动态生成三相电压 2-50 次谐波指标顺序。
/// @details 兼容旧调用;新的谐波/间谐波族统一走 stat_dynamic_metric_order_for_group / stat_all_dynamic_metric_order。
std : : vector < StatMetricId > stat_voltage_harmonic_metric_order ( )
{
return stat_dynamic_metric_order_for_group ( StatDynamicMetricGroup : : VoltageHarmonic ) ;
}
/// @brief 当前全部主要指标打印顺序。
/// @details 核心 22 项 + 所有动态谐波/间谐波指标。动态指标不逐项写 enum, 统一按族和区间生成。
const std : : vector < StatMetricId > & stat_primary_metric_print_order ( )
{
static const std : : vector < StatMetricId > order = [ ] ( ) {
std : : vector < StatMetricId > out = stat_core_metric_print_order ( ) ;
const std : : vector < StatMetricId > dynamic_metrics = stat_all_dynamic_metric_order ( ) ;
out . insert ( out . end ( ) , dynamic_metrics . begin ( ) , dynamic_metrics . end ( ) ) ;
return out ;
} ( ) ;
return order ;
}
/// @brief 已声明但当前 14 项之外的扩展指标打印顺序。
/// @details 这些指标不会计入“14 项”,但如果后续规则命中,也会在日志末尾显示,便于扩展核查。
const std : : vector < StatMetricId > & stat_extra_metric_print_order ( )
{
static const std : : vector < StatMetricId > order = {
StatMetricId : : UabDeviation ,
StatMetricId : : UbcDeviation ,
StatMetricId : : UcaDeviation
} ;
return order ;
}
bool stat_is_line_voltage_metric ( StatMetricId id )
{
return id = = StatMetricId : : UabRms | |
id = = StatMetricId : : UbcRms | |
id = = StatMetricId : : UcaRms ;
}
bool stat_is_phase_voltage_metric ( StatMetricId id )
{
return id = = StatMetricId : : UaRms | |
id = = StatMetricId : : UbRms | |
id = = StatMetricId : : UcRms ;
}
bool stat_is_current_metric ( StatMetricId id )
{
return id = = StatMetricId : : IaRms | |
id = = StatMetricId : : IbRms | |
id = = StatMetricId : : IcRms ;
}
bool stat_is_frequency_metric ( StatMetricId id )
{
return id = = StatMetricId : : Frequency | | id = = StatMetricId : : FrequencyDeviation ;
}
bool stat_is_voltage_sequence_component_metric ( StatMetricId id )
{
return id = = StatMetricId : : UZeroSeq | |
id = = StatMetricId : : UNegSeq | |
id = = StatMetricId : : UPosSeq ;
}
bool stat_is_voltage_sequence_unbalance_metric ( StatMetricId id )
{
return id = = StatMetricId : : UNegSeqUnbalance ;
}
bool stat_is_current_sequence_component_metric ( StatMetricId id )
{
return id = = StatMetricId : : IZeroSeq | |
id = = StatMetricId : : INegSeq | |
id = = StatMetricId : : IPosSeq ;
}
bool stat_is_current_sequence_unbalance_metric ( StatMetricId id )
{
return id = = StatMetricId : : INegSeqUnbalance ;
}
bool stat_allow_all_zero_without_score_penalty ( StatMetricId id )
{
return stat_is_current_metric ( id ) | |
stat_is_dynamic_metric ( id ) | |
id = = StatMetricId : : UZeroSeq | |
id = = StatMetricId : : UNegSeq | |
id = = StatMetricId : : UNegSeqUnbalance | |
stat_is_current_sequence_component_metric ( id ) | |
stat_is_current_sequence_unbalance_metric ( id ) ;
}
bool stat_has_value_kind ( const AggregatedStatValues & agg , StatValueKind kind )
{
switch ( kind )
{
case StatValueKind : : Min : return agg . has_min ;
case StatValueKind : : Max : return agg . has_max ;
case StatValueKind : : Avg : return agg . has_avg ;
case StatValueKind : : P95 : return agg . has_p95 ;
default : return false ;
}
}
double stat_get_value_by_kind ( const AggregatedStatValues & agg , StatValueKind kind )
{
switch ( kind )
{
case StatValueKind : : Min : return agg . min_value ;
case StatValueKind : : Max : return agg . max_value ;
case StatValueKind : : Avg : return agg . avg_value ;
case StatValueKind : : P95 : return agg . p95_value ;
default : return 0.0 ;
}
}
/// @brief 根据 observation 找到其关联的数据源定义。
/// @param lf 已解析完成的完整 PQDIF 逻辑对象。
/// @param obs 当前 observation。
/// @return 关联到的数据源定义指针;找不到时返回 nullptr。
const PqdifDataSourceRecord * stat_find_related_data_source (
const PqdifLogicalFile & lf ,
const PqdifObservationRecord & obs )
{
if ( obs . related_data_source_index > = 0 & &
static_cast < size_t > ( obs . related_data_source_index ) < lf . data_sources . size ( ) )
{
return & lf . data_sources [ static_cast < size_t > ( obs . related_data_source_index ) ] ;
}
if ( ! lf . data_sources . empty ( ) )
return & lf . data_sources . front ( ) ;
return nullptr ;
}
/// @brief 根据通道定义索引查找通道定义。
/// @param ds 数据源定义指针。
/// @param channel_def_index 通道定义索引。
/// @return 通道定义指针;找不到时返回 nullptr。
const PqdifChannelDefinition * stat_find_channel_definition (
const PqdifDataSourceRecord * ds ,
int channel_def_index )
{
if ( ds = = nullptr | | channel_def_index < 0 )
return nullptr ;
const size_t idx = static_cast < size_t > ( channel_def_index ) ;
if ( idx > = ds - > channel_definitions . size ( ) )
return nullptr ;
return & ds - > channel_definitions [ idx ] ;
}
/// @brief 根据序列定义索引查找序列定义。
/// @param ch_def 通道定义指针。
/// @param series_def_index 序列定义索引。
/// @return 序列定义指针;找不到时返回 nullptr。
const PqdifSeriesDefinition * stat_find_series_definition (
const PqdifChannelDefinition * ch_def ,
int series_def_index )
{
if ( ch_def = = nullptr | | series_def_index < 0 )
return nullptr ;
const size_t idx = static_cast < size_t > ( series_def_index ) ;
if ( idx > = ch_def - > series_definitions . size ( ) )
return nullptr ;
return & ch_def - > series_definitions [ idx ] ;
}
/// @brief 根据 monitor settings 与相别分布识别接线方式。
/// @details
/// 优先使用正式配置字段 physical_connection; 若不可用, 则回退到相别启发式判断。
/// @param lf 已解析完成的完整 PQDIF 逻辑对象。
/// @return 识别出的接线方式。
ParsedConnectionKind stat_classify_connection_kind ( const PqdifLogicalFile & lf )
{
if ( ! lf . monitor_settings . empty ( ) )
{
const unsigned int pc = lf . monitor_settings . front ( ) . physical_connection ;
switch ( pc )
{
case ID_2_5ELEMENT_WYE :
case ID_3ELMENT_WYE :
return ParsedConnectionKind : : Wye ;
case ID_2ELEMENT_DELTA :
case ID_3ELEMENT_DELTA :
return ParsedConnectionKind : : Delta ;
default :
break ;
}
}
bool has_phase_to_neutral = false ;
bool has_line_to_line = false ;
for ( const auto & ds : lf . data_sources )
{
for ( const auto & ch : ds . channel_definitions )
{
if ( ch . phase_id = = ID_PHASE_AN | | ch . phase_id = = ID_PHASE_BN | | ch . phase_id = = ID_PHASE_CN )
has_phase_to_neutral = true ;
if ( ch . phase_id = = ID_PHASE_AB | | ch . phase_id = = ID_PHASE_BC | | ch . phase_id = = ID_PHASE_CA )
has_line_to_line = true ;
}
}
if ( has_phase_to_neutral )
return ParsedConnectionKind : : Wye ;
if ( has_line_to_line )
return ParsedConnectionKind : : Delta ;
return ParsedConnectionKind : : Unknown ;
}
/// @brief 判断 observation 是否为当前阶段需要处理的“统计类 observation”。
/// @details
/// 当前阶段不处理全部 observation, 而是先筛出统计类 observation。判断顺序:
/// 1) 触发方式为 ID_TRIGGER_METH_PERIODIC_STATS;
/// 2) 名称中包含 TREND / STAT / STATISTIC;
/// 3) 只要 observation 下有 VALUELOG 型通道,也认为它是统计 observation。
/// @param obs 当前 observation。
/// @param lf 完整 PQDIF 逻辑对象。
/// @return 若是统计类 observation, 则返回 true。
bool stat_is_statistical_observation (
const PqdifLogicalFile & lf ,
const PqdifObservationRecord & obs )
{
if ( obs . trigger_method_id = = ID_TRIGGER_METH_PERIODIC_STATS )
return true ;
auto compact = [ & ] ( const std : : string & s ) {
std : : string out ;
for ( unsigned char ch : s )
{
if ( std : : isalnum ( ch ) )
out . push_back ( static_cast < char > ( std : : toupper ( ch ) ) ) ;
}
return out ;
} ;
const std : : string obs_name = compact ( obs . observation_name ) ;
if ( obs_name . find ( " TREND " ) ! = std : : string : : npos | |
obs_name . find ( " STAT " ) ! = std : : string : : npos | |
obs_name . find ( " STATISTIC " ) ! = std : : string : : npos )
{
return true ;
}
for ( const auto & ch : obs . channel_instances )
{
if ( pqdif_sem : : IsQuantityTypeValueLog ( ch . quantity_type_id . value ) )
return true ;
}
( void ) lf ;
return false ;
}
/// @brief 选择当前阶段要处理的“主统计 observation”。
/// @details
/// 当前阶段为了避免不同 observation 在同一 timestamp 上相互覆盖,
/// 只选择第一条命中的统计类 observation 参与识别与聚合。
/// @param lf 完整 PQDIF 逻辑对象。
/// @return 命中的 observation 指针;若没有则返回 nullptr。
const PqdifObservationRecord * stat_select_primary_statistical_observation ( const PqdifLogicalFile & lf )
{
for ( const auto & obs : lf . observations )
{
if ( stat_is_statistical_observation ( lf , obs ) )
return & obs ;
}
return nullptr ;
}
/// @brief 解析共享序列。
/// @details
/// 某些序列自身没有 values, 而是通过 share_channel_index/share_series_index
/// 共享另一个序列的数组值,例如 B/C 相共享 A 相时间轴。
/// @param obs 当前 observation。
/// @param si 当前序列实例。
/// @return 最终应使用的“真实值序列实例”指针。
const PqdifSeriesInstance * stat_resolve_shared_series (
const PqdifObservationRecord & obs ,
const PqdifSeriesInstance & si )
{
const PqdifSeriesInstance * current = & si ;
for ( int depth = 0 ; depth < 8 ; + + depth )
{
if ( current - > values . count > 0 )
return current ;
if ( current - > share_channel_index < 0 | | current - > share_series_index < 0 )
return current ;
const size_t ch_idx = static_cast < size_t > ( current - > share_channel_index ) ;
const size_t ser_idx = static_cast < size_t > ( current - > share_series_index ) ;
if ( ch_idx > = obs . channel_instances . size ( ) )
return current ;
const auto & shared_ch = obs . channel_instances [ ch_idx ] ;
if ( ser_idx > = shared_ch . series_instances . size ( ) )
return current ;
const PqdifSeriesInstance * next = & shared_ch . series_instances [ ser_idx ] ;
if ( next = = current )
return current ;
current = next ;
}
return current ;
}
/// @brief 从数组容器中读取指定下标的原始数值。
/// @details 支持 real/int/uint/bool。
/// @param arr 数组容器。
/// @param idx 样本下标。
/// @param out_raw 返回原始数值。
/// @return 成功读取返回 true, 否则返回 false。
bool stat_try_get_raw_numeric_at ( const PqdifValueArray & arr , size_t idx , double & out_raw )
{
if ( idx < arr . real_values . size ( ) )
{
out_raw = arr . real_values [ idx ] ;
return true ;
}
if ( idx < arr . int_values . size ( ) )
{
out_raw = static_cast < double > ( arr . int_values [ idx ] ) ;
return true ;
}
if ( idx < arr . uint_values . size ( ) )
{
out_raw = static_cast < double > ( arr . uint_values [ idx ] ) ;
return true ;
}
if ( idx < arr . bool_values . size ( ) )
{
out_raw = arr . bool_values [ idx ] ? 1.0 : 0.0 ;
return true ;
}
return false ;
}
/// @brief 将原始值还原为工程值。
/// @details
/// 当序列存储方式包含 SCALED 且底层值是整型时,使用 raw*scale+offset;
/// 其他情况默认认为原始值即为工程值。
/// @param raw_value 原始数值。
/// @param si 序列实例,用于读取 scale/offset。
/// @param storage_method_id 序列定义层的存储方式。
/// @param physical_type 当前数组的物理类型。
/// @return 工程值。
double stat_decode_engineering_value (
double raw_value ,
const PqdifSeriesInstance & si ,
unsigned int storage_method_id ,
int physical_type )
{
const bool integer_like =
physical_type = = ID_PHYS_TYPE_INTEGER1 | |
physical_type = = ID_PHYS_TYPE_INTEGER2 | |
physical_type = = ID_PHYS_TYPE_INTEGER4 | |
physical_type = = ID_PHYS_TYPE_UNS_INTEGER1 | |
physical_type = = ID_PHYS_TYPE_UNS_INTEGER2 | |
physical_type = = ID_PHYS_TYPE_UNS_INTEGER4 ;
if ( ( storage_method_id & ID_SERIES_METHOD_SCALED ) & & integer_like )
return raw_value * si . scale + si . offset ;
return raw_value ;
}
/// @brief 解析某一个样本点对应的绝对时刻。
/// @details
/// 优先使用 timestamp_values; 若时间序列是数值型, 则按
/// observation.time_start + 偏移秒 计算。
/// @param obs 当前 observation。
/// @param time_series 已解析出的时间序列实例。
/// @param idx 样本下标。
/// @param out_ts 返回绝对时刻。
/// @return 成功返回 true, 否则返回 false。
bool stat_resolve_timestamp_at (
const PqdifObservationRecord & obs ,
const PqdifSeriesInstance & time_series ,
size_t idx ,
time_t & out_ts )
{
if ( idx < time_series . values . timestamp_values . size ( ) )
{
out_ts = time_series . values . timestamp_values [ idx ] . unix_time ;
return true ;
}
double raw_time = 0.0 ;
if ( stat_try_get_raw_numeric_at ( time_series . values , idx , raw_time ) )
{
if ( obs . time_start . unix_time ! = 0 )
{
out_ts = obs . time_start . unix_time + static_cast < time_t > ( std : : llround ( raw_time ) ) ;
return true ;
}
out_ts = static_cast < time_t > ( std : : llround ( raw_time ) ) ;
return true ;
}
return false ;
}
/// @brief 判断 GUID 是否相等。
/// @param a GUID A。
/// @param b GUID B。
/// @return 相等返回 true。
bool stat_guid_equals ( const GUID & a , const GUID & b )
{
return PQDIF_IsEqualGUID ( a , b ) ;
}
/// @brief 判断是否为旧式 P95 值类型。
/// @param value_type_id 值类型 GUID。
/// @return 若是旧式 P95 值类型则返回 true。
bool stat_is_p95_value_type ( const GUID & value_type_id )
{
return stat_guid_equals ( value_type_id , ID_SERIES_VALUE_TYPE_P95 ) ;
}
/// @brief 识别序列属于哪一种统计值类型。
/// @details 支持 MIN / MAX / AVG / P95。
/// @param si 序列实例。
/// @param sd 关联的序列定义,可为空。
/// @return 识别出的统计值类型。
StatValueKind stat_identify_value_kind (
const PqdifSeriesInstance & si ,
const PqdifSeriesDefinition * sd )
{
if ( pqdif_sem : : IsValueTypeMin ( si . value_type_id . value ) | |
stat_guid_equals ( si . value_type_id . value , ID_SERIES_VALUE_TYPE_PHASEANGLE_MIN ) )
return StatValueKind : : Min ;
if ( pqdif_sem : : IsValueTypeMax ( si . value_type_id . value ) | |
stat_guid_equals ( si . value_type_id . value , ID_SERIES_VALUE_TYPE_PHASEANGLE_MAX ) )
return StatValueKind : : Max ;
if ( pqdif_sem : : IsValueTypeAvg ( si . value_type_id . value ) | |
stat_guid_equals ( si . value_type_id . value , ID_SERIES_VALUE_TYPE_PHASEANGLE_AVG ) )
return StatValueKind : : Avg ;
if ( stat_is_p95_value_type ( si . value_type_id . value ) )
return StatValueKind : : P95 ;
if ( sd ! = nullptr & & std : : fabs ( sd - > prob_percentile - 95.0 ) < 1e-6 )
return StatValueKind : : P95 ;
return StatValueKind : : Unknown ;
}
/// @brief 判断是否为可以用“单值序列”兜底填充 Min/Max/Avg/P95 的指标。
/// @details 部分 PQDIF 厂家会把 Pst/Plt/DVC 这类已经是统计窗口结果的量,
/// 存成 Value/Instantaneous 单序列,而不是四条 Min/Max/Avg/P95 序列。
bool stat_metric_can_use_scalar_series_as_all_kinds ( StatMetricId id )
{
switch ( id )
{
case StatMetricId : : UaDvc :
case StatMetricId : : UbDvc :
case StatMetricId : : UcDvc :
case StatMetricId : : UabDvc :
case StatMetricId : : UbcDvc :
case StatMetricId : : UcaDvc :
case StatMetricId : : UaPst :
case StatMetricId : : UbPst :
case StatMetricId : : UcPst :
case StatMetricId : : UabPst :
case StatMetricId : : UbcPst :
case StatMetricId : : UcaPst :
case StatMetricId : : UaPlt :
case StatMetricId : : UbPlt :
case StatMetricId : : UcPlt :
case StatMetricId : : UabPlt :
case StatMetricId : : UbcPlt :
case StatMetricId : : UcaPlt :
return true ;
default :
return false ;
}
}
/// @brief 判断未知值类型序列是否可作为 Pst/Plt/DVC 的单值数据序列。
/// @details 排除时间轴、状态、计数、区间等非物理量序列,避免把质量标志误写成指标值。
bool stat_series_can_be_scalar_stat_value (
const PqdifSeriesInstance & si ,
const PqdifSeriesDefinition * sd )
{
const GUID & vt = si . value_type_id . value ;
if ( pqdif_sem : : IsValueTypeTime ( vt ) | |
stat_guid_equals ( vt , ID_SERIES_VALUE_TYPE_TIME ) | |
stat_guid_equals ( vt , ID_SERIES_VALUE_TYPE_STATUS ) | |
stat_guid_equals ( vt , ID_SERIES_VALUE_TYPE_COUNT ) | |
stat_guid_equals ( vt , ID_SERIES_VALUE_TYPE_INTERVAL ) | |
stat_guid_equals ( vt , ID_SERIES_VALUE_TYPE_DURATION ) )
{
return false ;
}
if ( sd ! = nullptr )
{
const GUID & def_vt = sd - > value_type_id . value ;
if ( pqdif_sem : : IsValueTypeTime ( def_vt ) | |
stat_guid_equals ( def_vt , ID_SERIES_VALUE_TYPE_TIME ) | |
stat_guid_equals ( def_vt , ID_SERIES_VALUE_TYPE_STATUS ) | |
stat_guid_equals ( def_vt , ID_SERIES_VALUE_TYPE_COUNT ) | |
stat_guid_equals ( def_vt , ID_SERIES_VALUE_TYPE_INTERVAL ) | |
stat_guid_equals ( def_vt , ID_SERIES_VALUE_TYPE_DURATION ) )
{
return false ;
}
}
return true ;
}
/// @brief 指标对象族。
/// @details 先按 C.2 表语义识别对象族,再根据相别拆成最终 metric_id。
enum class StatFamily
{
Unknown = 0 ,
VoltageRms ,
CurrentRms ,
VoltageDeviation ,
Frequency ,
FrequencyDeviation ,
VoltageZeroSequence ,
VoltageNegativeSequence ,
VoltagePositiveSequence ,
VoltageNegativeSequenceUnbalance ,
CurrentZeroSequence ,
CurrentNegativeSequence ,
CurrentPositiveSequence ,
CurrentNegativeSequenceUnbalance
} ;
/// @brief 将名称归一化为“仅保留字母数字并转大写”的紧凑形式。
/// @param s 原始名称。
/// @return 归一化后的字符串。
std : : string stat_compact_upper ( const std : : string & s )
{
std : : string out ;
out . reserve ( s . size ( ) ) ;
for ( unsigned char ch : s )
{
if ( std : : isalnum ( ch ) )
out . push_back ( static_cast < char > ( std : : toupper ( ch ) ) ) ;
}
return out ;
}
/// @brief 判断归一化后的文本中是否包含任一别名。
/// @param compact_text 归一化文本。
/// @param aliases 别名集合。
/// @return 命中任一别名则返回 true。
bool stat_contains_any_alias (
const std : : string & compact_text ,
const std : : vector < std : : string > & aliases )
{
for ( const auto & alias : aliases )
{
if ( ! alias . empty ( ) & & compact_text . find ( alias ) ! = std : : string : : npos )
return true ;
}
return false ;
}
bool stat_ends_with ( const std : : string & text , const std : : string & suffix )
{
return text . size ( ) > = suffix . size ( ) & &
text . compare ( text . size ( ) - suffix . size ( ) , suffix . size ( ) , suffix ) = = 0 ;
}
std : : string stat_two_digit ( int n )
{
char buf [ 8 ] = { 0 } ;
std : : snprintf ( buf , sizeof ( buf ) , " %02d " , n ) ;
return std : : string ( buf ) ;
}
int stat_round_order_if_valid ( double value )
{
const int rounded = static_cast < int > ( std : : llround ( value ) ) ;
if ( rounded < 2 | | rounded > 50 )
return - 1 ;
if ( std : : fabs ( value - static_cast < double > ( rounded ) ) > 1e-6 )
return - 1 ;
return rounded ;
}
/// @brief 动态谱类次数解析的特殊返回值:标准 group=1 表示基波,应跳过,不能兜底映射为 2 次。
constexpr int STAT_DYNAMIC_ORDER_SKIP_FUNDAMENTAL = - 100000 ;
bool stat_is_voltage_harmonic_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_HRMS ) | |
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SPECTRA ) | |
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SPECTRA_HGROUP ) ;
}
bool stat_is_voltage_harmonic_unit ( unsigned int unit_id )
{
// UaHarmXX/UbHarmXX/UcHarmXX 表示谐波电压 RMS 值,单位应为 V。
// OBS 中常见的 V HR A/B/C 是谐波含有率/百分比,后续应单独建 HarmRatio 指标,
// 这里不能混入同一个谐波电压指标。
return unit_id = = ID_QU_VOLTS ;
}
/// @brief C.2 标准字段优先识别:电压谐波 RMS / 按谐波组频谱。
/// @details
/// 这里优先使用三个标准字段,而不是名称:
/// tagQuantityMeasuredID = ID_QM_VOLTAGE
/// tagQuantityCharacteristicID = ID_QC_HRMS / ID_QC_SPECTRA / ID_QC_SPECTRA_HGROUP
/// tagQuantityUnitsID = ID_QU_VOLTS
/// 谐波次数不在这三个 ID 中,当前文件通过 tagChannelGroupID 表达 group=2..50。
bool stat_match_c2_voltage_harmonic_rms ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_VOLTAGE & &
stat_is_voltage_harmonic_unit ( p . quantity_units_id ) & &
stat_is_voltage_harmonic_characteristic ( p ) ;
}
int stat_extract_harmonic_order_from_compact_text ( const std : : string & compact_text )
{
if ( compact_text . empty ( ) )
return - 1 ;
// 优先匹配带有明确 HARM/HRMS/H 前缀或后缀的写法,避免误把普通数字当作谐波次数。
for ( int order = 2 ; order < = 50 ; + + order )
{
const std : : string n = std : : to_string ( order ) ;
const std : : string nn = stat_two_digit ( order ) ;
const std : : vector < std : : string > aliases = {
" HARMONIC " + n ,
" HARMONIC " + nn ,
" HARM " + n ,
" HARM " + nn ,
" HRMS " + n ,
" HRMS " + nn ,
" HGROUP " + n ,
" HGROUP " + nn ,
" H " + n ,
" H " + nn ,
n + " HARMONIC " ,
nn + " HARMONIC " ,
n + " HARM " ,
nn + " HARM "
} ;
if ( stat_contains_any_alias ( compact_text , aliases ) )
return order ;
}
return - 1 ;
}
int stat_extract_order_from_standard_fields ( const ExpandedStatPoint & p )
{
// 当前厂家文件把谐波次数写在 channel_group_id 中: group=1 为基波, group=2..50 为 2-50 次谐波。
// 修复点:一旦明确出现 group=1, 必须视为基波并跳过, 不能继续用 order_hint/name 兜底成 2 次。
if ( p . channel_group_id = = 1 )
return STAT_DYNAMIC_ORDER_SKIP_FUNDAMENTAL ;
const int order_by_group = stat_round_order_if_valid ( static_cast < double > ( p . channel_group_id ) ) ;
if ( order_by_group > = 2 & & order_by_group < = 50 )
return order_by_group ;
// 其他厂家可能放在 series_base_quantity 或 nominal_quantity, 作为标准字段不足时的补充。
// 仅当 channel_group_id 没有给出明确次数时才使用这些字段。
const int order_by_base = stat_round_order_if_valid ( p . series_base_quantity ) ;
if ( order_by_base > = 2 & & order_by_base < = 50 )
return order_by_base ;
const int order_by_nominal = stat_round_order_if_valid ( p . nominal_quantity ) ;
if ( order_by_nominal > = 2 & & order_by_nominal < = 50 )
return order_by_nominal ;
// 有些厂家把谐波/相角谱线拆成多个 channel instance, 但每条通道名称完全相同,
// 且没有写 channel_group_id/base/nominal。此时在展开通道时会按同类通道实例顺序
// 生成 order_hint, 避免所有通道都被名称里的 “HARM2” 误识别为 2 次。
// 注意:如果上面已检测到 group=1, 这里不会执行。
if ( p . channel_spectrum_order_hint > = 2 & & p . channel_spectrum_order_hint < = 50 )
return p . channel_spectrum_order_hint ;
return - 1 ;
}
int stat_extract_voltage_harmonic_order ( const ExpandedStatPoint & p )
{
if ( p . quantity_measured_id ! = ID_QM_VOLTAGE )
return - 1 ;
if ( ! stat_is_voltage_harmonic_unit ( p . quantity_units_id ) )
return - 1 ;
// 第一优先级: C.2 标准字段组合。
// 只要 measured + characteristic + unit 命中,就直接从 group/base/nominal 读取次数,
// 不要求通道名包含 HRMS/HARM/HGROUP。
if ( stat_match_c2_voltage_harmonic_rms ( p ) )
{
const int order_by_standard = stat_extract_order_from_standard_fields ( p ) ;
if ( order_by_standard = = STAT_DYNAMIC_ORDER_SKIP_FUNDAMENTAL )
return - 1 ;
if ( order_by_standard > = 2 & & order_by_standard < = 50 )
return order_by_standard ;
}
// 第二优先级:厂家名称兜底。仅在标准 characteristic 缺失或不规范时使用。
const std : : string compact_text =
stat_compact_upper ( p . channel_name ) + stat_compact_upper ( p . quantity_name ) ;
const bool text_has_harmonic_hint = stat_contains_any_alias ( compact_text , {
" HARM " , " HARMONIC " , " HRMS " , " HGROUP " , " SPECTRA "
} ) ;
if ( ! text_has_harmonic_hint )
return - 1 ;
const int order_by_name = stat_extract_harmonic_order_from_compact_text ( compact_text ) ;
if ( order_by_name > = 2 & & order_by_name < = 50 )
return order_by_name ;
{
const int order_by_standard = stat_extract_order_from_standard_fields ( p ) ;
if ( order_by_standard = = STAT_DYNAMIC_ORDER_SKIP_FUNDAMENTAL )
return - 1 ;
return order_by_standard ;
}
}
int stat_extract_voltage_harmonic_phase_index ( const ExpandedStatPoint & p )
{
if ( p . phase_id = = ID_PHASE_AN ) return 0 ;
if ( p . phase_id = = ID_PHASE_BN ) return 1 ;
if ( p . phase_id = = ID_PHASE_CN ) return 2 ;
const std : : string compact_text =
stat_compact_upper ( p . channel_name ) + stat_compact_upper ( p . quantity_name ) ;
if ( stat_contains_any_alias ( compact_text , { " PHASEA " , " PHA " , " VHA " , " UHARMA " , " VHARMONICA " , " UHARMONICA " , " HRMSA " } ) )
return 0 ;
if ( stat_contains_any_alias ( compact_text , { " PHASEB " , " PHB " , " VHB " , " UHARMB " , " VHARMONICB " , " UHARMONICB " , " HRMSB " } ) )
return 1 ;
if ( stat_contains_any_alias ( compact_text , { " PHASEC " , " PHC " , " VHC " , " UHARMC " , " VHARMONICC " , " UHARMONICC " , " HRMSC " } ) )
return 2 ;
// 常见格式如 VH02A / V2HA / U50B: 优先使用末尾相别, 但避免把单独的 HARMONIC 误判成 C 相。
if ( stat_ends_with ( compact_text , " A " ) & & ! stat_ends_with ( compact_text , " HARMONICA " ) )
return 0 ;
if ( stat_ends_with ( compact_text , " B " ) & & ! stat_ends_with ( compact_text , " HARMONICB " ) )
return 1 ;
if ( stat_ends_with ( compact_text , " C " ) & & ! stat_ends_with ( compact_text , " HARMONIC " ) )
return 2 ;
return - 1 ;
}
bool stat_is_harmonic_group_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_HRMS ) | |
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SPECTRA ) | |
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SPECTRA_HGROUP ) ;
}
bool stat_is_interharmonic_group_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SPECTRA_IGROUP ) ;
}
bool stat_is_phase_angle_value_type ( const GUID & value_type_id )
{
return stat_guid_equals ( value_type_id , ID_SERIES_VALUE_TYPE_PHASEANGLE ) | |
stat_guid_equals ( value_type_id , ID_SERIES_VALUE_TYPE_PHASEANGLE_MIN ) | |
stat_guid_equals ( value_type_id , ID_SERIES_VALUE_TYPE_PHASEANGLE_MAX ) | |
stat_guid_equals ( value_type_id , ID_SERIES_VALUE_TYPE_PHASEANGLE_AVG ) ;
}
bool stat_is_angle_like_point ( const ExpandedStatPoint & p )
{
if ( stat_is_phase_angle_value_type ( p . value_type_id . value ) )
return true ;
if ( p . quantity_units_id = = ID_QU_DEGREES )
return true ;
const std : : string compact = stat_compact_upper ( p . channel_name ) +
stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . value_type_id . symbolic_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) ;
return stat_contains_any_alias ( compact , { " PHASEANGLE " , " PHASANGLE " , " ANGLE " , " PANGLE " , " HANGLE " } ) ;
}
bool stat_is_magnitude_unit_for_measured ( unsigned int measured_id , unsigned int unit_id )
{
if ( measured_id = = ID_QM_VOLTAGE )
return unit_id = = ID_QU_VOLTS ;
if ( measured_id = = ID_QM_CURRENT )
return unit_id = = ID_QU_AMPS ;
return false ;
}
int stat_extract_line_voltage_phase_index ( const ExpandedStatPoint & p )
{
if ( p . phase_id = = ID_PHASE_AB ) return 0 ;
if ( p . phase_id = = ID_PHASE_BC ) return 1 ;
if ( p . phase_id = = ID_PHASE_CA ) return 2 ;
const std : : string compact = stat_compact_upper ( p . channel_name ) + stat_compact_upper ( p . quantity_name ) ;
if ( stat_contains_any_alias ( compact , { " PHASEAB " , " PHAB " , " UAB " , " VAB " , " HRMSAB " , " HARMAB " } ) ) return 0 ;
if ( stat_contains_any_alias ( compact , { " PHASEBC " , " PHBC " , " UBC " , " VBC " , " HRMSBC " , " HARMBC " } ) ) return 1 ;
if ( stat_contains_any_alias ( compact , { " PHASECA " , " PHCA " , " UCA " , " VCA " , " HRMSCA " , " HARMCA " } ) ) return 2 ;
if ( stat_ends_with ( compact , " AB " ) ) return 0 ;
if ( stat_ends_with ( compact , " BC " ) ) return 1 ;
if ( stat_ends_with ( compact , " CA " ) ) return 2 ;
return - 1 ;
}
int stat_extract_phase_current_phase_index ( const ExpandedStatPoint & p )
{
if ( p . phase_id = = ID_PHASE_AN ) return 0 ;
if ( p . phase_id = = ID_PHASE_BN ) return 1 ;
if ( p . phase_id = = ID_PHASE_CN ) return 2 ;
const std : : string compact = stat_compact_upper ( p . channel_name ) + stat_compact_upper ( p . quantity_name ) ;
if ( stat_contains_any_alias ( compact , { " PHASEA " , " PHA " , " IHA " , " IHARMA " , " IHARMONICA " , " HRMSA " } ) ) return 0 ;
if ( stat_contains_any_alias ( compact , { " PHASEB " , " PHB " , " IHB " , " IHARMB " , " IHARMONICB " , " HRMSB " } ) ) return 1 ;
if ( stat_contains_any_alias ( compact , { " PHASEC " , " PHC " , " IHC " , " IHARMC " , " IHARMONICC " , " HRMSC " } ) ) return 2 ;
if ( stat_ends_with ( compact , " A " ) ) return 0 ;
if ( stat_ends_with ( compact , " B " ) ) return 1 ;
if ( stat_ends_with ( compact , " C " ) ) return 2 ;
return - 1 ;
}
int stat_round_interharmonic_slot_if_valid ( double value )
{
if ( value < - 1e-6 | | value > 49.5 + 1e-6 )
return - 1 ;
const double slot_value = value - 0.5 ;
const int slot = static_cast < int > ( std : : llround ( slot_value ) ) ;
if ( slot < 0 | | slot > 49 )
return - 1 ;
if ( std : : fabs ( slot_value - static_cast < double > ( slot ) ) > 1e-6 )
return - 1 ;
return slot ;
}
int stat_extract_interharmonic_slot_from_standard_fields ( const ExpandedStatPoint & p )
{
// 间谐波优先按标准 group 解析: group=1..50 -> 0.5..49.5。
// 即 slot=0..49。不要再兼容 group=0..49,否则会把缺失/无效 group 错映射为 0.5。
if ( p . channel_group_id > = 1 & & p . channel_group_id < = 50 )
return p . channel_group_id - 1 ;
int slot = stat_round_interharmonic_slot_if_valid ( p . series_base_quantity ) ;
if ( slot > = 0 ) return slot ;
slot = stat_round_interharmonic_slot_if_valid ( p . nominal_quantity ) ;
if ( slot > = 0 ) return slot ;
// 仅当标准字段没有给出 group/base/nominal 时,才使用展开阶段生成的顺序提示。
if ( p . channel_spectrum_order_hint > = 1 & & p . channel_spectrum_order_hint < = 50 )
return p . channel_spectrum_order_hint - 1 ;
return - 1 ;
}
bool stat_text_has_harmonic_hint ( const std : : string & compact )
{
return stat_contains_any_alias ( compact , { " HARM " , " HARMONIC " , " HRMS " , " HGROUP " , " SPECTRA " } ) ;
}
bool stat_text_has_interharmonic_hint ( const std : : string & compact )
{
return stat_contains_any_alias ( compact , { " INTERHARM " , " INTERHARMONIC " , " IHARM " , " IGROUP " , " IHGROUP " } ) ;
}
int stat_extract_interharmonic_slot_from_text ( const std : : string & compact )
{
if ( ! stat_text_has_interharmonic_hint ( compact ) )
return - 1 ;
for ( int slot = 0 ; slot < = 49 ; + + slot )
{
const int n = slot ;
const std : : string nn = stat_two_digit ( n ) ;
const std : : vector < std : : string > aliases = {
" IH " + nn + " P5 " , " IH " + std : : to_string ( n ) + " P5 " ,
" INTERHARM " + nn + " P5 " , " INTERHARM " + std : : to_string ( n ) + " P5 " ,
" INTERHARMONIC " + nn + " P5 " , " INTERHARMONIC " + std : : to_string ( n ) + " P5 "
} ;
if ( stat_contains_any_alias ( compact , aliases ) )
return slot ;
}
return - 1 ;
}
bool stat_guid_in ( const GUID & value , const std : : vector < const GUID * > & candidates )
{
for ( const GUID * g : candidates )
{
if ( g ! = nullptr & & stat_guid_equals ( value , * g ) )
return true ;
}
return false ;
}
bool stat_unit_is_ratio_like ( unsigned int unit_id )
{
return unit_id = = ID_QU_PERCENT | | unit_id = = ID_QU_PERUNIT | | unit_id = = ID_QU_NONE ;
}
bool stat_unit_is_power_unit ( unsigned int unit_id )
{
return unit_id = = ID_QU_WATTS | | unit_id = = ID_QU_VARS | | unit_id = = ID_QU_VA ;
}
bool stat_text_has_total_hint ( const std : : string & compact )
{
return stat_contains_any_alias ( compact , {
" TOTAL " , " TOT " , " SUM " , " THREEPHASE " , " 3PHASE " , " ALLPHASE " , " NET " ,
" PTOTAL " , " QTOTAL " , " STOTAL " , " PALL " , " QALL " , " SALL "
} ) ;
}
int stat_extract_phase_or_total_index ( const ExpandedStatPoint & p )
{
if ( p . phase_id = = ID_PHASE_AN ) return 0 ;
if ( p . phase_id = = ID_PHASE_BN ) return 1 ;
if ( p . phase_id = = ID_PHASE_CN ) return 2 ;
if ( p . phase_id = = ID_PHASE_TOTAL | | p . phase_id = = ID_PHASE_NET ) return 3 ;
const std : : string compact = stat_compact_upper ( p . channel_name ) + stat_compact_upper ( p . quantity_name ) ;
if ( stat_text_has_total_hint ( compact ) ) return 3 ;
if ( stat_contains_any_alias ( compact , { " PHASEA " , " PHA " , " PA " , " PA " , " A相 " } ) | | stat_ends_with ( compact , " A " ) ) return 0 ;
if ( stat_contains_any_alias ( compact , { " PHASEB " , " PHB " , " PB " , " PB " , " B相 " } ) | | stat_ends_with ( compact , " B " ) ) return 1 ;
if ( stat_contains_any_alias ( compact , { " PHASEC " , " PHC " , " PC " , " PC " , " C相 " } ) | | stat_ends_with ( compact , " C " ) ) return 2 ;
return - 1 ;
}
StatMetricId stat_metric_from_phase4 ( int phase , StatMetricId a , StatMetricId b , StatMetricId c , StatMetricId total )
{
if ( phase = = 0 ) return a ;
if ( phase = = 1 ) return b ;
if ( phase = = 2 ) return c ;
if ( phase = = 3 ) return total ;
return StatMetricId : : Unknown ;
}
StatMetricId stat_metric_from_phase3 ( int phase , StatMetricId a , StatMetricId b , StatMetricId c )
{
if ( phase = = 0 ) return a ;
if ( phase = = 1 ) return b ;
if ( phase = = 2 ) return c ;
return StatMetricId : : Unknown ;
}
bool stat_is_power_factor_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_in ( p . quantity_characteristic_id . value , {
& ID_QC_PF , & ID_QC_PF_VECTOR , & ID_QC_PF_ARITH
} ) ;
}
bool stat_is_fund_power_factor_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_in ( p . quantity_characteristic_id . value , {
& ID_QC_DF , & ID_QC_DF_VECTOR , & ID_QC_DF_ARITH
} ) ;
}
bool stat_is_active_power_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_P ) ;
}
bool stat_is_reactive_power_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_Q ) ;
}
bool stat_is_apparent_power_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_in ( p . quantity_characteristic_id . value , {
& ID_QC_S , & ID_QC_S_VECTOR , & ID_QC_S_ARITH
} ) ;
}
bool stat_is_active_fund_power_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_P_FUND ) ;
}
bool stat_is_reactive_fund_power_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_Q_FUND ) ;
}
bool stat_is_apparent_fund_power_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_in ( p . quantity_characteristic_id . value , {
& ID_QC_S_FUND , & ID_QC_S_VECTOR_FUND , & ID_QC_S_ARITH_FUND
} ) ;
}
bool stat_text_has_fund_hint ( const ExpandedStatPoint & p )
{
const std : : string compact = stat_compact_upper ( p . channel_name ) +
stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) ;
return stat_contains_any_alias ( compact , {
" FUND " , " FUNDAMENTAL " , " BASEFREQ " , " FREQFUND " , " 1STHARM " , " HARM1 "
} ) ;
}
bool stat_text_has_dvc_hint ( const ExpandedStatPoint & p )
{
const std : : string compact = stat_compact_upper ( p . channel_name ) +
stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) ;
return stat_contains_any_alias ( compact , { " DVC " , " DVV " , " DELTAV " , " VOLTAGECHANGE " , " VCHANGE " , " FLKRMAXDVV " } ) ;
}
bool stat_text_has_pst_hint ( const ExpandedStatPoint & p )
{
const std : : string compact = stat_compact_upper ( p . channel_name ) +
stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) ;
return stat_contains_any_alias ( compact , { " PST " , " SHORTTERMFLICKER " , " SHORTFLICKER " , " FLKRPST " } ) ;
}
bool stat_text_has_plt_hint ( const ExpandedStatPoint & p )
{
const std : : string compact = stat_compact_upper ( p . channel_name ) +
stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) ;
return stat_contains_any_alias ( compact , { " PLT " , " LONGTERMFLICKER " , " LONGFLICKER " , " FLKRPLT " } ) ;
}
bool stat_text_has_thd_hint ( const ExpandedStatPoint & p )
{
const std : : string compact = stat_compact_upper ( p . channel_name ) +
stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) ;
return stat_contains_any_alias ( compact , { " THD " , " TOTALHARMONICDISTORTION " , " TOTALTHD " } ) & &
! stat_contains_any_alias ( compact , { " OTHD " , " ETHD " , " ODDTHD " , " EVENTHD " } ) ;
}
bool stat_is_total_thd_characteristic ( const ExpandedStatPoint & p )
{
return stat_guid_in ( p . quantity_characteristic_id . value , {
& ID_QC_TOTAL_THD , & ID_QC_TOTAL_THD_RMS
} ) ;
}
StatMetricId stat_identify_scalar_extension_metric ( const ExpandedStatPoint & p , bool & matched_by_name_fallback )
{
matched_by_name_fallback = false ;
const std : : string compact = stat_compact_upper ( p . channel_name ) +
stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) ;
// 三相/总有功、无功、视在功率;以及基波功率。
if ( p . quantity_measured_id = = ID_QM_POWER )
{
const int phase = stat_extract_phase_or_total_index ( p ) ;
if ( phase > = 0 )
{
if ( stat_is_active_power_characteristic ( p ) & & p . quantity_units_id = = ID_QU_WATTS )
return stat_metric_from_phase4 ( phase , StatMetricId : : PaPower , StatMetricId : : PbPower , StatMetricId : : PcPower , StatMetricId : : PTotalPower ) ;
if ( stat_is_reactive_power_characteristic ( p ) & & p . quantity_units_id = = ID_QU_VARS )
return stat_metric_from_phase4 ( phase , StatMetricId : : QaPower , StatMetricId : : QbPower , StatMetricId : : QcPower , StatMetricId : : QTotalPower ) ;
if ( stat_is_apparent_power_characteristic ( p ) & & p . quantity_units_id = = ID_QU_VA )
return stat_metric_from_phase4 ( phase , StatMetricId : : SaPower , StatMetricId : : SbPower , StatMetricId : : ScPower , StatMetricId : : STotalPower ) ;
if ( stat_is_active_fund_power_characteristic ( p ) & & p . quantity_units_id = = ID_QU_WATTS )
return stat_metric_from_phase4 ( phase , StatMetricId : : PaFundPower , StatMetricId : : PbFundPower , StatMetricId : : PcFundPower , StatMetricId : : PTotalFundPower ) ;
if ( stat_is_reactive_fund_power_characteristic ( p ) & & p . quantity_units_id = = ID_QU_VARS )
return stat_metric_from_phase4 ( phase , StatMetricId : : QaFundPower , StatMetricId : : QbFundPower , StatMetricId : : QcFundPower , StatMetricId : : QTotalFundPower ) ;
if ( stat_is_apparent_fund_power_characteristic ( p ) & & p . quantity_units_id = = ID_QU_VA )
return stat_metric_from_phase4 ( phase , StatMetricId : : SaFundPower , StatMetricId : : SbFundPower , StatMetricId : : ScFundPower , StatMetricId : : STotalFundPower ) ;
if ( stat_is_power_factor_characteristic ( p ) & & stat_unit_is_ratio_like ( p . quantity_units_id ) )
return stat_metric_from_phase4 ( phase , StatMetricId : : PFa , StatMetricId : : PFb , StatMetricId : : PFc , StatMetricId : : PFTotal ) ;
if ( stat_is_fund_power_factor_characteristic ( p ) & & stat_unit_is_ratio_like ( p . quantity_units_id ) )
return stat_metric_from_phase4 ( phase , StatMetricId : : FundPFa , StatMetricId : : FundPFb , StatMetricId : : FundPFc , StatMetricId : : FundPFTotal ) ;
}
// 名称兜底:厂家可能只填 NONE/INSTANTANEOUS, 但通道名中含 P/Q/S/PF/FUND。
if ( phase > = 0 )
{
if ( p . quantity_units_id = = ID_QU_WATTS & & stat_contains_any_alias ( compact , { " PFUND " , " FUNDAMENTALP " , " FUNDPOWERP " } ) )
{
matched_by_name_fallback = true ;
return stat_metric_from_phase4 ( phase , StatMetricId : : PaFundPower , StatMetricId : : PbFundPower , StatMetricId : : PcFundPower , StatMetricId : : PTotalFundPower ) ;
}
if ( p . quantity_units_id = = ID_QU_VARS & & stat_contains_any_alias ( compact , { " QFUND " , " FUNDAMENTALQ " , " FUNDPOWERQ " } ) )
{
matched_by_name_fallback = true ;
return stat_metric_from_phase4 ( phase , StatMetricId : : QaFundPower , StatMetricId : : QbFundPower , StatMetricId : : QcFundPower , StatMetricId : : QTotalFundPower ) ;
}
if ( p . quantity_units_id = = ID_QU_VA & & stat_contains_any_alias ( compact , { " SFUND " , " FUNDAMENTALS " , " FUNDPOWERS " } ) )
{
matched_by_name_fallback = true ;
return stat_metric_from_phase4 ( phase , StatMetricId : : SaFundPower , StatMetricId : : SbFundPower , StatMetricId : : ScFundPower , StatMetricId : : STotalFundPower ) ;
}
if ( stat_unit_is_ratio_like ( p . quantity_units_id ) & & stat_contains_any_alias ( compact , { " FUNDPF " , " FUNDAMENTALPF " , " DISPLACEMENTFACTOR " , " DF " } ) )
{
matched_by_name_fallback = true ;
return stat_metric_from_phase4 ( phase , StatMetricId : : FundPFa , StatMetricId : : FundPFb , StatMetricId : : FundPFc , StatMetricId : : FundPFTotal ) ;
}
if ( stat_unit_is_ratio_like ( p . quantity_units_id ) & & stat_contains_any_alias ( compact , { " POWERFACTOR " , " PF " } ) )
{
matched_by_name_fallback = true ;
return stat_metric_from_phase4 ( phase , StatMetricId : : PFa , StatMetricId : : PFb , StatMetricId : : PFc , StatMetricId : : PFTotal ) ;
}
if ( p . quantity_units_id = = ID_QU_WATTS & & stat_contains_any_alias ( compact , { " ACTIVEPOWER " , " REALPOWER " , " PW " , " PPOWER " } ) )
{
matched_by_name_fallback = true ;
return stat_metric_from_phase4 ( phase , StatMetricId : : PaPower , StatMetricId : : PbPower , StatMetricId : : PcPower , StatMetricId : : PTotalPower ) ;
}
if ( p . quantity_units_id = = ID_QU_VARS & & stat_contains_any_alias ( compact , { " REACTIVEPOWER " , " QPOWER " , " VAR " } ) )
{
matched_by_name_fallback = true ;
return stat_metric_from_phase4 ( phase , StatMetricId : : QaPower , StatMetricId : : QbPower , StatMetricId : : QcPower , StatMetricId : : QTotalPower ) ;
}
if ( p . quantity_units_id = = ID_QU_VA & & stat_contains_any_alias ( compact , { " APPARENTPOWER " , " SPOWER " , " VA " } ) )
{
matched_by_name_fallback = true ;
return stat_metric_from_phase4 ( phase , StatMetricId : : SaPower , StatMetricId : : SbPower , StatMetricId : : ScPower , StatMetricId : : STotalPower ) ;
}
}
}
// 总谐波畸变率 THD: 三相电压/电流/线电压。
if ( ( p . quantity_measured_id = = ID_QM_VOLTAGE | | p . quantity_measured_id = = ID_QM_CURRENT ) & &
stat_unit_is_ratio_like ( p . quantity_units_id ) & &
( stat_is_total_thd_characteristic ( p ) | | stat_text_has_thd_hint ( p ) ) )
{
if ( ! stat_is_total_thd_characteristic ( p ) )
matched_by_name_fallback = true ;
if ( p . quantity_measured_id = = ID_QM_CURRENT )
{
const int phase = stat_extract_phase_current_phase_index ( p ) ;
return stat_metric_from_phase3 ( phase , StatMetricId : : IaThd , StatMetricId : : IbThd , StatMetricId : : IcThd ) ;
}
int phase = stat_extract_voltage_harmonic_phase_index ( p ) ;
if ( phase > = 0 )
return stat_metric_from_phase3 ( phase , StatMetricId : : UaThd , StatMetricId : : UbThd , StatMetricId : : UcThd ) ;
phase = stat_extract_line_voltage_phase_index ( p ) ;
if ( phase > = 0 )
return stat_metric_from_phase3 ( phase , StatMetricId : : UabThd , StatMetricId : : UbcThd , StatMetricId : : UcaThd ) ;
}
// 电压变动幅值 DVC/dV/V。
// 先按 C.2 ID 组合识别;若厂家未规范填写 measured, 只要名称明确包含电压变动提示, 也允许名称兜底。
const bool measured_is_voltage_or_voltage_name =
p . quantity_measured_id = = ID_QM_VOLTAGE | |
stat_contains_any_alias ( compact , { " VOLTAGE " , " VFLICKER " , " VFLKR " , " VDVC " , " UDVC " , " DVV " } ) ;
if ( measured_is_voltage_or_voltage_name & &
stat_unit_is_ratio_like ( p . quantity_units_id ) & &
( stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_FLKR_MAX_DVV ) | | stat_text_has_dvc_hint ( p ) ) )
{
if ( p . quantity_measured_id ! = ID_QM_VOLTAGE | |
! stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_FLKR_MAX_DVV ) )
{
matched_by_name_fallback = true ;
}
int phase = stat_extract_voltage_harmonic_phase_index ( p ) ;
if ( phase > = 0 )
return stat_metric_from_phase3 ( phase , StatMetricId : : UaDvc , StatMetricId : : UbDvc , StatMetricId : : UcDvc ) ;
phase = stat_extract_line_voltage_phase_index ( p ) ;
if ( phase > = 0 )
return stat_metric_from_phase3 ( phase , StatMetricId : : UabDvc , StatMetricId : : UbcDvc , StatMetricId : : UcaDvc ) ;
}
// 闪变 Pst/Plt。
// 修复点:
// 1) ID_QC_FLKR_PST / ID_QC_FLKR_PLT 仍然是第一优先级;
// 2) 对 V Flicker Plt A/B/C、PLT VA/VB/VC 等厂家名称进行兜底;
// 3) 不强依赖 measured 必须填成 Voltage, 避免厂家只靠通道名表达对象。
const bool id_is_pst = stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_FLKR_PST ) ;
const bool id_is_plt = stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_FLKR_PLT ) ;
const bool is_pst = id_is_pst | | stat_text_has_pst_hint ( p ) ;
const bool is_plt = id_is_plt | | stat_text_has_plt_hint ( p ) ;
const bool measured_is_voltage_or_flicker_name =
measured_is_voltage_or_voltage_name | | is_pst | | is_plt ;
// Plt/Pst 修复:厂家有时会把 Plt 序列 unit 写成 Unknown/Unrecognized, 而不是 None/pu/%。
// 只要 C.2 characteristic ID 或通道名称已经明确是 Pst/Plt, 就不再用 unit 作为硬性门槛。
// unit 仅用于辅助判断,不作为清晰 Flicker 通道的排除条件。
if ( measured_is_voltage_or_flicker_name & & ( is_pst | | is_plt ) )
{
if ( p . quantity_measured_id ! = ID_QM_VOLTAGE | | ! ( id_is_pst | | id_is_plt ) )
matched_by_name_fallback = true ;
// 若同时命中 Pst 和 Plt, 以标准 ID 为准;没有标准 ID 时 Plt 优先,
// 避免 “V Flicker Plt A/B/C” 因 unit 非标准而被跳过。
const bool choose_plt = id_is_plt | | ( ! id_is_pst & & is_plt ) ;
int phase = stat_extract_voltage_harmonic_phase_index ( p ) ;
if ( phase > = 0 )
return choose_plt ?
stat_metric_from_phase3 ( phase , StatMetricId : : UaPlt , StatMetricId : : UbPlt , StatMetricId : : UcPlt ) :
stat_metric_from_phase3 ( phase , StatMetricId : : UaPst , StatMetricId : : UbPst , StatMetricId : : UcPst ) ;
phase = stat_extract_line_voltage_phase_index ( p ) ;
if ( phase > = 0 )
return choose_plt ?
stat_metric_from_phase3 ( phase , StatMetricId : : UabPlt , StatMetricId : : UbcPlt , StatMetricId : : UcaPlt ) :
stat_metric_from_phase3 ( phase , StatMetricId : : UabPst , StatMetricId : : UbcPst , StatMetricId : : UcaPst ) ;
}
// 基波有效值和基波相角。
if ( ( p . quantity_measured_id = = ID_QM_VOLTAGE | | p . quantity_measured_id = = ID_QM_CURRENT ) & & stat_text_has_fund_hint ( p ) )
{
const bool is_angle = stat_is_angle_like_point ( p ) | | stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_ANGLE_FUND ) ;
const bool is_rms = stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_RMS ) | |
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_INSTANTANEOUS ) | |
stat_contains_any_alias ( compact , { " RMS " , " FUND " } ) ;
if ( p . quantity_measured_id = = ID_QM_CURRENT )
{
const int phase = stat_extract_phase_current_phase_index ( p ) ;
if ( is_angle & & p . quantity_units_id = = ID_QU_DEGREES )
{
matched_by_name_fallback = ! stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_ANGLE_FUND ) ;
return stat_metric_from_phase3 ( phase , StatMetricId : : IaFundAngle , StatMetricId : : IbFundAngle , StatMetricId : : IcFundAngle ) ;
}
if ( is_rms & & p . quantity_units_id = = ID_QU_AMPS )
{
matched_by_name_fallback = ! stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_RMS ) ;
return stat_metric_from_phase3 ( phase , StatMetricId : : IaFundRms , StatMetricId : : IbFundRms , StatMetricId : : IcFundRms ) ;
}
}
else
{
int phase = stat_extract_voltage_harmonic_phase_index ( p ) ;
if ( phase > = 0 )
{
if ( is_angle & & p . quantity_units_id = = ID_QU_DEGREES )
{
matched_by_name_fallback = ! stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_ANGLE_FUND ) ;
return stat_metric_from_phase3 ( phase , StatMetricId : : UaFundAngle , StatMetricId : : UbFundAngle , StatMetricId : : UcFundAngle ) ;
}
if ( is_rms & & p . quantity_units_id = = ID_QU_VOLTS )
{
matched_by_name_fallback = ! stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_RMS ) ;
return stat_metric_from_phase3 ( phase , StatMetricId : : UaFundRms , StatMetricId : : UbFundRms , StatMetricId : : UcFundRms ) ;
}
}
phase = stat_extract_line_voltage_phase_index ( p ) ;
if ( phase > = 0 )
{
if ( is_angle & & p . quantity_units_id = = ID_QU_DEGREES )
{
matched_by_name_fallback = ! stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_ANGLE_FUND ) ;
return stat_metric_from_phase3 ( phase , StatMetricId : : UabFundAngle , StatMetricId : : UbcFundAngle , StatMetricId : : UcaFundAngle ) ;
}
if ( is_rms & & p . quantity_units_id = = ID_QU_VOLTS )
{
matched_by_name_fallback = ! stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_RMS ) ;
return stat_metric_from_phase3 ( phase , StatMetricId : : UabFundRms , StatMetricId : : UbcFundRms , StatMetricId : : UcaFundRms ) ;
}
}
}
}
return StatMetricId : : Unknown ;
}
StatMetricId stat_identify_dynamic_extension_metric ( const ExpandedStatPoint & p , bool & matched_by_name_fallback )
{
matched_by_name_fallback = false ;
const std : : string compact = stat_compact_upper ( p . channel_name ) +
stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) +
stat_compact_upper ( p . value_type_id . symbolic_name ) ;
const bool standard_harmonic =
stat_is_harmonic_group_characteristic ( p ) | |
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_P_HARMONIC ) | |
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_P_HARMONIC_UNSIGNED ) ;
const bool fallback_harmonic = ! standard_harmonic & & stat_text_has_harmonic_hint ( compact ) & & ! stat_text_has_interharmonic_hint ( compact ) ;
if ( ! standard_harmonic & & ! fallback_harmonic )
return StatMetricId : : Unknown ;
if ( stat_is_angle_like_point ( p ) )
return StatMetricId : : Unknown ;
int order = - 1 ;
if ( standard_harmonic )
order = stat_extract_order_from_standard_fields ( p ) ;
if ( order = = STAT_DYNAMIC_ORDER_SKIP_FUNDAMENTAL )
return StatMetricId : : Unknown ;
// 名称兜底场景下,仍然优先尝试 group/base/nominal/order_hint;
// 这样 P HARM A 这类“通道名不含具体次数,但通道序列成组排列”的文件也能识别 2-50 次。
if ( order < 2 | | order > 50 )
{
const int order_by_standard_or_hint = stat_extract_order_from_standard_fields ( p ) ;
if ( order_by_standard_or_hint = = STAT_DYNAMIC_ORDER_SKIP_FUNDAMENTAL )
return StatMetricId : : Unknown ;
if ( order_by_standard_or_hint > = 2 & & order_by_standard_or_hint < = 50 )
order = order_by_standard_or_hint ;
}
if ( order < 2 | | order > 50 )
order = stat_extract_harmonic_order_from_compact_text ( compact ) ;
if ( order < 2 | | order > 50 )
return StatMetricId : : Unknown ;
// 谐波功率:按 POWER + W/var/VA 识别,支持三相和总。
if ( p . quantity_measured_id = = ID_QM_POWER & & stat_unit_is_power_unit ( p . quantity_units_id ) )
{
const int phase = stat_extract_phase_or_total_index ( p ) ;
if ( phase < 0 )
return StatMetricId : : Unknown ;
matched_by_name_fallback = ! standard_harmonic ;
if ( p . quantity_units_id = = ID_QU_WATTS )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : HarmonicActivePower , phase , order ) ;
if ( p . quantity_units_id = = ID_QU_VARS )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : HarmonicReactivePower , phase , order ) ;
if ( p . quantity_units_id = = ID_QU_VA )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : HarmonicApparentPower , phase , order ) ;
}
// 谐波含有率:按 %/pu/无量纲 识别,和 V/A RMS 谐波分离。
if ( ( p . quantity_measured_id = = ID_QM_VOLTAGE | | p . quantity_measured_id = = ID_QM_CURRENT ) & &
stat_unit_is_ratio_like ( p . quantity_units_id ) )
{
matched_by_name_fallback = ! standard_harmonic ;
if ( p . quantity_measured_id = = ID_QM_CURRENT )
{
const int phase = stat_extract_phase_current_phase_index ( p ) ;
if ( phase > = 0 )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : CurrentHarmonicRatio , phase , order ) ;
}
else
{
int phase = stat_extract_voltage_harmonic_phase_index ( p ) ;
if ( phase > = 0 )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : VoltageHarmonicRatio , phase , order ) ;
phase = stat_extract_line_voltage_phase_index ( p ) ;
if ( phase > = 0 )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : LineVoltageHarmonicRatio , phase , order ) ;
}
}
return StatMetricId : : Unknown ;
}
StatMetricId stat_identify_dynamic_spectrum_metric ( const ExpandedStatPoint & p , bool & matched_by_name_fallback )
{
matched_by_name_fallback = false ;
if ( p . quantity_measured_id ! = ID_QM_VOLTAGE & & p . quantity_measured_id ! = ID_QM_CURRENT )
return StatMetricId : : Unknown ;
const std : : string compact = stat_compact_upper ( p . channel_name ) + stat_compact_upper ( p . quantity_name ) +
stat_compact_upper ( p . quantity_characteristic_id . symbolic_name ) + stat_compact_upper ( p . value_type_id . symbolic_name ) ;
const bool standard_harmonic = stat_is_harmonic_group_characteristic ( p ) ;
const bool standard_interharmonic = stat_is_interharmonic_group_characteristic ( p ) ;
const bool has_interharmonic_text = stat_text_has_interharmonic_hint ( compact ) ;
const bool fallback_interharmonic = ! standard_interharmonic & & has_interharmonic_text ;
const bool fallback_harmonic = ! standard_harmonic & & stat_text_has_harmonic_hint ( compact ) & & ! has_interharmonic_text ;
const bool is_angle = stat_is_angle_like_point ( p ) ;
const bool magnitude_unit_ok = stat_is_magnitude_unit_for_measured ( p . quantity_measured_id , p . quantity_units_id ) ;
const bool angle_unit_ok = is_angle | | p . quantity_units_id = = ID_QU_DEGREES ;
if ( ! is_angle & & ! magnitude_unit_ok )
return StatMetricId : : Unknown ;
if ( is_angle & & ! angle_unit_ok )
return StatMetricId : : Unknown ;
// 间谐波必须优先于普通谐波识别,避免 IHRMS/INTERHARMONIC 被 HRMS/HARMONIC 兜底误吃掉。
if ( ( standard_interharmonic | | fallback_interharmonic ) & & ! is_angle )
{
int slot = - 1 ;
if ( standard_interharmonic )
slot = stat_extract_interharmonic_slot_from_standard_fields ( p ) ;
if ( slot < 0 )
slot = stat_extract_interharmonic_slot_from_text ( compact ) ;
if ( slot < 0 | | slot > 49 )
return StatMetricId : : Unknown ;
if ( p . quantity_measured_id = = ID_QM_VOLTAGE )
{
int phase = stat_extract_voltage_harmonic_phase_index ( p ) ;
if ( phase > = 0 )
{
matched_by_name_fallback = ! standard_interharmonic ;
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : VoltageInterharmonic , phase , slot ) ;
}
phase = stat_extract_line_voltage_phase_index ( p ) ;
if ( phase > = 0 )
{
matched_by_name_fallback = ! standard_interharmonic ;
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : LineVoltageInterharmonic , phase , slot ) ;
}
}
else if ( p . quantity_measured_id = = ID_QM_CURRENT )
{
const int phase = stat_extract_phase_current_phase_index ( p ) ;
if ( phase > = 0 )
{
matched_by_name_fallback = ! standard_interharmonic ;
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : CurrentInterharmonic , phase , slot ) ;
}
}
}
if ( standard_harmonic | | fallback_harmonic )
{
int order = - 1 ;
if ( standard_harmonic )
order = stat_extract_order_from_standard_fields ( p ) ;
// group=1 明确是基波,必须跳过。不能继续通过 order_hint/name fallback 变成 2 次谐波。
if ( order = = STAT_DYNAMIC_ORDER_SKIP_FUNDAMENTAL )
return StatMetricId : : Unknown ;
if ( order < 2 | | order > 50 )
order = stat_extract_harmonic_order_from_compact_text ( compact ) ;
if ( order < 2 | | order > 50 )
return StatMetricId : : Unknown ;
if ( p . quantity_measured_id = = ID_QM_VOLTAGE )
{
int phase = stat_extract_voltage_harmonic_phase_index ( p ) ;
if ( phase > = 0 )
{
matched_by_name_fallback = ! standard_harmonic ;
return stat_dynamic_metric_id ( is_angle ? StatDynamicMetricGroup : : VoltageHarmonicAngle : StatDynamicMetricGroup : : VoltageHarmonic , phase , order ) ;
}
phase = stat_extract_line_voltage_phase_index ( p ) ;
if ( phase > = 0 )
{
matched_by_name_fallback = ! standard_harmonic ;
return stat_dynamic_metric_id ( is_angle ? StatDynamicMetricGroup : : LineVoltageHarmonicAngle : StatDynamicMetricGroup : : LineVoltageHarmonic , phase , order ) ;
}
}
else if ( p . quantity_measured_id = = ID_QM_CURRENT )
{
const int phase = stat_extract_phase_current_phase_index ( p ) ;
if ( phase > = 0 )
{
matched_by_name_fallback = ! standard_harmonic ;
return stat_dynamic_metric_id ( is_angle ? StatDynamicMetricGroup : : CurrentHarmonicAngle : StatDynamicMetricGroup : : CurrentHarmonic , phase , order ) ;
}
}
}
return StatMetricId : : Unknown ;
}
/// @brief 判断样本点是否为 C.2 表中的“频率”对象族。
/// @details
/// 根据附录 C 表 C.2:
/// Frequency -> ID_QM_VOLTAGE + ID_QC_FREQUENCY + ID_QU_HERTZ。
/// @param p 样本点。
/// @return 若命中 Frequency 对象族则返回 true。
bool stat_match_c2_frequency ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_VOLTAGE & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_FREQUENCY ) & &
p . quantity_units_id = = ID_QU_HERTZ ;
}
/// @brief 判断样本点是否为 C.2 表中的“电压有效值”对象族。
/// @details
/// 根据附录 C 表 C.2:
/// V RMS -> ID_QM_VOLTAGE + ID_QC_RMS + ID_QU_VOLTS。
/// @param p 样本点。
/// @return 若命中 VoltageRms 对象族则返回 true。
bool stat_match_c2_voltage_rms ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_VOLTAGE & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_RMS ) & &
p . quantity_units_id = = ID_QU_VOLTS ;
}
/// @brief 判断样本点是否为 C.2 表中的“电流有效值”对象族。
/// @details
/// 根据附录 C 表 C.2:
/// I RMS -> ID_QM_CURRENT + ID_QC_RMS + ID_QU_AMPS。
/// @param p 样本点。
/// @return 若命中 CurrentRms 对象族则返回 true。
bool stat_match_c2_current_rms ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_CURRENT & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_RMS ) & &
p . quantity_units_id = = ID_QU_AMPS ;
}
/// @brief 判断样本点是否为 C.2 表中的“电压偏差”对象族。
/// @details
/// 根据附录 C 表 C.2:
/// V RMS Deviation -> ID_QM_VOLTAGE + ID_QC_RMS + ID_QU_PERCENT。
/// 为兼容部分设备文件,也允许单位为 PU 作为辅助兼容。
/// @param p 样本点。
/// @return 若命中 VoltageDeviation 对象族则返回 true。
bool stat_match_c2_voltage_deviation ( const ExpandedStatPoint & p )
{
const bool unit_match =
p . quantity_units_id = = ID_QU_PERCENT | |
p . quantity_units_id = = ID_QU_PERUNIT ;
return p . quantity_measured_id = = ID_QM_VOLTAGE & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_RMS ) & &
unit_match ;
}
/// @brief 判断样本点是否为 C.2 表中的“电压零序分量”对象族。
/// @details Zero sequence component -> ID_QM_VOLTAGE + ID_QC_SZERO + ID_QU_VOLTS。
bool stat_match_c2_voltage_zero_sequence ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_VOLTAGE & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SZERO ) & &
p . quantity_units_id = = ID_QU_VOLTS ;
}
/// @brief 判断样本点是否为 C.2 表中的“电压负序分量”对象族。
/// @details Negative sequence component -> ID_QM_VOLTAGE + ID_QC_SNEG + ID_QU_VOLTS。
bool stat_match_c2_voltage_negative_sequence ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_VOLTAGE & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SNEG ) & &
p . quantity_units_id = = ID_QU_VOLTS ;
}
/// @brief 判断样本点是否为 C.2 表中的“电压正序分量”对象族。
/// @details Positive sequence component -> ID_QM_VOLTAGE + ID_QC_SPOS + ID_QU_VOLTS。
bool stat_match_c2_voltage_positive_sequence ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_VOLTAGE & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SPOS ) & &
p . quantity_units_id = = ID_QU_VOLTS ;
}
/// @brief 判断样本点是否为 C.2 表中的“电压负序不平衡”对象族。
/// @details Negative sequence component unbalance -> ID_QM_VOLTAGE + ID_QC_S2S1 + ID_QU_PERCENT/ID_QU_PERUNIT。
bool stat_match_c2_voltage_negative_sequence_unbalance ( const ExpandedStatPoint & p )
{
const bool unit_match =
p . quantity_units_id = = ID_QU_PERCENT | |
p . quantity_units_id = = ID_QU_PERUNIT ;
return p . quantity_measured_id = = ID_QM_VOLTAGE & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_S2S1 ) & &
unit_match ;
}
/// @brief 判断样本点是否为 C.2 表中的“电流零序分量”对象族。
/// @details Zero sequence component -> ID_QM_CURRENT + ID_QC_SZERO + ID_QU_AMPS。
bool stat_match_c2_current_zero_sequence ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_CURRENT & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SZERO ) & &
p . quantity_units_id = = ID_QU_AMPS ;
}
/// @brief 判断样本点是否为 C.2 表中的“电流负序分量”对象族。
/// @details Negative sequence component -> ID_QM_CURRENT + ID_QC_SNEG + ID_QU_AMPS。
bool stat_match_c2_current_negative_sequence ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_CURRENT & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SNEG ) & &
p . quantity_units_id = = ID_QU_AMPS ;
}
/// @brief 判断样本点是否为 C.2 表中的“电流正序分量”对象族。
/// @details Positive sequence component -> ID_QM_CURRENT + ID_QC_SPOS + ID_QU_AMPS。
bool stat_match_c2_current_positive_sequence ( const ExpandedStatPoint & p )
{
return p . quantity_measured_id = = ID_QM_CURRENT & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_SPOS ) & &
p . quantity_units_id = = ID_QU_AMPS ;
}
/// @brief 判断样本点是否为 C.2 表中的“电流负序不平衡”对象族。
/// @details Negative sequence component unbalance -> ID_QM_CURRENT + ID_QC_S2S1 + ID_QU_PERCENT/ID_QU_PERUNIT。
bool stat_match_c2_current_negative_sequence_unbalance ( const ExpandedStatPoint & p )
{
const bool unit_match =
p . quantity_units_id = = ID_QU_PERCENT | |
p . quantity_units_id = = ID_QU_PERUNIT ;
return p . quantity_measured_id = = ID_QM_CURRENT & &
stat_guid_equals ( p . quantity_characteristic_id . value , ID_QC_S2S1 ) & &
unit_match ;
}
/// @brief 判断样本点是否为“频率偏差”对象族。
/// @details
/// 频率偏差和频率在 C.2 表里共用同一组核心语义:
/// ID_QM_VOLTAGE + ID_QC_FREQUENCY + ID_QU_HERTZ, 差别主要体现在对象名称。
/// 因此这里采用“C.2 语义 + 名称辅助”的方式识别。
/// @param p 样本点。
/// @return 若命中 FrequencyDeviation 对象族则返回 true。
bool stat_match_c2_frequency_deviation ( const ExpandedStatPoint & p )
{
if ( ! stat_match_c2_frequency ( p ) )
return false ;
const std : : string compact_channel = stat_compact_upper ( p . channel_name ) ;
const std : : string compact_quantity = stat_compact_upper ( p . quantity_name ) ;
const std : : vector < std : : string > aliases = {
" FREQDEV " , " FREQUENCYDEV " , " DELTAF " , " FDEV " , " DEVIATION "
} ;
return stat_contains_any_alias ( compact_channel , aliases ) | |
stat_contains_any_alias ( compact_quantity , aliases ) ;
}
/// @brief 基于 C.2 表的核心语义识别对象族。
/// @param p 已展开样本点。
/// @return 命中的对象族。
StatFamily stat_identify_family_by_c2 ( const ExpandedStatPoint & p )
{
if ( stat_match_c2_frequency_deviation ( p ) )
return StatFamily : : FrequencyDeviation ;
if ( stat_match_c2_frequency ( p ) )
return StatFamily : : Frequency ;
if ( stat_match_c2_voltage_negative_sequence_unbalance ( p ) )
return StatFamily : : VoltageNegativeSequenceUnbalance ;
if ( stat_match_c2_voltage_zero_sequence ( p ) )
return StatFamily : : VoltageZeroSequence ;
if ( stat_match_c2_voltage_negative_sequence ( p ) )
return StatFamily : : VoltageNegativeSequence ;
if ( stat_match_c2_voltage_positive_sequence ( p ) )
return StatFamily : : VoltagePositiveSequence ;
if ( stat_match_c2_current_negative_sequence_unbalance ( p ) )
return StatFamily : : CurrentNegativeSequenceUnbalance ;
if ( stat_match_c2_current_zero_sequence ( p ) )
return StatFamily : : CurrentZeroSequence ;
if ( stat_match_c2_current_negative_sequence ( p ) )
return StatFamily : : CurrentNegativeSequence ;
if ( stat_match_c2_current_positive_sequence ( p ) )
return StatFamily : : CurrentPositiveSequence ;
if ( stat_match_c2_voltage_deviation ( p ) )
return StatFamily : : VoltageDeviation ;
if ( stat_match_c2_voltage_rms ( p ) )
return StatFamily : : VoltageRms ;
if ( stat_match_c2_current_rms ( p ) )
return StatFamily : : CurrentRms ;
return StatFamily : : Unknown ;
}
/// @brief 根据接线方式、相别与 C.2 对象族,把样本点映射成最终业务指标。
/// @param connection_kind 当前文件接线方式。
/// @param p 已展开样本点。
/// @param matched_by_name_fallback 返回是否通过名称辅助识别。
/// @return 识别出的业务指标 ID。
StatMetricId stat_identify_metric_id (
ParsedConnectionKind connection_kind ,
const ExpandedStatPoint & p ,
bool & matched_by_name_fallback )
{
matched_by_name_fallback = false ;
// 扩展动态指标入口:谐波功率、谐波含有率。
// 仍然保持“ID 组合优先,名称兜底”策略;名称只在标准字段不足时参与。
{
bool dynamic_name_fallback = false ;
const StatMetricId dynamic_metric = stat_identify_dynamic_extension_metric ( p , dynamic_name_fallback ) ;
if ( dynamic_metric ! = StatMetricId : : Unknown )
{
matched_by_name_fallback = dynamic_name_fallback ;
return dynamic_metric ;
}
}
// 谐波/间谐波类指标统一入口:
// 先按 C.2 标准 ID 组合识别 measured/characteristic/unit/value_type, 再从 group/base/nominal 取次数;
// 标准字段不足时才使用通道名兜底。覆盖电压/电流/线电压、幅值/相角、谐波/间谐波。
{
bool dynamic_name_fallback = false ;
const StatMetricId dynamic_metric = stat_identify_dynamic_spectrum_metric ( p , dynamic_name_fallback ) ;
if ( dynamic_metric ! = StatMetricId : : Unknown )
{
matched_by_name_fallback = dynamic_name_fallback ;
return dynamic_metric ;
}
}
// 扩展静态指标入口: 功率、功率因数、DVC、闪变、THD、基波 RMS/相角。
{
bool scalar_name_fallback = false ;
const StatMetricId scalar_metric = stat_identify_scalar_extension_metric ( p , scalar_name_fallback ) ;
if ( scalar_metric ! = StatMetricId : : Unknown )
{
matched_by_name_fallback = scalar_name_fallback ;
return scalar_metric ;
}
}
const StatFamily family = stat_identify_family_by_c2 ( p ) ;
switch ( family )
{
case StatFamily : : VoltageRms :
if ( p . phase_id = = ID_PHASE_AN ) return StatMetricId : : UaRms ;
if ( p . phase_id = = ID_PHASE_BN ) return StatMetricId : : UbRms ;
if ( p . phase_id = = ID_PHASE_CN ) return StatMetricId : : UcRms ;
if ( p . phase_id = = ID_PHASE_AB ) return StatMetricId : : UabRms ;
if ( p . phase_id = = ID_PHASE_BC ) return StatMetricId : : UbcRms ;
if ( p . phase_id = = ID_PHASE_CA ) return StatMetricId : : UcaRms ;
break ;
case StatFamily : : CurrentRms :
if ( p . phase_id = = ID_PHASE_AN ) return StatMetricId : : IaRms ;
if ( p . phase_id = = ID_PHASE_BN ) return StatMetricId : : IbRms ;
if ( p . phase_id = = ID_PHASE_CN ) return StatMetricId : : IcRms ;
break ;
case StatFamily : : VoltageDeviation :
if ( connection_kind = = ParsedConnectionKind : : Wye | | connection_kind = = ParsedConnectionKind : : Unknown )
{
if ( p . phase_id = = ID_PHASE_AN ) return StatMetricId : : UaDeviation ;
if ( p . phase_id = = ID_PHASE_BN ) return StatMetricId : : UbDeviation ;
if ( p . phase_id = = ID_PHASE_CN ) return StatMetricId : : UcDeviation ;
}
if ( connection_kind = = ParsedConnectionKind : : Delta | | connection_kind = = ParsedConnectionKind : : Unknown )
{
if ( p . phase_id = = ID_PHASE_AB ) return StatMetricId : : UabDeviation ;
if ( p . phase_id = = ID_PHASE_BC ) return StatMetricId : : UbcDeviation ;
if ( p . phase_id = = ID_PHASE_CA ) return StatMetricId : : UcaDeviation ;
}
break ;
case StatFamily : : VoltageZeroSequence :
return StatMetricId : : UZeroSeq ;
case StatFamily : : VoltageNegativeSequence :
return StatMetricId : : UNegSeq ;
case StatFamily : : VoltagePositiveSequence :
return StatMetricId : : UPosSeq ;
case StatFamily : : VoltageNegativeSequenceUnbalance :
return StatMetricId : : UNegSeqUnbalance ;
case StatFamily : : CurrentZeroSequence :
return StatMetricId : : IZeroSeq ;
case StatFamily : : CurrentNegativeSequence :
return StatMetricId : : INegSeq ;
case StatFamily : : CurrentPositiveSequence :
return StatMetricId : : IPosSeq ;
case StatFamily : : CurrentNegativeSequenceUnbalance :
return StatMetricId : : INegSeqUnbalance ;
case StatFamily : : Frequency :
return StatMetricId : : Frequency ;
case StatFamily : : FrequencyDeviation :
matched_by_name_fallback = true ;
return StatMetricId : : FrequencyDeviation ;
default :
break ;
}
// 部分厂家文件可能没有正确填写序分量的 QuantityCharacteristicID,
// 但通道名/量名会包含 Pos/Neg/Zero/Sequence/S2S1 等信息;
// 这里只作为补充兜底,优先级低于上面的 C.2 GUID 语义识别。
const std : : string compact_text =
stat_compact_upper ( p . channel_name ) + stat_compact_upper ( p . quantity_name ) ;
if ( p . quantity_measured_id = = ID_QM_VOLTAGE )
{
if ( stat_contains_any_alias ( compact_text , {
" S2S1 " , " NEGATIVESEQUENCEUNBALANCE " , " NEGSEQUNBALANCE " ,
" NEGATIVEUNBALANCE " , " NEGUNBALANCE " , " VOLTAGEUNBALANCE " ,
" VUNBALANCE " , " VOLTAGEIMBALANCE "
} ) )
{
matched_by_name_fallback = true ;
return StatMetricId : : UNegSeqUnbalance ;
}
if ( stat_contains_any_alias ( compact_text , {
" ZEROSEQUENCE " , " ZEROSEQ " , " ZEROSQ " , " SZERO " ,
" VZEROSEQ " , " UZEROSEQ " , " V0 " , " U0 "
} ) )
{
matched_by_name_fallback = true ;
return StatMetricId : : UZeroSeq ;
}
if ( stat_contains_any_alias ( compact_text , {
" NEGATIVESEQUENCE " , " NEGSEQ " , " NEGSQ " , " SNEG " ,
" VNEGSEQ " , " UNEGSEQ " , " V2 " , " U2 "
} ) )
{
matched_by_name_fallback = true ;
return StatMetricId : : UNegSeq ;
}
if ( stat_contains_any_alias ( compact_text , {
" POSITIVESEQUENCE " , " POSSEQ " , " POSSQ " , " SPOS " ,
" VPOSSEQ " , " UPOSSEQ " , " V1 " , " U1 "
} ) )
{
matched_by_name_fallback = true ;
return StatMetricId : : UPosSeq ;
}
}
// 电流序分量使用同一组序分量 characteristic, 但 QuantityMeasured 为 CURRENT;
// 部分厂家仍可能只在通道名里写 I SPos / I SNeg / I SZero / I S2S1, 因此也做名称兜底。
if ( p . quantity_measured_id = = ID_QM_CURRENT )
{
if ( stat_contains_any_alias ( compact_text , {
" IS2S1 " , " CURRENTS2S1 " , " CURRENTNEGATIVESEQUENCEUNBALANCE " ,
" CURRENTNEGSEQUNBALANCE " , " INEGSEQUNBALANCE " , " NEGSEQUNBALANCEI " ,
" CURRENTUNBALANCE " , " IUNBALANCE " , " CURRENTIMBALANCE " , " IIMBALANCE "
} ) )
{
matched_by_name_fallback = true ;
return StatMetricId : : INegSeqUnbalance ;
}
if ( stat_contains_any_alias ( compact_text , {
" CURRENTZEROSEQUENCE " , " CURRENTZEROSEQ " , " IZEROSEQUENCE " , " IZEROSEQ " ,
" ISZERO " , " IZEROSQ " , " I0 "
} ) )
{
matched_by_name_fallback = true ;
return StatMetricId : : IZeroSeq ;
}
if ( stat_contains_any_alias ( compact_text , {
" CURRENTNEGATIVESEQUENCE " , " CURRENTNEGSEQ " , " INEGATIVESEQUENCE " , " INEGSEQ " ,
" ISNEG " , " INEGSQ " , " I2 "
} ) )
{
matched_by_name_fallback = true ;
return StatMetricId : : INegSeq ;
}
if ( stat_contains_any_alias ( compact_text , {
" CURRENTPOSITIVESEQUENCE " , " CURRENTPOSSEQ " , " IPOSITIVESEQUENCE " , " IPOSSEQ " ,
" ISPOS " , " IPOSSQ " , " I1 "
} ) )
{
matched_by_name_fallback = true ;
return StatMetricId : : IPosSeq ;
}
}
return StatMetricId : : Unknown ;
}
struct StatMetricSourceKey
{
int observation_index = - 1 ;
int channel_instance_index = - 1 ;
bool operator < ( const StatMetricSourceKey & other ) const
{
if ( observation_index ! = other . observation_index )
return observation_index < other . observation_index ;
return channel_instance_index < other . channel_instance_index ;
}
bool operator = = ( const StatMetricSourceKey & other ) const
{
return observation_index = = other . observation_index & &
channel_instance_index = = other . channel_instance_index ;
}
} ;
struct StatMetricSourceStats
{
StatMetricId metric_id = StatMetricId : : Unknown ;
StatMetricSourceKey key ;
std : : string observation_name ;
std : : string channel_name ;
std : : string quantity_name ;
unsigned int phase_id = 0 ;
unsigned int quantity_measured_id = 0 ;
unsigned int quantity_units_id = 0 ;
PqdifGuidValue quantity_characteristic_id ;
int channel_def_index = - 1 ;
size_t point_count = 0 ;
size_t non_zero_count = 0 ;
double first_value = 0.0 ;
double min_value = 0.0 ;
double max_value = 0.0 ;
double sum_abs_value = 0.0 ;
bool has_first = false ;
bool has_min_kind = false ;
bool has_max_kind = false ;
bool has_avg_kind = false ;
bool has_p95_kind = false ;
void add ( const ExpandedStatPoint & p )
{
if ( ! has_first )
{
has_first = true ;
first_value = p . value ;
min_value = p . value ;
max_value = p . value ;
metric_id = p . metric_id ;
key . observation_index = p . observation_index ;
key . channel_instance_index = p . channel_instance_index ;
observation_name = p . observation_name ;
channel_name = p . channel_name ;
quantity_name = p . quantity_name ;
phase_id = p . phase_id ;
quantity_measured_id = p . quantity_measured_id ;
quantity_units_id = p . quantity_units_id ;
quantity_characteristic_id = p . quantity_characteristic_id ;
channel_def_index = p . channel_def_index ;
}
+ + point_count ;
if ( std : : fabs ( p . value ) > 1e-12 )
+ + non_zero_count ;
if ( p . value < min_value )
min_value = p . value ;
if ( p . value > max_value )
max_value = p . value ;
sum_abs_value + = std : : fabs ( p . value ) ;
switch ( p . stat_kind )
{
case StatValueKind : : Min : has_min_kind = true ; break ;
case StatValueKind : : Max : has_max_kind = true ; break ;
case StatValueKind : : Avg : has_avg_kind = true ; break ;
case StatValueKind : : P95 : has_p95_kind = true ; break ;
default : break ;
}
}
double avg_abs_value ( ) const
{
if ( point_count = = 0 )
return 0.0 ;
return sum_abs_value / static_cast < double > ( point_count ) ;
}
bool all_zero ( ) const
{
return point_count > 0 & & non_zero_count = = 0 ;
}
} ;
struct StatMetricQualityInfo
{
StatMetricQuality quality = StatMetricQuality : : Normal ;
std : : string reason ;
StatMetricSourceKey source_key ;
std : : string source_channel_name ;
} ;
StatMetricSourceKey stat_make_source_key ( const ExpandedStatPoint & p )
{
StatMetricSourceKey key ;
key . observation_index = p . observation_index ;
key . channel_instance_index = p . channel_instance_index ;
return key ;
}
bool stat_metric_has_alias ( StatMetricId metric_id , const std : : string & compact_channel )
{
if ( stat_is_voltage_harmonic_metric ( metric_id ) )
{
const int order = stat_voltage_harmonic_order ( metric_id ) ;
const int phase = stat_voltage_harmonic_phase_index ( metric_id ) ;
const std : : string n = std : : to_string ( order ) ;
const std : : string nn = stat_two_digit ( order ) ;
const char p = phase = = 0 ? ' A ' : ( phase = = 1 ? ' B ' : ' C ' ) ;
std : : string phase_text ( 1 , p ) ;
std : : vector < std : : string > aliases = {
" VHARM " + n + phase_text ,
" VHARM " + nn + phase_text ,
" VHARMONIC " + n + phase_text ,
" VHARMONIC " + nn + phase_text ,
" VHRMS " + n + phase_text ,
" VHRMS " + nn + phase_text ,
" UHARM " + n + phase_text ,
" UHARM " + nn + phase_text ,
" UHARMONIC " + n + phase_text ,
" UHARMONIC " + nn + phase_text ,
" UHRMS " + n + phase_text ,
" UHRMS " + nn + phase_text ,
" H " + n + phase_text ,
" H " + nn + phase_text ,
" HARM " + n + phase_text ,
" HARM " + nn + phase_text ,
" HARMONIC " + n + phase_text ,
" HARMONIC " + nn + phase_text
} ;
return stat_contains_any_alias ( compact_channel , aliases ) ;
}
std : : vector < std : : string > aliases ;
switch ( metric_id )
{
case StatMetricId : : UaRms :
aliases = { " VRMSA " , " VARMS " , " VAN " , " UA " , " URMSA " } ;
break ;
case StatMetricId : : UbRms :
aliases = { " VRMSB " , " VBRMS " , " VBN " , " UB " , " URMSB " } ;
break ;
case StatMetricId : : UcRms :
aliases = { " VRMSC " , " VCRMS " , " VCN " , " UC " , " URMSC " } ;
break ;
case StatMetricId : : IaRms :
aliases = { " IRMSA " , " IARMS " , " IA " } ;
break ;
case StatMetricId : : IbRms :
aliases = { " IRMSB " , " IBRMS " , " IB " } ;
break ;
case StatMetricId : : IcRms :
aliases = { " IRMSC " , " ICRMS " , " IC " } ;
break ;
case StatMetricId : : UabRms :
aliases = { " VRMSAB " , " VABRMS " , " VAB " , " UAB " , " URMSAB " } ;
break ;
case StatMetricId : : UbcRms :
aliases = { " VRMSBC " , " VBCRMS " , " VBC " , " UBC " , " URMSBC " } ;
break ;
case StatMetricId : : UcaRms :
aliases = { " VRMSCA " , " VCARMS " , " VCA " , " UCA " , " URMSCA " } ;
break ;
case StatMetricId : : UaDeviation :
aliases = { " UADEVIATION " , " VADEVIATION " , " VDEVA " , " DEVA " } ;
break ;
case StatMetricId : : UbDeviation :
aliases = { " UBDEVIATION " , " VBDEVIATION " , " VDEVB " , " DEVB " } ;
break ;
case StatMetricId : : UcDeviation :
aliases = { " UCDEVIATION " , " VCDEVIATION " , " VDEVC " , " DEVC " } ;
break ;
case StatMetricId : : UabDeviation :
aliases = { " UABDEVIATION " , " VABDEVIATION " , " VDEVAB " , " DEVAB " } ;
break ;
case StatMetricId : : UbcDeviation :
aliases = { " UBCDEVIATION " , " VBCDEVIATION " , " VDEVBC " , " DEVBC " } ;
break ;
case StatMetricId : : UcaDeviation :
aliases = { " UCADEVIATION " , " VCADEVIATION " , " VDEVCA " , " DEVCA " } ;
break ;
case StatMetricId : : Frequency :
aliases = { " FREQUENCY " , " FREQ " , " HZ " } ;
break ;
case StatMetricId : : FrequencyDeviation :
aliases = { " FREQDEV " , " FREQUENCYDEV " , " DELTAF " , " FDEV " } ;
break ;
case StatMetricId : : UZeroSeq :
aliases = { " ZEROSEQUENCE " , " ZEROSEQ " , " ZEROSQ " , " SZERO " , " VZEROSEQ " , " UZEROSEQ " , " V0 " , " U0 " } ;
break ;
case StatMetricId : : UNegSeq :
aliases = { " NEGATIVESEQUENCE " , " NEGSEQ " , " NEGSQ " , " SNEG " , " VNEGSEQ " , " UNEGSEQ " , " V2 " , " U2 " } ;
break ;
case StatMetricId : : UPosSeq :
aliases = { " POSITIVESEQUENCE " , " POSSEQ " , " POSSQ " , " SPOS " , " VPOSSEQ " , " UPOSSEQ " , " V1 " , " U1 " } ;
break ;
case StatMetricId : : UNegSeqUnbalance :
aliases = { " S2S1 " , " NEGATIVESEQUENCEUNBALANCE " , " NEGSEQUNBALANCE " , " NEGATIVEUNBALANCE " , " NEGUNBALANCE " , " VOLTAGEUNBALANCE " , " VUNBALANCE " , " VOLTAGEIMBALANCE " } ;
break ;
case StatMetricId : : IZeroSeq :
aliases = { " CURRENTZEROSEQUENCE " , " CURRENTZEROSEQ " , " IZEROSEQUENCE " , " IZEROSEQ " , " ISZERO " , " IZEROSQ " , " I0 " } ;
break ;
case StatMetricId : : INegSeq :
aliases = { " CURRENTNEGATIVESEQUENCE " , " CURRENTNEGSEQ " , " INEGATIVESEQUENCE " , " INEGSEQ " , " ISNEG " , " INEGSQ " , " I2 " } ;
break ;
case StatMetricId : : IPosSeq :
aliases = { " CURRENTPOSITIVESEQUENCE " , " CURRENTPOSSEQ " , " IPOSITIVESEQUENCE " , " IPOSSEQ " , " ISPOS " , " IPOSSQ " , " I1 " } ;
break ;
case StatMetricId : : INegSeqUnbalance :
aliases = { " IS2S1 " , " CURRENTS2S1 " , " CURRENTNEGATIVESEQUENCEUNBALANCE " , " CURRENTNEGSEQUNBALANCE " , " INEGSEQUNBALANCE " , " NEGSEQUNBALANCEI " , " CURRENTUNBALANCE " , " IUNBALANCE " , " CURRENTIMBALANCE " , " IIMBALANCE " } ;
break ;
default :
break ;
}
return stat_contains_any_alias ( compact_channel , aliases ) ;
}
int stat_metric_source_score ( const StatMetricSourceStats & s )
{
int score = 0 ;
const std : : string compact_channel = stat_compact_upper ( s . channel_name ) ;
const std : : string compact_quantity = stat_compact_upper ( s . quantity_name ) ;
if ( stat_metric_has_alias ( s . metric_id , compact_channel ) )
score + = 300 ;
if ( stat_metric_has_alias ( s . metric_id , compact_quantity ) )
score + = 80 ;
if ( s . has_min_kind ) score + = 10 ;
if ( s . has_max_kind ) score + = 10 ;
if ( s . has_avg_kind ) score + = 10 ;
if ( s . has_p95_kind ) score + = 10 ;
// 统计点数越完整越优先,但不要让点数压过名称/语义。
if ( s . point_count > 0 )
score + = static_cast < int > ( std : : min < size_t > ( s . point_count / 100 , 50 ) ) ;
// 电压/频率一般不应全为 0; 但电流、零序/负序分量、负序不平衡为 0 在业务上可能是有效状态,
// 所以这些指标不在来源择优阶段扣分,只在质量状态中显式标记。
if ( s . all_zero ( ) & & ! stat_allow_all_zero_without_score_penalty ( s . metric_id ) )
score - = 200 ;
// 线电压 RMS 若是毫伏级,极大概率不是线电压有效值,但仍保留为候选并标记质量,
// 这里仅降低其优先级,避免有更可信来源时被选中。
if ( stat_is_line_voltage_metric ( s . metric_id ) & & s . avg_abs_value ( ) < 1.0 )
score - = 500 ;
// 频率值通常应在合理范围;异常值降低优先级。
if ( stat_is_frequency_metric ( s . metric_id ) )
{
const double avg_abs = s . avg_abs_value ( ) ;
if ( avg_abs > 0.0 & & ( avg_abs < 1.0 | | avg_abs > 1000.0 ) )
score - = 200 ;
}
return score ;
}
void stat_print_stream_stats_line (
const char * prefix ,
const StatMetricSourceStats & s ,
int score ,
const char * extra_text )
{
std : : cout < < prefix
< < " metric= " < < stat_metric_name ( s . metric_id )
< < " , ch= " < < s . key . channel_instance_index
< < " , ch_def= " < < s . channel_def_index
< < " , channel= " < < s . channel_name
< < " , phase= " < < pqdif_sem : : FindPhaseName ( s . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( s . quantity_measured_id )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( s . quantity_units_id )
< < " , characteristic= " < < short_guid_name ( s . quantity_characteristic_id )
< < " , points= " < < s . point_count
< < " , first= " < < s . first_value
< < " , min= " < < s . min_value
< < " , max= " < < s . max_value
< < " , avg_abs= " < < s . avg_abs_value ( )
< < " , kinds= "
< < ( s . has_min_kind ? " Min " : " - " ) < < " / "
< < ( s . has_max_kind ? " Max " : " - " ) < < " / "
< < ( s . has_avg_kind ? " Avg " : " - " ) < < " / "
< < ( s . has_p95_kind ? " P95 " : " - " )
< < " , score= " < < score ;
if ( extra_text ! = nullptr & & extra_text [ 0 ] ! = ' \0 ' )
std : : cout < < " , " < < extra_text ;
std : : cout < < std : : endl ;
}
/// @brief 对同一 metric 的多来源候选流进行择优,避免进入聚合层后静默覆盖。
/// @details
/// 保留同一 metric 最可信的一个通道来源;详细模式下打印所有候选来源。
/// 核心模式只打印汇总和必要的重复来源提示,避免谐波指标造成海量日志。
std : : vector < ExpandedStatPoint > stat_select_best_metric_sources (
const std : : vector < ExpandedStatPoint > & points )
{
typedef std : : map < StatMetricSourceKey , StatMetricSourceStats > SourceMap ;
std : : map < StatMetricId , SourceMap > by_metric ;
for ( const auto & p : points )
{
StatMetricSourceKey key = stat_make_source_key ( p ) ;
StatMetricSourceStats & stats = by_metric [ p . metric_id ] [ key ] ;
stats . add ( p ) ;
}
std : : map < StatMetricId , StatMetricSourceKey > selected_source ;
const bool detail_log = pqdif_is_detail_log_enabled ( ) ;
size_t selected_metric_count = 0 ;
size_t selected_voltage_harmonic_count = 0 ;
size_t selected_dynamic_metric_count = 0 ;
size_t duplicate_metric_count = 0 ;
std : : cout < < " ========== STAT STREAM SOURCE SUMMARY ========== " < < std : : endl ;
std : : cout < < " candidate_metric_count= " < < by_metric . size ( )
< < " , candidate_point_count= " < < points . size ( )
< < " , detail_log= " < < ( detail_log ? " on " : " off " )
< < std : : endl ;
for ( auto & metric_pair : by_metric )
{
const StatMetricId metric_id = metric_pair . first ;
SourceMap & sources = metric_pair . second ;
bool has_selected = false ;
StatMetricSourceKey best_key ;
int best_score = std : : numeric_limits < int > : : min ( ) ;
for ( auto & src_pair : sources )
{
const int score = stat_metric_source_score ( src_pair . second ) ;
if ( ! has_selected | | score > best_score )
{
has_selected = true ;
best_score = score ;
best_key = src_pair . first ;
}
}
if ( has_selected )
{
selected_source [ metric_id ] = best_key ;
+ + selected_metric_count ;
if ( stat_is_voltage_harmonic_metric ( metric_id ) )
+ + selected_voltage_harmonic_count ;
if ( stat_is_dynamic_metric ( metric_id ) )
+ + selected_dynamic_metric_count ;
}
if ( sources . size ( ) > 1 )
{
+ + duplicate_metric_count ;
if ( pqdif_log_enabled ( PqdifLogLevel : : Info ) )
{
std : : cout < < " [DUPLICATE METRIC SOURCES] metric= "
< < stat_metric_name ( metric_id )
< < " , source_count= " < < sources . size ( )
< < " , dynamic= " < < ( stat_is_dynamic_metric ( metric_id ) ? " true " : " false " )
< < " , action=select_best_source_and_drop_others "
< < std : : endl ;
}
}
// 只有 Debug/Trace 才展开每个 selected/dropped source。Core/Info 保持短日志,
// 避免一个谱类指标重复 50 个来源时刷屏。
if ( detail_log )
{
for ( auto & src_pair : sources )
{
const int score = stat_metric_source_score ( src_pair . second ) ;
const bool selected = has_selected & & ( src_pair . first = = best_key ) ;
stat_print_stream_stats_line (
selected ? " [STREAM SELECTED] " : " [STREAM DROPPED] " ,
src_pair . second ,
score ,
selected ? " selected=true " : " reason=duplicate_or_lower_score " ) ;
}
}
}
std : : cout < < " [STAT STREAM CORE] selected_metric_count= " < < selected_metric_count
< < " , selected_dynamic_metrics= " < < selected_dynamic_metric_count < < " / " < < stat_all_dynamic_metric_order ( ) . size ( )
< < " , selected_voltage_harmonics= " < < selected_voltage_harmonic_count < < " /147 "
< < " , duplicate_metric_count= " < < duplicate_metric_count
< < std : : endl ;
std : : cout < < " ================================================ " < < std : : endl ;
std : : vector < ExpandedStatPoint > out ;
out . reserve ( points . size ( ) ) ;
for ( const auto & p : points )
{
const auto it = selected_source . find ( p . metric_id ) ;
if ( it = = selected_source . end ( ) )
continue ;
if ( stat_make_source_key ( p ) = = it - > second )
out . push_back ( p ) ;
}
return out ;
}
std : : map < StatMetricId , StatMetricSourceStats > stat_collect_metric_stats (
const std : : vector < ExpandedStatPoint > & points )
{
std : : map < StatMetricId , StatMetricSourceStats > out ;
for ( const auto & p : points )
{
StatMetricSourceStats & stats = out [ p . metric_id ] ;
stats . add ( p ) ;
}
return out ;
}
std : : map < StatMetricId , StatMetricQualityInfo > stat_analyze_metric_quality (
const std : : vector < ExpandedStatPoint > & points )
{
std : : map < StatMetricId , StatMetricQualityInfo > out ;
std : : map < StatMetricId , StatMetricSourceStats > stats_by_metric = stat_collect_metric_stats ( points ) ;
double phase_voltage_avg_sum = 0.0 ;
int phase_voltage_avg_count = 0 ;
const StatMetricId phase_metrics [ 3 ] = {
StatMetricId : : UaRms ,
StatMetricId : : UbRms ,
StatMetricId : : UcRms
} ;
for ( int i = 0 ; i < 3 ; + + i )
{
const auto it = stats_by_metric . find ( phase_metrics [ i ] ) ;
if ( it ! = stats_by_metric . end ( ) & & it - > second . avg_abs_value ( ) > 1.0 )
{
phase_voltage_avg_sum + = it - > second . avg_abs_value ( ) ;
+ + phase_voltage_avg_count ;
}
}
const double phase_voltage_avg =
phase_voltage_avg_count > 0 ? phase_voltage_avg_sum / static_cast < double > ( phase_voltage_avg_count ) : 0.0 ;
for ( const auto & metric_pair : stats_by_metric )
{
const StatMetricId metric_id = metric_pair . first ;
const StatMetricSourceStats & s = metric_pair . second ;
StatMetricQualityInfo qi ;
qi . source_key = s . key ;
qi . source_channel_name = s . channel_name ;
qi . quality = StatMetricQuality : : Normal ;
qi . reason = " ok " ;
if ( s . all_zero ( ) )
{
qi . quality = StatMetricQuality : : AllZero ;
qi . reason = " all selected values are zero " ;
}
else if ( stat_is_phase_voltage_metric ( metric_id ) & & s . avg_abs_value ( ) < 1.0 )
{
qi . quality = StatMetricQuality : : SuspiciousRange ;
qi . reason = " phase voltage RMS avg_abs < 1V " ;
}
else if ( stat_is_line_voltage_metric ( metric_id ) )
{
const double line_avg = s . avg_abs_value ( ) ;
bool suspicious = false ;
std : : ostringstream reason ;
if ( line_avg < 1.0 )
{
suspicious = true ;
reason < < " line voltage RMS avg_abs < 1V " ;
}
if ( phase_voltage_avg > 1.0 )
{
const double ratio = line_avg / phase_voltage_avg ;
if ( ratio < 1.25 | | ratio > 2.20 )
{
if ( suspicious )
reason < < " ; " ;
suspicious = true ;
reason < < " line/phase ratio= " < < ratio
< < " outside expected Wye range [1.25,2.20] " ;
}
}
if ( suspicious )
{
qi . quality = StatMetricQuality : : SuspiciousRange ;
qi . reason = reason . str ( ) ;
}
}
else if ( stat_is_dynamic_metric ( metric_id ) )
{
// 谐波/间谐波/相角类指标可以为 0 或很小;这里只标记明显量级异常。
const double avg_abs = s . avg_abs_value ( ) ;
if ( avg_abs > 1000000.0 )
{
qi . quality = StatMetricQuality : : SuspiciousRange ;
qi . reason = " dynamic spectrum metric avg_abs > 1e6 " ;
}
}
else if ( stat_is_voltage_sequence_component_metric ( metric_id ) )
{
// 序分量可以很小,尤其是零序/负序;这里只对明显不合理的大幅值做提示,
// 不使用 <1V 判定,避免把健康系统的低负序/低零序误标为异常。
const double avg_abs = s . avg_abs_value ( ) ;
if ( avg_abs > 1000000.0 )
{
qi . quality = StatMetricQuality : : SuspiciousRange ;
qi . reason = " voltage sequence component avg_abs > 1e6 V " ;
}
}
else if ( stat_is_voltage_sequence_unbalance_metric ( metric_id ) )
{
const double avg_abs = s . avg_abs_value ( ) ;
if ( avg_abs > 1000.0 )
{
qi . quality = StatMetricQuality : : SuspiciousRange ;
qi . reason = " voltage negative sequence unbalance avg_abs > 1000 " ;
}
}
else if ( stat_is_current_sequence_component_metric ( metric_id ) )
{
// 电流序分量可以为 0 或很小;这里只对明显异常的大幅值做提示。
const double avg_abs = s . avg_abs_value ( ) ;
if ( avg_abs > 1000000.0 )
{
qi . quality = StatMetricQuality : : SuspiciousRange ;
qi . reason = " current sequence component avg_abs > 1e6 A " ;
}
}
else if ( stat_is_current_sequence_unbalance_metric ( metric_id ) )
{
const double avg_abs = s . avg_abs_value ( ) ;
if ( avg_abs > 1000.0 )
{
qi . quality = StatMetricQuality : : SuspiciousRange ;
qi . reason = " current negative sequence unbalance avg_abs > 1000 " ;
}
}
else if ( metric_id = = StatMetricId : : Frequency )
{
const double avg_abs = s . avg_abs_value ( ) ;
if ( avg_abs < 1.0 | | avg_abs > 1000.0 )
{
qi . quality = StatMetricQuality : : SuspiciousRange ;
qi . reason = " frequency avg_abs outside [1,1000] Hz " ;
}
}
out [ metric_id ] = qi ;
}
return out ;
}
const StatMetricQualityInfo * stat_find_quality_info (
const std : : map < StatMetricId , StatMetricQualityInfo > & quality_by_metric ,
StatMetricId metric_id )
{
const auto it = quality_by_metric . find ( metric_id ) ;
if ( it = = quality_by_metric . end ( ) )
return nullptr ;
return & it - > second ;
}
/// @brief 将一个 observation 中的单个通道实例展开为若干统计样本点。
bool stat_channel_has_explicit_harmonic_group_id ( const PqdifChannelInstance & ch )
{
return ch . channel_group_id > = 2 & & ch . channel_group_id < = 50 ;
}
bool stat_same_spectrum_channel_family (
const PqdifChannelInstance & a ,
const PqdifChannelInstance & b )
{
if ( a . channel_def_index ! = b . channel_def_index )
return false ;
if ( a . phase_id ! = b . phase_id )
return false ;
if ( a . quantity_measured_id ! = b . quantity_measured_id )
return false ;
return stat_compact_upper ( a . channel_name ) = = stat_compact_upper ( b . channel_name ) ;
}
int stat_make_channel_spectrum_order_hint (
const PqdifObservationRecord & obs ,
const PqdifChannelInstance & ch ,
int & block_offset ,
int & block_size )
{
block_offset = - 1 ;
block_size = 0 ;
// 已经有明确 group=2..50 时,必须优先走标准字段,不使用顺序推导。
if ( stat_channel_has_explicit_harmonic_group_id ( ch ) )
return - 1 ;
int offset = 0 ;
for ( const auto & other : obs . channel_instances )
{
if ( ! stat_same_spectrum_channel_family ( other , ch ) )
continue ;
// 只在“同一通道族内 group/base/nominal 缺失”的谱线中按顺序补次数。
// 这样不会把已经有标准 group 的通道也混入顺序计算。
if ( stat_channel_has_explicit_harmonic_group_id ( other ) )
continue ;
if ( other . channel_instance_index = = ch . channel_instance_index )
block_offset = offset ;
+ + offset ;
+ + block_size ;
}
if ( block_offset < 0 )
return - 1 ;
// 2-50 次谐波通常有 49 条谱线;个别厂家会额外带一个总量或占位,
// 所以允许 >=49, 但只映射前 49 条为 2..50。
if ( block_size < 49 )
return - 1 ;
const int order = block_offset + 2 ;
return ( order > = 2 & & order < = 50 ) ? order : - 1 ;
}
/// @details
/// 处理内容:
/// 1) 自动解析共享时间轴;
/// 2) 自动完成工程值还原;
/// 3) 依据 C.2 表语义 + 相别 + 值类型 识别业务指标。
/// @param lf 完整 PQDIF 逻辑对象。
/// @param connection_kind 当前文件接线方式。
/// @param obs 当前 observation。
/// @param ch 当前通道实例。
/// @return 当前通道展开出的统计样本点集合。
std : : vector < ExpandedStatPoint > stat_expand_channel_points (
const PqdifLogicalFile & lf ,
ParsedConnectionKind connection_kind ,
const PqdifObservationRecord & obs ,
const PqdifChannelInstance & ch )
{
std : : vector < ExpandedStatPoint > out ;
if ( ! pqdif_sem : : IsQuantityTypeValueLog ( ch . quantity_type_id . value ) )
return out ;
const PqdifDataSourceRecord * ds = stat_find_related_data_source ( lf , obs ) ;
const PqdifChannelDefinition * ch_def = stat_find_channel_definition ( ds , ch . channel_def_index ) ;
const PqdifSeriesInstance * time_series = nullptr ;
for ( const auto & si : ch . series_instances )
{
if ( pqdif_sem : : IsValueTypeTime ( si . value_type_id . value ) )
{
time_series = stat_resolve_shared_series ( obs , si ) ;
break ;
}
}
// Pst/Plt/DVC 等窗口统计量在部分设备中只有“一个值序列”,甚至不写独立时间轴。
// 这里不再因为缺失时间轴直接丢弃整个通道;后面仅允许可识别的标量统计指标使用
// observation 的 start/create time 作为兜底时间,避免 Plt 通道存在但被静默忽略。
const bool has_usable_time_series = ( time_series ! = nullptr & & time_series - > values . count > 0 ) ;
std : : set < StatValueKind > explicit_stat_value_kinds ;
for ( const auto & stat_probe_si : ch . series_instances )
{
const PqdifSeriesDefinition * stat_probe_sd = stat_find_series_definition ( ch_def , stat_probe_si . series_def_index ) ;
const StatValueKind probe_kind = stat_identify_value_kind ( stat_probe_si , stat_probe_sd ) ;
if ( probe_kind ! = StatValueKind : : Unknown )
explicit_stat_value_kinds . insert ( probe_kind ) ;
}
const bool channel_has_single_or_no_stat_value_kind = explicit_stat_value_kinds . size ( ) < = 1 ;
for ( size_t i = 0 ; i < ch . series_instances . size ( ) ; + + i )
{
const auto & si = ch . series_instances [ i ] ;
const PqdifSeriesDefinition * sd = stat_find_series_definition ( ch_def , si . series_def_index ) ;
const StatValueKind stat_kind = stat_identify_value_kind ( si , sd ) ;
const PqdifSeriesInstance * resolved_value_series = stat_resolve_shared_series ( obs , si ) ;
if ( resolved_value_series = = nullptr | | resolved_value_series - > values . count < = 0 )
continue ;
const unsigned int storage_method_id = ( sd ! = nullptr ) ? sd - > storage_method_id : 0 ;
const size_t value_count = static_cast < size_t > ( std : : max < long > ( resolved_value_series - > values . count , 0 ) ) ;
const size_t time_count = has_usable_time_series ?
static_cast < size_t > ( std : : max < long > ( time_series - > values . count , 0 ) ) : value_count ;
const size_t point_count = std : : min ( time_count , value_count ) ;
for ( size_t k = 0 ; k < point_count ; + + k )
{
time_t ts = 0 ;
if ( has_usable_time_series )
{
if ( ! stat_resolve_timestamp_at ( obs , * time_series , k , ts ) )
continue ;
}
else
{
// 单值统计通道无时间轴时,使用 observation 起始时间兜底;没有起始时间则用创建时间。
ts = ( obs . time_start . unix_time ! = 0 ) ? obs . time_start . unix_time : obs . time_create . unix_time ;
if ( ts = = 0 )
ts = std : : time ( nullptr ) ;
if ( k > 0 )
ts + = static_cast < time_t > ( k ) ;
}
double raw_value = 0.0 ;
if ( ! stat_try_get_raw_numeric_at ( resolved_value_series - > values , k , raw_value ) )
continue ;
ExpandedStatPoint p ;
p . timestamp = ts ;
p . timestamp_text = format_time_text ( ts ) ;
p . observation_index = obs . observation_index ;
p . channel_instance_index = ch . channel_instance_index ;
p . channel_def_index = ch . channel_def_index ;
p . channel_group_id = ch . channel_group_id ;
p . channel_spectrum_order_hint = stat_make_channel_spectrum_order_hint (
obs ,
ch ,
p . channel_spectrum_block_offset ,
p . channel_spectrum_block_size ) ;
p . series_instance_index = static_cast < int > ( i ) ;
p . series_def_index = si . series_def_index ;
p . sample_index = static_cast < int > ( k ) ;
p . observation_name = obs . observation_name ;
p . channel_name = ch . channel_name ;
p . quantity_name = ( ch_def ! = nullptr ) ? ch_def - > quantity_name : std : : string ( ) ;
p . phase_id = ch . phase_id ;
p . quantity_type_id = ch . quantity_type_id ;
p . quantity_measured_id = ch . quantity_measured_id ;
p . quantity_units_id = si . quantity_units_id ;
p . quantity_characteristic_id = si . quantity_characteristic_id ;
p . value_type_id = si . value_type_id ;
p . prob_percentile = ( sd ! = nullptr ) ? sd - > prob_percentile : 0.0 ;
p . series_base_quantity = si . series_base_quantity ;
p . nominal_quantity = si . nominal_quantity ;
p . connection_kind = connection_kind ;
p . stat_kind = stat_kind ;
p . value = stat_decode_engineering_value (
raw_value ,
* resolved_value_series ,
storage_method_id ,
resolved_value_series - > values . physical_type ) ;
p . metric_id = stat_identify_metric_id ( connection_kind , p , p . matched_by_name_fallback ) ;
if ( p . metric_id = = StatMetricId : : Unknown )
continue ;
const bool scalar_stat_metric =
stat_metric_can_use_scalar_series_as_all_kinds ( p . metric_id ) & &
stat_series_can_be_scalar_stat_value ( si , sd ) ;
// 没有时间轴的通道只允许 Pst/Plt/DVC 这类窗口标量指标走兜底,
// 防止其它普通统计通道在无时间轴时被错误展开。
if ( ! has_usable_time_series & & ! scalar_stat_metric )
continue ;
// 修复 Pst/Plt: 很多设备把 Pst/Plt 写成单个 Avg/Value 序列,而不是四条
// Min/Max/Avg/P95 序列。只要该通道只有单个统计序列或无明确统计值类型,就用同一物理值补齐四种
// 统计值,确保最终 bucket 与 METRIC STATUS 都能看到完整 max/min/avg/p95。
if ( scalar_stat_metric & & channel_has_single_or_no_stat_value_kind )
{
const StatValueKind fallback_kinds [ ] = {
StatValueKind : : Min ,
StatValueKind : : Max ,
StatValueKind : : Avg ,
StatValueKind : : P95
} ;
for ( const StatValueKind fallback_kind : fallback_kinds )
{
ExpandedStatPoint q = p ;
q . stat_kind = fallback_kind ;
out . push_back ( std : : move ( q ) ) ;
}
continue ;
}
if ( stat_kind ! = StatValueKind : : Unknown )
{
out . push_back ( std : : move ( p ) ) ;
continue ;
}
}
}
return out ;
}
bool stat_metric_in_primary_targets ( StatMetricId id )
{
const std : : vector < StatMetricId > & targets = stat_primary_metric_print_order ( ) ;
return std : : find ( targets . begin ( ) , targets . end ( ) , id ) ! = targets . end ( ) ;
}
/// @brief 从全部 observation 中补齐主 observation 缺失的目标指标。
/// @details
/// 统一 fallback 策略:所有指标都先从主统计 observation 解析;如果某个目标指标在主
/// observation 中完全找不到,则遍历其他 observations, 直到找到该指标的数据来源。
/// 这样后续新增谐波、间谐波、电流谐波等指标时,不需要为每类指标写一套独立的
/// observation 查找流程。
std : : vector < ExpandedStatPoint > stat_expand_missing_metrics_from_all_observations (
const PqdifLogicalFile & lf ,
ParsedConnectionKind connection_kind ,
const std : : vector < ExpandedStatPoint > & primary_points ,
int selected_observation_index )
{
std : : vector < ExpandedStatPoint > out ;
std : : set < StatMetricId > present_metrics ;
for ( const auto & p : primary_points )
{
if ( p . metric_id ! = StatMetricId : : Unknown )
present_metrics . insert ( p . metric_id ) ;
}
std : : set < StatMetricId > missing_targets ;
for ( const auto metric_id : stat_primary_metric_print_order ( ) )
{
if ( present_metrics . find ( metric_id ) = = present_metrics . end ( ) )
missing_targets . insert ( metric_id ) ;
}
if ( missing_targets . empty ( ) )
return out ;
std : : map < StatMetricId , std : : vector < ExpandedStatPoint > > fallback_by_metric ;
for ( const auto & obs : lf . observations )
{
if ( obs . observation_index = = selected_observation_index )
continue ;
for ( const auto & ch : obs . channel_instances )
{
auto points = stat_expand_channel_points ( lf , connection_kind , obs , ch ) ;
for ( auto & p : points )
{
if ( missing_targets . find ( p . metric_id ) = = missing_targets . end ( ) )
continue ;
fallback_by_metric [ p . metric_id ] . push_back ( std : : move ( p ) ) ;
}
}
// 已经为所有缺失指标找到了候选点,可以停止继续扫后续 observation。
bool all_found = true ;
for ( const auto metric_id : missing_targets )
{
if ( fallback_by_metric . find ( metric_id ) = = fallback_by_metric . end ( ) )
{
all_found = false ;
break ;
}
}
if ( all_found )
break ;
}
for ( const auto metric_id : stat_primary_metric_print_order ( ) )
{
const auto it = fallback_by_metric . find ( metric_id ) ;
if ( it = = fallback_by_metric . end ( ) )
continue ;
out . insert ( out . end ( ) ,
std : : make_move_iterator ( it - > second . begin ( ) ) ,
std : : make_move_iterator ( it - > second . end ( ) ) ) ;
}
if ( ! out . empty ( ) & & pqdif_log_enabled ( PqdifLogLevel : : Info ) )
{
size_t dynamic_metrics = 0 ;
size_t core_metrics = 0 ;
std : : set < StatMetricId > loaded_metrics ;
for ( const auto & p : out )
loaded_metrics . insert ( p . metric_id ) ;
for ( const auto metric_id : loaded_metrics )
{
if ( stat_is_dynamic_metric ( metric_id ) )
+ + dynamic_metrics ;
else
+ + core_metrics ;
}
std : : cout < < " [PQDIF] observation fallback loaded "
< < out . size ( ) < < " points for " < < loaded_metrics . size ( )
< < " missing metrics "
< < " (core= " < < core_metrics
< < " , dynamic_spectrum= " < < dynamic_metrics < < " ) "
< < std : : endl ;
}
else if ( out . empty ( ) & & pqdif_log_enabled ( PqdifLogLevel : : Debug ) )
{
std : : cout < < " [PQDIF] observation fallback: no missing target metric found in other observations "
< < std : : endl ;
}
return out ;
}
bool stat_has_any_voltage_harmonic_points ( const std : : vector < ExpandedStatPoint > & points )
{
for ( const auto & p : points )
{
if ( stat_is_voltage_harmonic_metric ( p . metric_id ) )
return true ;
}
return false ;
}
/// @brief 展开统计样本点,并对缺失指标执行 observation fallback。
/// @details
/// 第一阶段仍以主统计 observation 为基础,避免普通 RMS/频率/序分量在多 observation 之间互相覆盖。
/// 第二阶段针对当前已知会出现在其他 observation 的三相电压谐波 RMS, 遍历全部 observations 补充。
/// 后续新增间谐波、电流谐波等指标时,应优先复用这种“主 observation + 指标族 fallback”的模式。
std : : vector < ExpandedStatPoint > stat_expand_selected_statistical_observation (
const PqdifLogicalFile & lf ,
ParsedConnectionKind connection_kind ,
int & selected_observation_index ,
std : : string & selected_observation_name )
{
selected_observation_index = - 1 ;
selected_observation_name . clear ( ) ;
std : : vector < ExpandedStatPoint > out ;
const PqdifObservationRecord * selected = stat_select_primary_statistical_observation ( lf ) ;
if ( selected ! = nullptr )
{
selected_observation_index = selected - > observation_index ;
selected_observation_name = selected - > observation_name ;
for ( const auto & ch : selected - > channel_instances )
{
auto points = stat_expand_channel_points ( lf , connection_kind , * selected , ch ) ;
out . insert ( out . end ( ) ,
std : : make_move_iterator ( points . begin ( ) ) ,
std : : make_move_iterator ( points . end ( ) ) ) ;
}
}
// 对主 observation 缺失的目标指标执行统一 observation fallback。
// 例如:普通趋势指标可能在 Trend observation, 而 V HRMS A/B/C 2-50 次谐波在另一条 observation。
auto fallback_points = stat_expand_missing_metrics_from_all_observations (
lf ,
connection_kind ,
out ,
selected_observation_index ) ;
if ( ! fallback_points . empty ( ) )
{
out . insert ( out . end ( ) ,
std : : make_move_iterator ( fallback_points . begin ( ) ) ,
std : : make_move_iterator ( fallback_points . end ( ) ) ) ;
}
// 对同一 metric 的多来源候选流先做择优,避免后续按 timestamp 聚合时静默覆盖。
return stat_select_best_metric_sources ( out ) ;
}
bool pqdif_probe_text_looks_like_flicker ( const std : : string & text )
{
const std : : string key = normalize_key ( text ) ;
return key . find ( " FLICKER " ) ! = std : : string : : npos | |
key . find ( " FLKR " ) ! = std : : string : : npos | |
key . find ( " PST " ) ! = std : : string : : npos | |
key . find ( " PLT " ) ! = std : : string : : npos | |
key . find ( " DVC " ) ! = std : : string : : npos | |
key . find ( " DELTAV " ) ! = std : : string : : npos ;
}
/// @brief DEBUG 级别闪变候选通道诊断。
/// @details 用于核查 Pst/Plt/DVC 通道是否真实有数据;尤其是 Plt 通道存在但 values.count=0 时,
/// 这里能直接看出来,避免误以为是名称匹配失败。
void dump_flicker_candidate_probe ( const ParsedPqdifFile & parsed_file )
{
const auto & lf = parsed_file . logical_file ;
std : : cout < < " ========== PQDIF FLICKER CANDIDATE PROBE V18 ========== " < < std : : endl ;
std : : cout < < " file= " < < parsed_file . source_file < < std : : endl ;
std : : cout < < " rule=DEBUG level: print all Pst/Plt/DVC/Flicker candidate channels and series counts " < < std : : endl ;
size_t hit_count = 0 ;
size_t plt_channel_count = 0 ;
size_t plt_value_point_count = 0 ;
for ( const auto & obs : lf . observations )
{
const PqdifDataSourceRecord * ds = stat_find_related_data_source ( lf , obs ) ;
for ( const auto & ch : obs . channel_instances )
{
const PqdifChannelDefinition * ch_def = stat_find_channel_definition ( ds , ch . channel_def_index ) ;
bool candidate = pqdif_probe_text_looks_like_flicker ( ch . channel_name ) | |
pqdif_probe_text_looks_like_flicker ( ch . quantity_type_id . symbolic_name ) ;
if ( ch_def ! = nullptr )
{
candidate = candidate | |
pqdif_probe_text_looks_like_flicker ( ch_def - > channel_name ) | |
pqdif_probe_text_looks_like_flicker ( ch_def - > quantity_name ) ;
}
for ( const auto & si : ch . series_instances )
{
candidate = candidate | |
pqdif_probe_text_looks_like_flicker ( si . value_type_id . symbolic_name ) | |
pqdif_probe_text_looks_like_flicker ( si . quantity_characteristic_id . symbolic_name ) ;
}
if ( ! candidate )
continue ;
+ + hit_count ;
const std : : string compact_name = normalize_key ( ch . channel_name + " " + ( ( ch_def ! = nullptr ) ? ch_def - > channel_name : std : : string ( ) ) ) ;
const bool is_plt_candidate = compact_name . find ( " PLT " ) ! = std : : string : : npos ;
if ( is_plt_candidate )
+ + plt_channel_count ;
std : : cout < < " [FLICKER-CH] obs= " < < obs . observation_index
< < " , obs_name= " < < obs . observation_name
< < " , obs_start= " < < obs . time_start . text
< < " , ch= " < < ch . channel_instance_index
< < " , ch_def= " < < ch . channel_def_index
< < " , channel= " < < ch . channel_name
< < " , def_channel= " < < ( ( ch_def ! = nullptr ) ? ch_def - > channel_name : std : : string ( ) )
< < " , phase= " < < pqdif_sem : : FindPhaseName ( ch . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( ch . quantity_measured_id )
< < " , series_instances= " < < ch . series_instances . size ( )
< < std : : endl ;
for ( size_t si_index = 0 ; si_index < ch . series_instances . size ( ) ; + + si_index )
{
const auto & si = ch . series_instances [ si_index ] ;
const PqdifSeriesDefinition * sd = stat_find_series_definition ( ch_def , si . series_def_index ) ;
const PqdifSeriesInstance * resolved = stat_resolve_shared_series ( obs , si ) ;
const long value_count = ( resolved ! = nullptr ) ? resolved - > values . count : - 1 ;
if ( is_plt_candidate & & value_count > 0 )
plt_value_point_count + = static_cast < size_t > ( value_count ) ;
std : : cout < < " [FLICKER-SER] ser= " < < si_index
< < " , ser_def= " < < si . series_def_index
< < " , value_type= " < < short_guid_name ( si . value_type_id )
< < " , characteristic= " < < short_guid_name ( si . quantity_characteristic_id )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( si . quantity_units_id )
< < " , resolved_count= " < < value_count ;
if ( resolved ! = nullptr & & value_count > 0 )
{
double raw_value = 0.0 ;
if ( stat_try_get_raw_numeric_at ( resolved - > values , 0 , raw_value ) )
{
const unsigned int storage_method_id = ( sd ! = nullptr ) ? sd - > storage_method_id : 0 ;
const double first_value = stat_decode_engineering_value (
raw_value ,
* resolved ,
storage_method_id ,
resolved - > values . physical_type ) ;
std : : cout < < " , first= " < < first_value ;
}
}
std : : cout < < std : : endl ;
}
}
}
std : : cout < < " [FLICKER SUMMARY] candidate_channels= " < < hit_count
< < " , plt_candidate_channels= " < < plt_channel_count
< < " , plt_value_points= " < < plt_value_point_count
< < std : : endl ;
std : : cout < < " =================================================== " < < std : : endl ;
}
/// @brief 将单个样本点应用到同一时间桶里的某个指标聚合值上。
/// @details
/// 这里不再允许静默覆盖:同一 timestamp + metric + kind 再次写入时,保留首次写入值,
/// 并把该 metric 标记为 DuplicateSource, 同时打印冲突来源。
/// @param bucket 目标时间桶。
/// @param p 已识别统计样本点。
/// @param quality_by_metric 指标级质量状态。
void stat_apply_point_to_bucket (
TimeAggregatedStatBucket & bucket ,
const ExpandedStatPoint & p ,
const std : : map < StatMetricId , StatMetricQualityInfo > & quality_by_metric )
{
auto & agg = bucket . metrics [ p . metric_id ] ;
const StatMetricQualityInfo * qi = stat_find_quality_info ( quality_by_metric , p . metric_id ) ;
if ( qi ! = nullptr & & agg . quality = = StatMetricQuality : : Normal )
{
agg . quality = qi - > quality ;
agg . quality_reason = qi - > reason ;
}
if ( agg . source_observation_index < 0 )
{
agg . source_observation_index = p . observation_index ;
agg . source_channel_instance_index = p . channel_instance_index ;
agg . source_channel_name = p . channel_name ;
}
if ( stat_has_value_kind ( agg , p . stat_kind ) )
{
const double old_value = stat_get_value_by_kind ( agg , p . stat_kind ) ;
std : : ostringstream reason ;
reason < < " duplicate write: kind= " < < stat_value_kind_name ( p . stat_kind )
< < " , old_value= " < < old_value
< < " , new_value= " < < p . value
< < " , old_ch= " < < agg . source_channel_instance_index
< < " , new_ch= " < < p . channel_instance_index ;
agg . quality = StatMetricQuality : : DuplicateSource ;
agg . quality_reason = reason . str ( ) ;
static size_t duplicate_print_count = 0 ;
if ( duplicate_print_count < 80 )
{
std : : cout < < " [DUPLICATE STAT VALUE] "
< < " time= " < < p . timestamp_text
< < " , metric= " < < stat_metric_name ( p . metric_id )
< < " , kind= " < < stat_value_kind_name ( p . stat_kind )
< < " , old_value= " < < old_value
< < " , new_value= " < < p . value
< < " , old_ch= " < < agg . source_channel_instance_index
< < " , new_ch= " < < p . channel_instance_index
< < " , new_ser= " < < p . series_instance_index
< < " , new_channel= " < < p . channel_name
< < std : : endl ;
+ + duplicate_print_count ;
if ( duplicate_print_count = = 80 )
{
std : : cout < < " [DUPLICATE STAT VALUE] print limit reached, further duplicate details suppressed "
< < std : : endl ;
}
}
// 调试和修复阶段保留首次写入,拒绝覆盖,避免结果继续被污染。
return ;
}
agg . source_series_instance_index = p . series_instance_index ;
switch ( p . stat_kind )
{
case StatValueKind : : Min :
agg . has_min = true ;
agg . min_value = p . value ;
break ;
case StatValueKind : : Max :
agg . has_max = true ;
agg . max_value = p . value ;
break ;
case StatValueKind : : Avg :
agg . has_avg = true ;
agg . avg_value = p . value ;
break ;
case StatValueKind : : P95 :
agg . has_p95 = true ;
agg . p95_value = p . value ;
break ;
default :
break ;
}
}
/// @brief 将统计样本点按 timestamp 聚合成时间桶。
/// @details
/// 当前阶段已经先筛掉非统计 observation, 并做过 metric 来源择优;这里仍保留覆盖检测,
/// 用于防止后续新增指标时重新引入静默覆盖。
/// @param points 已识别统计样本点。
/// @return 聚合后的时间桶数组,按时间升序输出。
std : : vector < TimeAggregatedStatBucket > stat_group_points_by_timestamp (
const std : : vector < ExpandedStatPoint > & points )
{
const std : : map < StatMetricId , StatMetricQualityInfo > quality_by_metric =
stat_analyze_metric_quality ( points ) ;
std : : map < time_t , TimeAggregatedStatBucket > buckets ;
for ( const auto & p : points )
{
auto & bucket = buckets [ p . timestamp ] ;
if ( bucket . timestamp = = 0 )
{
bucket . timestamp = p . timestamp ;
bucket . timestamp_text = p . timestamp_text ;
}
stat_apply_point_to_bucket ( bucket , p , quality_by_metric ) ;
}
std : : vector < TimeAggregatedStatBucket > out ;
out . reserve ( buckets . size ( ) ) ;
for ( auto & kv : buckets )
out . push_back ( std : : move ( kv . second ) ) ;
return out ;
}
void stat_print_aggregated_metric_line ( StatMetricId metric_id , const AggregatedStatValues * agg )
{
std : : cout < < " metric= " < < stat_metric_name ( metric_id ) ;
if ( agg = = nullptr )
{
std : : cout < < " , quality= " < < stat_metric_quality_name ( StatMetricQuality : : Missing )
< < std : : endl ;
return ;
}
if ( agg - > has_min ) std : : cout < < " , min= " < < agg - > min_value ;
else std : : cout < < " , min=N/A " ;
if ( agg - > has_max ) std : : cout < < " , max= " < < agg - > max_value ;
else std : : cout < < " , max=N/A " ;
if ( agg - > has_avg ) std : : cout < < " , avg= " < < agg - > avg_value ;
else std : : cout < < " , avg=N/A " ;
if ( agg - > has_p95 ) std : : cout < < " , p95= " < < agg - > p95_value ;
else std : : cout < < " , p95=N/A " ;
std : : cout < < " , quality= " < < stat_metric_quality_name ( agg - > quality ) ;
if ( ! agg - > quality_reason . empty ( ) )
std : : cout < < " , reason= " < < agg - > quality_reason ;
if ( agg - > source_channel_instance_index > = 0 )
{
std : : cout < < " , source_ch= " < < agg - > source_channel_instance_index ;
if ( ! agg - > source_channel_name . empty ( ) )
std : : cout < < " , source_channel= " < < agg - > source_channel_name ;
}
std : : cout < < std : : endl ;
}
size_t stat_count_present_metrics ( const std : : vector < ExpandedStatPoint > & points )
{
std : : set < StatMetricId > ids ;
for ( const auto & p : points )
ids . insert ( p . metric_id ) ;
return ids . size ( ) ;
}
size_t stat_count_present_voltage_harmonic_metrics ( const std : : vector < ExpandedStatPoint > & points )
{
std : : set < StatMetricId > ids ;
for ( const auto & p : points )
{
if ( stat_is_voltage_harmonic_metric ( p . metric_id ) )
ids . insert ( p . metric_id ) ;
}
return ids . size ( ) ;
}
std : : vector < StatMetricId > stat_present_voltage_harmonic_metrics (
const std : : map < StatMetricId , StatMetricSourceStats > & stats_by_metric )
{
std : : vector < StatMetricId > out ;
for ( const auto & kv : stats_by_metric )
{
if ( stat_is_voltage_harmonic_metric ( kv . first ) )
out . push_back ( kv . first ) ;
}
std : : sort ( out . begin ( ) , out . end ( ) , [ ] ( StatMetricId a , StatMetricId b ) {
return static_cast < int > ( a ) < static_cast < int > ( b ) ;
} ) ;
return out ;
}
const char * stat_dynamic_group_display_name ( StatDynamicMetricGroup group )
{
switch ( group )
{
case StatDynamicMetricGroup : : VoltageHarmonic : return " VoltageHarmonic " ;
case StatDynamicMetricGroup : : LineVoltageHarmonic : return " LineVoltageHarmonic " ;
case StatDynamicMetricGroup : : CurrentHarmonic : return " CurrentHarmonic " ;
case StatDynamicMetricGroup : : VoltageHarmonicAngle : return " VoltageHarmonicAngle " ;
case StatDynamicMetricGroup : : LineVoltageHarmonicAngle : return " LineVoltageHarmonicAngle " ;
case StatDynamicMetricGroup : : CurrentHarmonicAngle : return " CurrentHarmonicAngle " ;
case StatDynamicMetricGroup : : HarmonicActivePower : return " HarmonicActivePower " ;
case StatDynamicMetricGroup : : HarmonicReactivePower : return " HarmonicReactivePower " ;
case StatDynamicMetricGroup : : HarmonicApparentPower : return " HarmonicApparentPower " ;
case StatDynamicMetricGroup : : VoltageHarmonicRatio : return " VoltageHarmonicRatio " ;
case StatDynamicMetricGroup : : LineVoltageHarmonicRatio : return " LineVoltageHarmonicRatio " ;
case StatDynamicMetricGroup : : CurrentHarmonicRatio : return " CurrentHarmonicRatio " ;
case StatDynamicMetricGroup : : VoltageInterharmonic : return " VoltageInterharmonic " ;
case StatDynamicMetricGroup : : LineVoltageInterharmonic : return " LineVoltageInterharmonic " ;
case StatDynamicMetricGroup : : CurrentInterharmonic : return " CurrentInterharmonic " ;
default : return " UnknownDynamicGroup " ;
}
}
size_t stat_dynamic_group_expected_count ( StatDynamicMetricGroup group )
{
return stat_dynamic_metric_order_for_group ( group ) . size ( ) ;
}
std : : vector < StatMetricId > stat_present_dynamic_group_metrics (
const std : : map < StatMetricId , StatMetricSourceStats > & stats_by_metric ,
StatDynamicMetricGroup group )
{
std : : vector < StatMetricId > out ;
for ( const auto & kv : stats_by_metric )
{
if ( stat_is_dynamic_metric_group ( kv . first , group ) )
out . push_back ( kv . first ) ;
}
std : : sort ( out . begin ( ) , out . end ( ) , [ ] ( StatMetricId a , StatMetricId b ) {
return static_cast < int > ( a ) < static_cast < int > ( b ) ;
} ) ;
return out ;
}
size_t stat_count_present_dynamic_group_metrics (
const std : : vector < ExpandedStatPoint > & points ,
StatDynamicMetricGroup group )
{
std : : set < StatMetricId > ids ;
for ( const auto & p : points )
{
if ( stat_is_dynamic_metric_group ( p . metric_id , group ) )
ids . insert ( p . metric_id ) ;
}
return ids . size ( ) ;
}
size_t stat_count_present_all_dynamic_metrics ( const std : : vector < ExpandedStatPoint > & points )
{
std : : set < StatMetricId > ids ;
for ( const auto & p : points )
{
if ( stat_is_dynamic_metric ( p . metric_id ) )
ids . insert ( p . metric_id ) ;
}
return ids . size ( ) ;
}
const std : : vector < StatDynamicMetricGroup > & stat_dynamic_summary_groups ( )
{
static const std : : vector < StatDynamicMetricGroup > groups = {
StatDynamicMetricGroup : : VoltageHarmonic ,
StatDynamicMetricGroup : : LineVoltageHarmonic ,
StatDynamicMetricGroup : : CurrentHarmonic ,
StatDynamicMetricGroup : : VoltageHarmonicAngle ,
StatDynamicMetricGroup : : LineVoltageHarmonicAngle ,
StatDynamicMetricGroup : : CurrentHarmonicAngle ,
StatDynamicMetricGroup : : HarmonicActivePower ,
StatDynamicMetricGroup : : HarmonicReactivePower ,
StatDynamicMetricGroup : : HarmonicApparentPower ,
StatDynamicMetricGroup : : VoltageHarmonicRatio ,
StatDynamicMetricGroup : : LineVoltageHarmonicRatio ,
StatDynamicMetricGroup : : CurrentHarmonicRatio ,
StatDynamicMetricGroup : : VoltageInterharmonic ,
StatDynamicMetricGroup : : LineVoltageInterharmonic ,
StatDynamicMetricGroup : : CurrentInterharmonic
} ;
return groups ;
}
void stat_print_dynamic_group_compact_summaries (
const std : : map < StatMetricId , StatMetricSourceStats > & stats_by_metric )
{
for ( auto group : stat_dynamic_summary_groups ( ) )
{
const std : : vector < StatMetricId > present = stat_present_dynamic_group_metrics ( stats_by_metric , group ) ;
std : : cout < < " [DYNAMIC SPECTRUM SUMMARY] group= " < < stat_dynamic_group_display_name ( group )
< < " , present= " < < present . size ( ) < < " / " < < stat_dynamic_group_expected_count ( group ) ;
if ( ! present . empty ( ) )
{
const double first_order = stat_dynamic_metric_order_value ( present . front ( ) ) ;
const double last_order = stat_dynamic_metric_order_value ( present . back ( ) ) ;
std : : cout < < " , range= " < < first_order < < " - " < < last_order ;
}
std : : cout < < std : : endl ;
}
}
void stat_print_voltage_harmonic_compact_summary (
const std : : map < StatMetricId , StatMetricSourceStats > & stats_by_metric )
{
bool present [ 3 ] [ 51 ] = { } ;
for ( const auto & kv : stats_by_metric )
{
if ( ! stat_is_voltage_harmonic_metric ( kv . first ) )
continue ;
const int phase = stat_voltage_harmonic_phase_index ( kv . first ) ;
const int order = stat_voltage_harmonic_order ( kv . first ) ;
if ( phase > = 0 & & phase < 3 & & order > = 2 & & order < = 50 )
present [ phase ] [ order ] = true ;
}
size_t total_present = 0 ;
for ( int phase = 0 ; phase < 3 ; + + phase )
{
for ( int order = 2 ; order < = 50 ; + + order )
{
if ( present [ phase ] [ order ] )
+ + total_present ;
}
}
std : : cout < < " [VOLTAGE HARMONIC SUMMARY] present= " < < total_present
< < " /147 " ;
const char * phase_names [ 3 ] = { " A " , " B " , " C " } ;
for ( int phase = 0 ; phase < 3 ; + + phase )
{
int first = - 1 ;
int last = - 1 ;
int count = 0 ;
for ( int order = 2 ; order < = 50 ; + + order )
{
if ( ! present [ phase ] [ order ] )
continue ;
if ( first < 0 )
first = order ;
last = order ;
+ + count ;
}
std : : cout < < " , " < < phase_names [ phase ] < < " = " ;
if ( count = = 0 )
std : : cout < < " missing " ;
else if ( count = = 49 )
std : : cout < < " 2-50 " ;
else
std : : cout < < " count: " < < count < < " range: " < < first < < " - " < < last ;
}
std : : cout < < std : : endl ;
}
/// @brief 打印已展开统计样本点预览。
/// @details
/// 不再只打印前 12 个样本点,而是按“主要指标 × Min/Max/Avg/P95”打印每个流的首样本,
/// 这样后续新增指标时可以直接看出哪个 metric/kind 缺失或来自哪个通道。
/// @param parsed_file 当前已解析文件对象。
void dump_expanded_stat_preview ( const ParsedPqdifFile & parsed_file )
{
const auto & points = parsed_file . expanded_stat_points ;
if ( ! pqdif_is_trace_log_enabled ( ) )
{
std : : cout < < " ========== EXPANDED STAT SUMMARY ========== " < < std : : endl ;
std : : cout < < " connection_kind= " < < stat_connection_kind_name ( parsed_file . connection_kind )
< < " , selected_observation_index= " < < parsed_file . selected_observation_index
< < " , selected_observation_name= " < < parsed_file . selected_observation_name
< < " , selected_points= " < < points . size ( )
< < " , present_metrics= " < < stat_count_present_metrics ( points )
< < " , present_dynamic_spectrum= " < < stat_count_present_all_dynamic_metrics ( points )
< < " / " < < stat_all_dynamic_metric_order ( ) . size ( )
< < " , present_voltage_harmonics= " < < stat_count_present_voltage_harmonic_metrics ( points )
< < " /147 "
< < std : : endl ;
std : : cout < < " =========================================== " < < std : : endl ;
return ;
}
std : : cout < < " ========== EXPANDED STAT PREVIEW ========== " < < std : : endl ;
std : : cout < < " connection_kind= " < < stat_connection_kind_name ( parsed_file . connection_kind )
< < " , selected_observation_index= " < < parsed_file . selected_observation_index
< < " , selected_observation_name= " < < parsed_file . selected_observation_name
< < " , selected_points= " < < points . size ( )
< < std : : endl ;
typedef std : : pair < StatMetricId , StatValueKind > StreamKey ;
std : : map < StreamKey , const ExpandedStatPoint * > first_point_by_stream ;
for ( const auto & p : points )
{
StreamKey key ( p . metric_id , p . stat_kind ) ;
if ( first_point_by_stream . find ( key ) = = first_point_by_stream . end ( ) )
first_point_by_stream [ key ] = & p ;
}
const StatValueKind kinds [ 4 ] = {
StatValueKind : : Min ,
StatValueKind : : Max ,
StatValueKind : : Avg ,
StatValueKind : : P95
} ;
std : : cout < < " [PRIMARY METRIC STREAMS] metric_slots= "
< < stat_primary_metric_print_order ( ) . size ( )
< < " , stream_slots= " < < stat_primary_metric_print_order ( ) . size ( ) * 4
< < std : : endl ;
for ( const auto metric_id : stat_primary_metric_print_order ( ) )
{
for ( int i = 0 ; i < 4 ; + + i )
{
const StatValueKind kind = kinds [ i ] ;
StreamKey key ( metric_id , kind ) ;
const auto it = first_point_by_stream . find ( key ) ;
std : : cout < < " [STREAM] "
< < " metric= " < < stat_metric_name ( metric_id )
< < " , kind= " < < stat_value_kind_name ( kind ) ;
if ( it = = first_point_by_stream . end ( ) )
{
std : : cout < < " , status=MISSING " < < std : : endl ;
continue ;
}
const ExpandedStatPoint & p = * it - > second ;
std : : cout
< < " , status=OK "
< < " , first_time= " < < p . timestamp_text
< < " , first_value= " < < p . value
< < " , obs= " < < p . observation_index
< < " , ch= " < < p . channel_instance_index
< < " , ch_def= " < < p . channel_def_index
< < " , group= " < < p . channel_group_id
< < " , ser= " < < p . series_instance_index
< < " , ser_def= " < < p . series_def_index
< < " , channel= " < < p . channel_name
< < " , phase= " < < pqdif_sem : : FindPhaseName ( p . phase_id )
< < " , measured= " < < pqdif_sem : : FindQuantityMeasuredName ( p . quantity_measured_id )
< < " , unit= " < < pqdif_sem : : FindQuantityUnitsName ( p . quantity_units_id )
< < ( p . matched_by_name_fallback ? " , by_name=true " : " " )
< < std : : endl ;
}
}
// 扩展指标只有命中时才打印,避免干扰当前 14 项核查。
for ( const auto metric_id : stat_extra_metric_print_order ( ) )
{
bool has_any = false ;
for ( int i = 0 ; i < 4 ; + + i )
{
StreamKey key ( metric_id , kinds [ i ] ) ;
if ( first_point_by_stream . find ( key ) ! = first_point_by_stream . end ( ) )
{
has_any = true ;
break ;
}
}
if ( ! has_any )
continue ;
for ( int i = 0 ; i < 4 ; + + i )
{
StreamKey key ( metric_id , kinds [ i ] ) ;
const auto it = first_point_by_stream . find ( key ) ;
std : : cout < < " [EXTRA STREAM] "
< < " metric= " < < stat_metric_name ( metric_id )
< < " , kind= " < < stat_value_kind_name ( kinds [ i ] ) ;
if ( it = = first_point_by_stream . end ( ) )
{
std : : cout < < " , status=MISSING " < < std : : endl ;
continue ;
}
const ExpandedStatPoint & p = * it - > second ;
std : : cout
< < " , status=OK "
< < " , first_time= " < < p . timestamp_text
< < " , first_value= " < < p . value
< < " , ch= " < < p . channel_instance_index
< < " , channel= " < < p . channel_name
< < std : : endl ;
}
}
std : : cout < < " =========================================== " < < std : : endl ;
}
/// @brief 打印时间聚合桶预览。
/// @details
/// 每个预览桶固定打印当前核查的主要指标,不再用 shown>=12 截断。
/// 若某项缺失,会显式打印 quality=MISSING。
/// @param parsed_file 当前已解析文件对象。
void dump_grouped_bucket_preview ( const ParsedPqdifFile & parsed_file )
{
if ( ! pqdif_is_trace_log_enabled ( ) )
{
std : : cout < < " ========== GROUPED STAT CORE SUMMARY ========== " < < std : : endl ;
std : : cout < < " connection_kind= " < < stat_connection_kind_name ( parsed_file . connection_kind )
< < " , selected_observation_index= " < < parsed_file . selected_observation_index
< < " , selected_observation_name= " < < parsed_file . selected_observation_name
< < " , expanded_points= " < < parsed_file . expanded_stat_points . size ( )
< < " , buckets= " < < parsed_file . aggregated_stat_buckets . size ( )
< < " , core_metric_slots= " < < stat_core_metric_print_order ( ) . size ( )
< < " , dynamic_spectrum_slots= " < < stat_all_dynamic_metric_order ( ) . size ( )
< < std : : endl ;
std : : map < StatMetricId , StatMetricSourceStats > stats_by_metric =
stat_collect_metric_stats ( parsed_file . expanded_stat_points ) ;
std : : map < StatMetricId , StatMetricQualityInfo > quality_by_metric =
stat_analyze_metric_quality ( parsed_file . expanded_stat_points ) ;
stat_print_dynamic_group_compact_summaries ( stats_by_metric ) ;
const size_t bucket_limit = std : : min < size_t > ( parsed_file . aggregated_stat_buckets . size ( ) , 3 ) ;
for ( size_t i = 0 ; i < bucket_limit ; + + i )
{
const auto & b = parsed_file . aggregated_stat_buckets [ i ] ;
std : : cout < < " [BUCKET " < < i < < " ] "
< < " time= " < < b . timestamp_text
< < " , metric_count_present= " < < b . metrics . size ( )
< < std : : endl ;
for ( const auto metric_id : stat_core_metric_print_order ( ) )
{
const auto it = b . metrics . find ( metric_id ) ;
stat_print_aggregated_metric_line (
metric_id ,
it = = b . metrics . end ( ) ? nullptr : & it - > second ) ;
}
// 核心日志仅抽样打印 2/3/5 次谐波,避免每桶输出 147 行。
const int sample_orders [ ] = { 2 , 3 , 5 } ;
bool printed_header = false ;
for ( int phase = 0 ; phase < 3 ; + + phase )
{
for ( int oi = 0 ; oi < 3 ; + + oi )
{
const StatMetricId hid = stat_voltage_harmonic_metric_id ( phase , sample_orders [ oi ] ) ;
const auto hit = b . metrics . find ( hid ) ;
if ( hit = = b . metrics . end ( ) )
continue ;
if ( ! printed_header )
{
std : : cout < < " [VOLTAGE HARMONIC SAMPLE] orders=2/3/5 " < < std : : endl ;
printed_header = true ;
}
stat_print_aggregated_metric_line ( hid , & hit - > second ) ;
}
}
}
std : : cout < < " ========== METRIC STATUS CORE SUMMARY ========== " < < std : : endl ;
for ( const auto metric_id : stat_core_metric_print_order ( ) )
{
const auto stats_it = stats_by_metric . find ( metric_id ) ;
const auto quality_it = quality_by_metric . find ( metric_id ) ;
std : : cout < < " [METRIC STATUS] metric= " < < stat_metric_name ( metric_id ) ;
if ( stats_it = = stats_by_metric . end ( ) )
{
std : : cout < < " , quality= " < < stat_metric_quality_name ( StatMetricQuality : : Missing )
< < " , points=0 " < < std : : endl ;
continue ;
}
const StatMetricSourceStats & st = stats_it - > second ;
const StatMetricQuality quality =
quality_it = = quality_by_metric . end ( ) ? StatMetricQuality : : Missing : quality_it - > second . quality ;
const std : : string reason =
quality_it = = quality_by_metric . end ( ) ? std : : string ( " missing " ) : quality_it - > second . reason ;
std : : cout
< < " , quality= " < < stat_metric_quality_name ( quality )
< < " , reason= " < < reason
< < " , points= " < < st . point_count
< < " , source_obs= " < < st . key . observation_index
< < " , source_ch= " < < st . key . channel_instance_index
< < " , source_channel= " < < st . channel_name
< < " , min= " < < st . min_value
< < " , max= " < < st . max_value
< < " , avg_abs= " < < st . avg_abs_value ( )
< < std : : endl ;
}
for ( auto group : stat_dynamic_summary_groups ( ) )
{
const std : : vector < StatMetricId > present = stat_present_dynamic_group_metrics ( stats_by_metric , group ) ;
std : : cout < < " [DYNAMIC SPECTRUM STATUS] group= " < < stat_dynamic_group_display_name ( group )
< < " , present_metrics= " < < present . size ( )
< < " / " < < stat_dynamic_group_expected_count ( group ) < < std : : endl ;
size_t sample_count = 0 ;
for ( const auto metric_id : present )
{
if ( sample_count > = 6 )
break ;
const auto stats_it = stats_by_metric . find ( metric_id ) ;
if ( stats_it = = stats_by_metric . end ( ) )
continue ;
const StatMetricSourceStats & st = stats_it - > second ;
std : : cout < < " [DYNAMIC STATUS SAMPLE] metric= " < < stat_metric_name ( metric_id )
< < " , points= " < < st . point_count
< < " , source_obs= " < < st . key . observation_index
< < " , source_ch= " < < st . key . channel_instance_index
< < " , order= " < < stat_dynamic_metric_order_value ( metric_id )
< < " , source_channel= " < < st . channel_name
< < " , min= " < < st . min_value
< < " , max= " < < st . max_value
< < " , avg_abs= " < < st . avg_abs_value ( )
< < std : : endl ;
+ + sample_count ;
}
}
std : : cout < < " ================================================= " < < std : : endl ;
return ;
}
std : : cout < < " ========== GROUPED STAT BUCKET PREVIEW ========== " < < std : : endl ;
std : : cout < < " connection_kind= " < < stat_connection_kind_name ( parsed_file . connection_kind )
< < " , selected_observation_index= " < < parsed_file . selected_observation_index
< < " , selected_observation_name= " < < parsed_file . selected_observation_name
< < " , expanded_points= " < < parsed_file . expanded_stat_points . size ( )
< < " , buckets= " < < parsed_file . aggregated_stat_buckets . size ( )
< < " , primary_metric_slots= " < < stat_primary_metric_print_order ( ) . size ( )
< < std : : endl ;
const size_t bucket_limit = std : : min < size_t > ( parsed_file . aggregated_stat_buckets . size ( ) , 3 ) ;
for ( size_t i = 0 ; i < bucket_limit ; + + i )
{
const auto & b = parsed_file . aggregated_stat_buckets [ i ] ;
std : : cout < < " [BUCKET " < < i < < " ] "
< < " time= " < < b . timestamp_text
< < " , metric_count_present= " < < b . metrics . size ( )
< < std : : endl ;
for ( const auto metric_id : stat_primary_metric_print_order ( ) )
{
const auto it = b . metrics . find ( metric_id ) ;
stat_print_aggregated_metric_line (
metric_id ,
it = = b . metrics . end ( ) ? nullptr : & it - > second ) ;
}
for ( const auto metric_id : stat_extra_metric_print_order ( ) )
{
const auto it = b . metrics . find ( metric_id ) ;
if ( it ! = b . metrics . end ( ) )
stat_print_aggregated_metric_line ( metric_id , & it - > second ) ;
}
}
std : : cout < < " ========== METRIC STATUS SUMMARY ========== " < < std : : endl ;
std : : map < StatMetricId , StatMetricSourceStats > stats_by_metric =
stat_collect_metric_stats ( parsed_file . expanded_stat_points ) ;
std : : map < StatMetricId , StatMetricQualityInfo > quality_by_metric =
stat_analyze_metric_quality ( parsed_file . expanded_stat_points ) ;
for ( const auto metric_id : stat_primary_metric_print_order ( ) )
{
const auto stats_it = stats_by_metric . find ( metric_id ) ;
const auto quality_it = quality_by_metric . find ( metric_id ) ;
std : : cout < < " [METRIC STATUS] metric= " < < stat_metric_name ( metric_id ) ;
if ( stats_it = = stats_by_metric . end ( ) )
{
std : : cout < < " , quality= " < < stat_metric_quality_name ( StatMetricQuality : : Missing )
< < " , points=0 " < < std : : endl ;
continue ;
}
const StatMetricSourceStats & s = stats_it - > second ;
const StatMetricQuality quality =
quality_it = = quality_by_metric . end ( ) ? StatMetricQuality : : Missing : quality_it - > second . quality ;
const std : : string reason =
quality_it = = quality_by_metric . end ( ) ? std : : string ( " missing " ) : quality_it - > second . reason ;
std : : cout
< < " , quality= " < < stat_metric_quality_name ( quality )
< < " , reason= " < < reason
< < " , points= " < < s . point_count
< < " , source_ch= " < < s . key . channel_instance_index
< < " , source_channel= " < < s . channel_name
< < " , min= " < < s . min_value
< < " , max= " < < s . max_value
< < " , avg_abs= " < < s . avg_abs_value ( )
< < std : : endl ;
}
std : : cout < < " ================================================= " < < std : : endl ;
}
bool parse_container_record ( CPQDIF & file_convert ,
CPQDIF_R_General * record ,
long record_index ,
PqdifContainerRecord & out )
{
if ( record = = nullptr )
return false ;
out . header = build_record_header_info ( record , record_index ) ;
CPQDIF_E_Collection * main = record - > GetMainCollection ( ) ;
if ( main = = nullptr )
return true ;
out . version_info = read_vector_uint_values ( main , tagVersionInfo ) ;
read_string_tag ( main , tagFileName , out . file_name ) ;
read_timestamp_tag ( main , tagCreation , file_convert , out . creation_time ) ;
read_timestamp_tag ( main , tagLastSaved , file_convert , out . last_saved_time ) ;
read_uint_tag ( main , tagTimesSaved , out . times_saved ) ;
read_string_tag ( main , tagLanguage , out . language ) ;
read_string_tag ( main , tagTitle , out . title ) ;
read_string_tag ( main , tagSubject , out . subject ) ;
read_string_tag ( main , tagAuthor , out . author ) ;
read_string_tag ( main , tagKeywords , out . keywords ) ;
read_string_tag ( main , tagComments , out . comments ) ;
read_string_tag ( main , tagLastSavedBy , out . last_saved_by ) ;
read_string_tag ( main , tagApplication , out . application ) ;
read_guid_tag ( main , tagCompressionStyleID , out . compression_style_id ) ;
read_uint_tag ( main , tagCompressionAlgorithmID , out . compression_algorithm_id ) ;
read_uint_tag ( main , tagCompressionChecksum , out . compression_checksum ) ;
read_string_tag ( main , tagOwner , out . owner ) ;
read_string_tag ( main , tagCopyright , out . copyright ) ;
read_string_tag ( main , tagTrademarks , out . trademarks ) ;
read_string_tag ( main , tagNotes , out . notes ) ;
read_string_tag ( main , tagAddress1 , out . address1 ) ;
read_string_tag ( main , tagAddress2 , out . address2 ) ;
read_string_tag ( main , tagCity , out . city ) ;
read_string_tag ( main , tagState , out . state ) ;
read_string_tag ( main , tagPostalCode , out . postal_code ) ;
read_string_tag ( main , tagCountry , out . country ) ;
read_string_tag ( main , tagPhoneVoice , out . phone_voice ) ;
read_string_tag ( main , tagPhoneFAX , out . phone_fax ) ;
read_string_tag ( main , tagEMail , out . email ) ;
const std : : set < std : : string > known = {
" tagVersionInfo " , " tagFileName " , " tagCreation " , " tagLastSaved " , " tagTimesSaved " ,
" tagLanguage " , " tagTitle " , " tagSubject " , " tagAuthor " , " tagKeywords " , " tagComments " ,
" tagLastSavedBy " , " tagApplication " , " tagCompressionStyleID " , " tagCompressionAlgorithmID " ,
" tagCompressionChecksum " , " tagOwner " , " tagCopyright " , " tagTrademarks " , " tagNotes " ,
" tagAddress1 " , " tagAddress2 " , " tagCity " , " tagState " , " tagPostalCode " , " tagCountry " ,
" tagPhoneVoice " , " tagPhoneFAX " , " tagEMail "
} ;
collect_extra_tags ( main , known , file_convert , out . extra_tags ) ;
return true ;
}
bool parse_data_source_record ( CPQDIF & file_convert ,
CPQDIF_R_DataSource * record ,
long record_index ,
int data_source_index ,
PqdifDataSourceRecord & out )
{
if ( record = = nullptr )
return false ;
out . header = build_record_header_info ( record , record_index ) ;
out . data_source_index = data_source_index ;
out . record_index = record_index ;
GUID ds_type { } ;
GUID vendor { } ;
GUID equip { } ;
std : : string serial ;
std : : string version ;
std : : string name ;
std : : string owner ;
std : : string location ;
std : : string time_zone ;
if ( record - > GetInfo ( ds_type , vendor , equip , serial , version , name , owner , location , time_zone ) )
{
out . data_source_type_id = make_guid_value ( ds_type ) ;
out . vendor_id = make_guid_value ( vendor ) ;
out . equipment_id = make_guid_value ( equip ) ;
out . serial_number = serial ;
out . version = version ;
out . name = name ;
out . owner = owner ;
out . location = location ;
out . time_zone = time_zone ;
}
CPQDIF_E_Collection * main = record - > GetMainCollection ( ) ;
if ( main ! = nullptr )
{
read_string_tag ( main , tagCustomSourceInfo , out . custom_source_info ) ;
read_guid_tag ( main , tagInstrumentTypeID , out . instrument_type_id ) ;
read_string_tag ( main , tagInstrumentModelName , out . instrument_model_name ) ;
read_string_tag ( main , tagInstrumentModelNumber , out . instrument_model_number ) ;
read_string_tag ( main , tagSerialNumberDS , out . serial_number ) ;
read_string_tag ( main , tagVersionDS , out . version ) ;
read_string_tag ( main , tagNameDS , out . name ) ;
read_string_tag ( main , tagOwnerDS , out . owner ) ;
read_string_tag ( main , tagLocationDS , out . location ) ;
read_string_tag ( main , tagTimeZoneDS , out . time_zone ) ;
read_string_tag ( main , tagCoordinatesDS , out . coordinates ) ;
const std : : set < std : : string > known = {
" tagDataSourceTypeID " , " tagVendorID " , " tagEquipmentID " , " tagCustomSourceInfo " ,
" tagInstrumentTypeID " , " tagInstrumentModelName " , " tagInstrumentModelNumber " ,
" tagSerialNumberDS " , " tagVersionDS " , " tagNameDS " , " tagOwnerDS " , " tagLocationDS " ,
" tagTimeZoneDS " , " tagCoordinatesDS " , " tagChannelDefns "
} ;
collect_extra_tags ( main , known , file_convert , out . extra_tags ) ;
}
const long channel_count = record - > GetCountChannelDefns ( ) ;
out . channel_definitions . reserve ( static_cast < size_t > ( std : : max < long > ( channel_count , 0 ) ) ) ;
for ( long i = 0 ; i < channel_count ; + + i )
{
PqdifChannelDefinition channel_def ;
channel_def . channel_def_index = static_cast < int > ( i ) ;
std : : string channel_name ;
UINT4 phase_id = 0 ;
GUID quantity_type { } ;
UINT4 quantity_measured = 0 ;
if ( record - > GetChannelDefnInfo ( i , channel_name , phase_id , quantity_type , quantity_measured ) )
{
channel_def . channel_name = channel_name ;
channel_def . phase_id = phase_id ;
channel_def . quantity_type_id = make_guid_value ( quantity_type ) ;
channel_def . quantity_measured_id = quantity_measured ;
}
long primary_series = - 1 ;
if ( record - > GetChannelPrimarySeries ( i , primary_series ) )
channel_def . primary_series_index = static_cast < int > ( primary_series ) ;
CPQDIF_E_Collection * channel_coll = record - > GetOneChannelDefn ( i ) ;
if ( channel_coll ! = nullptr )
{
read_string_tag ( channel_coll , tagOtherChannelIdentifier , channel_def . other_channel_identifier ) ;
read_string_tag ( channel_coll , tagGroupName , channel_def . group_name ) ;
read_uint_tag ( channel_coll , tagPhysicalChannel , channel_def . physical_channel ) ;
read_string_tag ( channel_coll , tagQuantityName , channel_def . quantity_name ) ;
const std : : set < std : : string > known_channel = {
" tagChannelName " , " tagPhaseID " , " tagOtherChannelIdentifier " , " tagGroupName " ,
" tagQuantityTypeID " , " tagQuantityMeasuredID " , " tagPhysicalChannel " ,
" tagQuantityName " , " tagPrimarySeriesIdx " , " tagSeriesDefns "
} ;
collect_extra_tags ( channel_coll , known_channel , file_convert , channel_def . extra_tags ) ;
}
const long series_count = record - > GetCountSeriesDefns ( static_cast < int > ( i ) ) ;
channel_def . series_definitions . reserve ( static_cast < size_t > ( std : : max < long > ( series_count , 0 ) ) ) ;
for ( long j = 0 ; j < series_count ; + + j )
{
PqdifSeriesDefinition series_def ;
series_def . series_def_index = static_cast < int > ( j ) ;
UINT4 quantity_units = 0 ;
GUID value_type { } ;
GUID quantity_characteristic { } ;
UINT4 storage_method = 0 ;
if ( record - > GetSeriesDefnInfo ( i , j , quantity_units , value_type , quantity_characteristic , storage_method ) )
{
series_def . quantity_units_id = quantity_units ;
series_def . value_type_id = make_guid_value ( value_type ) ;
series_def . quantity_characteristic_id = make_guid_value ( quantity_characteristic ) ;
series_def . storage_method_id = storage_method ;
}
UINT4 precision = 0 ;
double resolution = 0.0 ;
if ( record - > GetSeriesDefnPrecisionAndResolution ( i , j , precision , resolution ) )
{
series_def . significant_digits_id = precision ;
series_def . quantity_resolution = resolution ;
}
double nominal = 0.0 ;
if ( record - > GetSeriesDefnNominal ( i , j , nominal ) )
series_def . nominal_quantity = nominal ;
CPQDIF_E_Collection * series_coll = record - > GetOneSeriesDefn ( i , j ) ;
if ( series_coll ! = nullptr )
{
read_string_tag ( series_coll , tagValueTypeName , series_def . value_type_name ) ;
read_uint_tag ( series_coll , tagHintGreekPrefixID , series_def . hint_greek_prefix_id ) ;
read_uint_tag ( series_coll , tagHintPreferredUnitsID , series_def . hint_preferred_units_id ) ;
read_uint_tag ( series_coll , tagHintDefaultDisplayID , series_def . hint_default_display_id ) ;
read_double_tag ( series_coll , tagProbInterval , series_def . prob_interval ) ;
read_double_tag ( series_coll , tagProbPercentile , series_def . prob_percentile ) ;
read_double_tag ( series_coll , tagSeriesNominalQuantity , series_def . nominal_quantity ) ;
read_timestamp_tag ( series_coll , tagEffective , file_convert , series_def . effective_time ) ;
const std : : set < std : : string > known_series = {
" tagValueTypeID " , " tagQuantityUnitsID " , " tagQuantityCharacteristicID " ,
" tagQuantitySignificantDigitsID " , " tagQuantityResolutionID " , " tagStorageMethodID " ,
" tagValueTypeName " , " tagHintGreekPrefixID " , " tagHintPreferredUnitsID " ,
" tagHintDefaultDisplayID " , " tagProbInterval " , " tagProbPercentile " ,
" tagSeriesNominalQuantity " , " tagEffective "
} ;
collect_extra_tags ( series_coll , known_series , file_convert , series_def . extra_tags ) ;
}
channel_def . series_definitions . push_back ( std : : move ( series_def ) ) ;
}
out . channel_definitions . push_back ( std : : move ( channel_def ) ) ;
}
return true ;
}
bool parse_monitor_settings_record ( CPQDIF & file_convert ,
CPQDIF_R_Settings * record ,
long record_index ,
int settings_index ,
PqdifMonitorSettingsRecord & out )
{
if ( record = = nullptr )
return false ;
out . header = build_record_header_info ( record , record_index ) ;
out . settings_index = settings_index ;
out . record_index = record_index ;
TIMESTAMPPQDIF time_effective { } ;
TIMESTAMPPQDIF time_installed { } ;
TIMESTAMPPQDIF time_removed { } ;
bool use_cal = false ;
bool use_trans = false ;
if ( record - > GetInfo ( time_effective , time_installed , time_removed , use_cal , use_trans ) )
{
out . effective_time = make_timestamp_value ( file_convert , time_effective ) ;
out . time_installed = make_timestamp_value ( file_convert , time_installed ) ;
out . time_removed = make_timestamp_value ( file_convert , time_removed ) ;
out . use_calibration = use_cal ;
out . use_transducer = use_trans ;
}
UINT4 connection_type = 0 ;
if ( record - > GetConnectionInfo ( connection_type ) )
out . physical_connection = connection_type ;
REAL8 nominal_frequency = 0.0 ;
if ( record - > GetNominalFrequency ( nominal_frequency ) )
out . nominal_frequency = nominal_frequency ;
CPQDIF_E_Collection * main = record - > GetMainCollection ( ) ;
if ( main ! = nullptr )
{
read_double_tag ( main , tagNominalVoltage , out . nominal_voltage ) ;
read_bool_tag ( main , tagIsPCC , out . is_pcc ) ;
const std : : set < std : : string > known = {
" tagEffective " , " tagTimeInstalled " , " tagTimeRemoved " , " tagUseCalibration " ,
" tagUseTransducer " , " tagNominalFrequency " , " tagSettingPhysicalConnection " ,
" tagNominalVoltage " , " tagIsPCC " , " tagChannelSettings "
} ;
collect_extra_tags ( main , known , file_convert , out . extra_tags ) ;
}
const long channel_count = record - > GetCountChannels ( ) ;
out . channel_settings . reserve ( static_cast < size_t > ( std : : max < long > ( channel_count , 0 ) ) ) ;
for ( long i = 0 ; i < channel_count ; + + i )
{
PqdifChannelSetting channel_setting ;
channel_setting . channel_setting_index = static_cast < int > ( i ) ;
UINT4 channel_def_index = 0 ;
if ( record - > GetChannelInfo ( i , channel_def_index ) )
channel_setting . channel_def_index = static_cast < int > ( channel_def_index ) ;
CPQDIF_E_Collection * channel_coll = record - > GetOneChannelSetting ( i ) ;
if ( channel_coll ! = nullptr )
{
read_uint_tag ( channel_coll , tagTriggerTypeID , channel_setting . trigger_type_id ) ;
read_double_tag ( channel_coll , tagFullScale , channel_setting . full_scale ) ;
read_double_tag ( channel_coll , tagNoiseFloor , channel_setting . noise_floor ) ;
read_uint_tag ( channel_coll , tagXDTransformerTypeID , channel_setting . xd_transformer_type_id ) ;
read_double_tag ( channel_coll , tagXDSystemSideRatio , channel_setting . xd_system_side_ratio ) ;
read_double_tag ( channel_coll , tagXDMonitorSideRatio , channel_setting . xd_monitor_side_ratio ) ;
read_double_tag ( channel_coll , tagCalTimeSkew , channel_setting . cal_time_skew ) ;
read_double_tag ( channel_coll , tagCalOffset , channel_setting . cal_offset ) ;
read_double_tag ( channel_coll , tagCalRatio , channel_setting . cal_ratio ) ;
read_bool_tag ( channel_coll , tagCalMustUseARCal , channel_setting . cal_must_use_arcal ) ;
read_double_tag ( channel_coll , tagTriggerHighHigh , channel_setting . trigger_high_high ) ;
read_double_tag ( channel_coll , tagTriggerHigh , channel_setting . trigger_high ) ;
read_double_tag ( channel_coll , tagTriggerLow , channel_setting . trigger_low ) ;
read_double_tag ( channel_coll , tagTriggerLowLow , channel_setting . trigger_low_low ) ;
read_double_tag ( channel_coll , tagTriggerDeadBand , channel_setting . trigger_deadband ) ;
read_double_tag ( channel_coll , tagTriggerRate , channel_setting . trigger_rate ) ;
CPQDIF_Element * trigger_shape = channel_coll - > GetElement ( tagTriggerShapeParam , ID_ELEMENT_TYPE_VECTOR ) ;
if ( trigger_shape ! = nullptr )
channel_setting . trigger_shape_param = extract_vector_values ( static_cast < CPQDIF_E_Vector * > ( trigger_shape ) , file_convert ) ;
CPQDIF_Element * xd_response = channel_coll - > GetElement ( tagXDFrequencyResponse , ID_ELEMENT_TYPE_VECTOR ) ;
if ( xd_response ! = nullptr )
channel_setting . xd_frequency_response = extract_vector_values ( static_cast < CPQDIF_E_Vector * > ( xd_response ) , file_convert ) ;
CPQDIF_Element * cal_applied = channel_coll - > GetElement ( tagCalApplied , ID_ELEMENT_TYPE_VECTOR ) ;
if ( cal_applied ! = nullptr )
channel_setting . cal_applied = extract_vector_values ( static_cast < CPQDIF_E_Vector * > ( cal_applied ) , file_convert ) ;
CPQDIF_Element * cal_recorded = channel_coll - > GetElement ( tagCalRecorded , ID_ELEMENT_TYPE_VECTOR ) ;
if ( cal_recorded ! = nullptr )
channel_setting . cal_recorded = extract_vector_values ( static_cast < CPQDIF_E_Vector * > ( cal_recorded ) , file_convert ) ;
const std : : set < std : : string > known_channel = {
" tagChannelDefnIdx " , " tagTriggerTypeID " , " tagFullScale " , " tagNoiseFloor " ,
" tagTriggerShapeParam " , " tagXDTransformerTypeID " , " tagXDSystemSideRatio " ,
" tagXDMonitorSideRatio " , " tagXDFrequencyResponse " , " tagCalTimeSkew " ,
" tagCalOffset " , " tagCalRatio " , " tagCalMustUseARCal " , " tagCalApplied " ,
" tagCalRecorded " , " tagTriggerHighHigh " , " tagTriggerHigh " , " tagTriggerLow " ,
" tagTriggerLowLow " , " tagTriggerDeadBand " , " tagTriggerRate "
} ;
collect_extra_tags ( channel_coll , known_channel , file_convert , channel_setting . extra_tags ) ;
}
out . channel_settings . push_back ( std : : move ( channel_setting ) ) ;
}
return true ;
}
bool parse_observation_record ( CPQDIF & file_convert ,
CPQDIF_R_Observation * record ,
long record_index ,
int observation_index ,
int related_data_source_index ,
long related_data_source_record_index ,
int related_settings_index ,
long related_settings_record_index ,
PqdifObservationRecord & out )
{
if ( record = = nullptr )
return false ;
out . header = build_record_header_info ( record , record_index ) ;
out . observation_index = observation_index ;
out . record_index = record_index ;
out . related_data_source_index = related_data_source_index ;
out . related_data_source_record_index = related_data_source_record_index ;
out . related_settings_index = related_settings_index ;
out . related_settings_record_index = related_settings_record_index ;
TIMESTAMPPQDIF time_start { } ;
TIMESTAMPPQDIF time_create { } ;
std : : string observation_name ;
if ( record - > GetInfo ( time_start , time_create , observation_name ) )
{
out . observation_name = observation_name ;
out . time_start = make_timestamp_value ( file_convert , time_start ) ;
out . time_create = make_timestamp_value ( file_convert , time_create ) ;
}
UINT4 trigger_method = 0 ;
CPQDIF_E_Vector * trigger_channels = nullptr ;
TIMESTAMPPQDIF time_triggered { } ;
if ( record - > GetTriggerInfo ( trigger_method , & trigger_channels , time_triggered ) )
{
out . trigger_method_id = trigger_method ;
out . time_triggered = make_timestamp_value ( file_convert , time_triggered ) ;
if ( trigger_channels ! = nullptr )
{
PqdifValueArray trigger_indexes = extract_vector_values ( trigger_channels , file_convert ) ;
for ( size_t i = 0 ; i < trigger_indexes . int_values . size ( ) ; + + i )
out . channel_trigger_indexes . push_back ( static_cast < int > ( trigger_indexes . int_values [ i ] ) ) ;
for ( size_t i = 0 ; i < trigger_indexes . uint_values . size ( ) ; + + i )
out . channel_trigger_indexes . push_back ( static_cast < int > ( trigger_indexes . uint_values [ i ] ) ) ;
}
}
CPQDIF_E_Collection * main = record - > GetMainCollection ( ) ;
if ( main ! = nullptr )
{
read_uint_tag ( main , tagObservationSerial , out . observation_serial ) ;
read_uint_tag ( main , tagObservationAggregationSerial , out . observation_aggregation_serial ) ;
read_guid_tag ( main , tagDisturbanceCategoryID , out . disturbance_category_id ) ;
const std : : set < std : : string > known = {
" tagObservationName " , " tagTimeCreate " , " tagTimeStart " , " tagTriggerMethodID " ,
" tagTimeTriggered " , " tagChannelTriggerIdx " , " tagObservationSerial " ,
" tagObservationAggregationSerial " , " tagDisturbanceCategoryID " , " tagChannelInstances "
} ;
collect_extra_tags ( main , known , file_convert , out . extra_tags ) ;
}
const long channel_count = record - > GetCountChannels ( ) ;
out . channel_instances . reserve ( static_cast < size_t > ( std : : max < long > ( channel_count , 0 ) ) ) ;
for ( long i = 0 ; i < channel_count ; + + i )
{
PqdifChannelInstance channel_instance ;
channel_instance . channel_instance_index = static_cast < int > ( i ) ;
long channel_def_index = - 1 ;
if ( record - > GetChannelDefnIdx ( i , channel_def_index ) )
channel_instance . channel_def_index = static_cast < int > ( channel_def_index ) ;
std : : string channel_name ;
UINT4 phase_id = 0 ;
GUID quantity_type { } ;
UINT4 quantity_measured = 0 ;
if ( record - > GetChannelInfo ( i , channel_name , phase_id , quantity_type , quantity_measured ) )
{
channel_instance . channel_name = channel_name ;
channel_instance . phase_id = phase_id ;
channel_instance . quantity_type_id = make_guid_value ( quantity_type ) ;
channel_instance . quantity_measured_id = quantity_measured ;
}
long primary_series = - 1 ;
if ( record - > GetChannelPrimarySeries ( i , primary_series ) )
channel_instance . primary_series_index = static_cast < int > ( primary_series ) ;
CPQDIF_E_Collection * channel_coll = record - > GetOneChannel ( i ) ;
if ( channel_coll ! = nullptr )
{
read_double_tag ( channel_coll , tagCharactDuration , channel_instance . charact_duration ) ;
read_double_tag ( channel_coll , tagCharactMagnitude , channel_instance . charact_magnitude ) ;
read_double_tag ( channel_coll , tagCharactFrequency , channel_instance . charact_frequency ) ;
read_double_tag ( channel_coll , tagChannelFrequency , channel_instance . channel_frequency ) ;
read_int_tag ( channel_coll , tagChannelGroupID , channel_instance . channel_group_id ) ;
const std : : set < std : : string > known_channel = {
" tagChannelDefnIdx " , " tagCharactDuration " , " tagCharactMagnitude " ,
" tagCharactFrequency " , " tagChannelFrequency " , " tagChannelGroupID " ,
" tagSeriesInstances "
} ;
collect_extra_tags ( channel_coll , known_channel , file_convert , channel_instance . extra_tags ) ;
}
const long series_count = record - > GetCountSeries ( static_cast < int > ( i ) ) ;
channel_instance . series_instances . reserve ( static_cast < size_t > ( std : : max < long > ( series_count , 0 ) ) ) ;
for ( long j = 0 ; j < series_count ; + + j )
{
PqdifSeriesInstance series_instance ;
series_instance . series_instance_index = static_cast < int > ( j ) ;
series_instance . series_def_index = static_cast < int > ( j ) ;
UINT4 quantity_units = 0 ;
GUID quantity_characteristic { } ;
GUID value_type { } ;
if ( record - > GetSeriesInfo ( i , j , quantity_units , quantity_characteristic , value_type ) )
{
series_instance . quantity_units_id = quantity_units ;
series_instance . quantity_characteristic_id = make_guid_value ( quantity_characteristic ) ;
series_instance . value_type_id = make_guid_value ( value_type ) ;
}
long base_type = - 1 ;
if ( record - > GetSeriesBaseType ( i , j , base_type ) )
series_instance . series_base_type = base_type ;
record - > GetSeriesBaseQuantity ( i , j , series_instance . series_base_quantity ) ;
record - > GetSeriesScale ( i , j , series_instance . scale , series_instance . offset ) ;
record - > GetSeriesDefnNominal ( i , j , series_instance . nominal_quantity ) ;
UINT4 precision = 0 ;
double resolution = 0.0 ;
if ( record - > GetSeriesDefnPrecisionAndResolution ( i , j , precision , resolution ) )
{
series_instance . significant_digits_id = precision ;
series_instance . quantity_resolution = resolution ;
}
CPQDIF_E_Collection * series_coll = record - > GetOneSeries ( i , j ) ;
if ( series_coll ! = nullptr )
{
read_int_tag ( series_coll , tagSeriesShareChannelIdx , series_instance . share_channel_index ) ;
read_int_tag ( series_coll , tagSeriesShareSeriesIdx , series_instance . share_series_index ) ;
const std : : set < std : : string > known_series = {
" tagSeriesBaseQuantity " , " tagSeriesScale " , " tagSeriesOffset " ,
" tagSeriesShareChannelIdx " , " tagSeriesShareSeriesIdx " , " tagSeriesValues "
} ;
collect_extra_tags ( series_coll , known_series , file_convert , series_instance . extra_tags ) ;
}
CPQDIF_E_Vector * series_values = record - > GetSeriesValueVector ( i , j ) ;
if ( series_values ! = nullptr )
series_instance . values = extract_vector_values ( series_values , file_convert ) ;
channel_instance . series_instances . push_back ( std : : move ( series_instance ) ) ;
}
out . channel_instances . push_back ( std : : move ( channel_instance ) ) ;
}
return true ;
}
bool parse_pqdif_file_full ( const std : : string & file_path , PqdifLogicalFile & out_file , std : : string & err )
{
out_file = PqdifLogicalFile { } ;
CPQDIF file_convert ;
file_convert . put_FlatFileName ( file_path ) ;
std : : string basic_err ;
if ( ! dump_file_basic_info ( file_path , basic_err ) )
{
err = " precheck failed: " + basic_err ;
return false ;
}
if ( ! file_convert . Read ( ) )
{
err = " CPQDIF::Read() failed " ;
return false ;
}
const int record_count = static_cast < int > ( file_convert . RecordGetCount ( ) ) ;
int current_data_source_index = - 1 ;
long current_data_source_record_index = - 1 ;
int current_settings_index = - 1 ;
long current_settings_record_index = - 1 ;
for ( int i_record = 0 ; i_record < record_count ; + + i_record )
{
GUID record_guid { } ;
std : : string record_name ;
if ( ! file_convert . RecordGetInfo ( i_record , & record_guid , record_name ) )
continue ;
long raw_record_handle = 0 ;
if ( file_convert . RecordRequestRecord ( i_record , & raw_record_handle ) )
{
CPQDIFRecord * raw_record = reinterpret_cast < CPQDIFRecord * > ( raw_record_handle ) ;
out_file . record_headers . push_back ( build_record_header_info ( raw_record , i_record ) ) ;
}
if ( PQDIF_IsEqualGUID ( record_guid , tagContainer ) )
{
CPQDIF_R_General * record = reinterpret_cast < CPQDIF_R_General * > ( raw_record_handle ) ;
PqdifContainerRecord container ;
if ( parse_container_record ( file_convert , record , i_record , container ) )
out_file . containers . push_back ( std : : move ( container ) ) ;
continue ;
}
if ( PQDIF_IsEqualGUID ( record_guid , tagRecDataSource ) )
{
CPQDIF_R_DataSource * record = reinterpret_cast < CPQDIF_R_DataSource * > ( raw_record_handle ) ;
PqdifDataSourceRecord data_source ;
const int data_source_index = static_cast < int > ( out_file . data_sources . size ( ) ) ;
if ( parse_data_source_record ( file_convert , record , i_record , data_source_index , data_source ) )
{
out_file . data_sources . push_back ( std : : move ( data_source ) ) ;
current_data_source_index = data_source_index ;
current_data_source_record_index = i_record ;
current_settings_index = - 1 ;
current_settings_record_index = - 1 ;
}
continue ;
}
if ( PQDIF_IsEqualGUID ( record_guid , tagRecMonitorSettings ) )
{
CPQDIF_R_Settings * record = reinterpret_cast < CPQDIF_R_Settings * > ( raw_record_handle ) ;
PqdifMonitorSettingsRecord settings ;
const int settings_index = static_cast < int > ( out_file . monitor_settings . size ( ) ) ;
if ( parse_monitor_settings_record ( file_convert , record , i_record , settings_index , settings ) )
{
out_file . monitor_settings . push_back ( std : : move ( settings ) ) ;
current_settings_index = settings_index ;
current_settings_record_index = i_record ;
}
continue ;
}
if ( PQDIF_IsEqualGUID ( record_guid , tagRecObservation ) )
{
long observation_handle = 0 ;
if ( ! file_convert . RecordRequestObservation ( i_record , & observation_handle ) )
continue ;
CPQDIF_R_Observation * observation = reinterpret_cast < CPQDIF_R_Observation * > ( observation_handle ) ;
PqdifObservationRecord record ;
const int observation_index = static_cast < int > ( out_file . observations . size ( ) ) ;
parse_observation_record (
file_convert ,
observation ,
i_record ,
observation_index ,
current_data_source_index ,
current_data_source_record_index ,
current_settings_index ,
current_settings_record_index ,
record ) ;
out_file . observations . push_back ( std : : move ( record ) ) ;
file_convert . RecordReleaseObservation ( observation_handle ) ;
}
}
file_convert . Close ( ) ;
return true ;
}
constexpr float kPqdifBase64MissingFloat = 3.14159f ;
int stat_value_kind_code_for_base64 ( StatValueKind kind )
{
switch ( kind )
{
case StatValueKind : : Max : return 1 ;
case StatValueKind : : Min : return 2 ;
case StatValueKind : : Avg : return 3 ;
case StatValueKind : : P95 : return 4 ;
default : return 0 ;
}
}
std : : string pqdif_base64_encode_bytes ( const unsigned char * bytes_to_encode , size_t in_len )
{
static const char base64_chars [ ] =
" ABCDEFGHIJKLMNOPQRSTUVWXYZ "
" abcdefghijklmnopqrstuvwxyz "
" 0123456789+/ " ;
std : : string ret ;
ret . reserve ( ( ( in_len + 2 ) / 3 ) * 4 ) ;
int i = 0 ;
unsigned char char_array_3 [ 3 ] = { 0 , 0 , 0 } ;
unsigned char char_array_4 [ 4 ] = { 0 , 0 , 0 , 0 } ;
while ( in_len - - )
{
char_array_3 [ i + + ] = * ( bytes_to_encode + + ) ;
if ( i = = 3 )
{
char_array_4 [ 0 ] = static_cast < unsigned char > ( ( char_array_3 [ 0 ] & 0xfc ) > > 2 ) ;
char_array_4 [ 1 ] = static_cast < unsigned char > ( ( ( char_array_3 [ 0 ] & 0x03 ) < < 4 ) + ( ( char_array_3 [ 1 ] & 0xf0 ) > > 4 ) ) ;
char_array_4 [ 2 ] = static_cast < unsigned char > ( ( ( char_array_3 [ 1 ] & 0x0f ) < < 2 ) + ( ( char_array_3 [ 2 ] & 0xc0 ) > > 6 ) ) ;
char_array_4 [ 3 ] = static_cast < unsigned char > ( char_array_3 [ 2 ] & 0x3f ) ;
for ( i = 0 ; i < 4 ; + + i )
ret + = base64_chars [ char_array_4 [ i ] ] ;
i = 0 ;
}
}
if ( i ! = 0 )
{
int j = 0 ;
for ( j = i ; j < 3 ; + + j )
char_array_3 [ j ] = ' \0 ' ;
char_array_4 [ 0 ] = static_cast < unsigned char > ( ( char_array_3 [ 0 ] & 0xfc ) > > 2 ) ;
char_array_4 [ 1 ] = static_cast < unsigned char > ( ( ( char_array_3 [ 0 ] & 0x03 ) < < 4 ) + ( ( char_array_3 [ 1 ] & 0xf0 ) > > 4 ) ) ;
char_array_4 [ 2 ] = static_cast < unsigned char > ( ( ( char_array_3 [ 1 ] & 0x0f ) < < 2 ) + ( ( char_array_3 [ 2 ] & 0xc0 ) > > 6 ) ) ;
char_array_4 [ 3 ] = static_cast < unsigned char > ( char_array_3 [ 2 ] & 0x3f ) ;
for ( j = 0 ; j < i + 1 ; + + j )
ret + = base64_chars [ char_array_4 [ j ] ] ;
while ( i + + < 3 )
ret + = ' = ' ;
}
return ret ;
}
std : : string pqdif_base64_encode_float_vector ( const std : : vector < float > & values )
{
if ( values . empty ( ) )
return std : : string ( ) ;
const unsigned char * byte_data = reinterpret_cast < const unsigned char * > ( values . data ( ) ) ;
const size_t byte_size = values . size ( ) * sizeof ( float ) ;
return pqdif_base64_encode_bytes ( byte_data , byte_size ) ;
}
size_t pqdif_stat_base64_count_records_in_batch ( const PqdifStatBase64FileBatch & batch )
{
size_t n = 0 ;
for ( const auto & tp : batch . time_points )
n + = tp . records . size ( ) ;
return n ;
}
// 统计任意 Base64 文件级批次队列内部包含的子记录总数。
// 对象用途:既可以统计“生成队列”,也可以统计“待后续处理队列”。
// 调用约定:调用方必须已经持有对应队列的互斥锁。
size_t pqdif_stat_base64_count_records_in_queue_unlocked ( const std : : deque < PqdifStatBase64FileBatch > & queue )
{
size_t n = 0 ;
for ( const auto & batch : queue )
n + = pqdif_stat_base64_count_records_in_batch ( batch ) ;
return n ;
}
// 统计“生成队列”内部的子记录总数。
// 对象用途:保留旧函数名,供已有 GetPqdifStatBase64RecordCountInQueue() 复用。
// 调用约定:调用方必须已经持有 g_pqdif_stat_base64_mutex。
size_t pqdif_stat_base64_count_records_in_queue_unlocked ( )
{
return pqdif_stat_base64_count_records_in_queue_unlocked ( g_pqdif_stat_base64_queue ) ;
}
void pqdif_dump_stat_base64_file_batch_full ( const PqdifStatBase64FileBatch & batch )
{
std : : cout < < " ========== PQDIF BASE64 SAVED OBJECT FULL DUMP ========== " < < std : : endl ;
std : : cout < < " [FILE BATCH] file= " < < batch . pqdif_file_path
< < " , source_file= " < < batch . source_file
< < " , mac= " < < batch . mac
< < " , parsed_at= " < < batch . parsed_at_text
< < " , connection_kind= " < < stat_connection_kind_name ( batch . connection_kind )
< < " , time_point_count= " < < batch . time_point_count
< < " , total_record_count= " < < batch . total_record_count
< < " , total_float_count= " < < batch . total_float_count
< < " , total_placeholder_count= " < < batch . total_placeholder_count
< < " , total_base64_chars= " < < batch . total_base64_chars
< < std : : endl ;
for ( size_t i = 0 ; i < batch . time_points . size ( ) ; + + i )
{
const PqdifStatBase64TimePointPacket & tp = batch . time_points [ i ] ;
std : : cout < < " [TIME POINT " < < i < < " ] time= " < < tp . timestamp_text
< < " , timestamp= " < < static_cast < long long > ( tp . timestamp )
< < " , record_count= " < < tp . record_count
< < " , total_float_count= " < < tp . total_float_count
< < " , total_placeholder_count= " < < tp . total_placeholder_count
< < " , total_base64_chars= " < < tp . total_base64_chars
< < std : : endl ;
for ( size_t j = 0 ; j < tp . records . size ( ) ; + + j )
{
const PqdifStatBase64Record & rec = tp . records [ j ] ;
std : : cout < < " [BASE64 SUB RECORD " < < j < < " ] file= " < < rec . pqdif_file_path
< < " , time= " < < rec . timestamp_text
< < " , timestamp= " < < static_cast < long long > ( rec . timestamp )
< < " , kind= " < < rec . value_kind_name
< < " , kind_code= " < < rec . value_kind_code
< < " , connection_kind= " < < stat_connection_kind_name ( rec . connection_kind )
< < " , float_count= " < < rec . float_count
< < " , placeholder_count= " < < rec . placeholder_count
< < " , base64_len= " < < rec . base64_payload . size ( )
< < std : : endl ;
std : : cout < < " base64_payload= " < < rec . base64_payload < < std : : endl ;
}
}
std : : cout < < " ========================================================== " < < std : : endl ;
}
bool push_pqdif_stat_base64_file_batch ( PqdifStatBase64FileBatch & & batch )
{
// 对象用途:把“一个 PQDIF 文件解析完成后的 Base64 文件级批次”放入生成队列。
// 这里不直接做入库/上传,避免解析线程被后续业务阻塞。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
if ( g_pqdif_stat_base64_queue . size ( ) > = kPqdifStatBase64QueueLimit )
{
std : : cout < < " [PQDIF BASE64] file-batch queue full, drop oldest file batch: file= "
< < g_pqdif_stat_base64_queue . front ( ) . pqdif_file_path
< < " , time_points= " < < g_pqdif_stat_base64_queue . front ( ) . time_point_count
< < " , records= " < < g_pqdif_stat_base64_queue . front ( ) . total_record_count
< < std : : endl ;
g_pqdif_stat_base64_queue . pop_front ( ) ;
}
g_pqdif_stat_base64_queue . emplace_back ( std : : move ( batch ) ) ;
return true ;
}
bool pqdif_move_one_generated_base64_batch_to_ready_queue ( )
{
// 对象用途: RunPqdifScanLoop() 每轮循环末尾调用。
// 从“生成队列”取出最多一个 PQDIF 文件级批次,移动到“待后续处理队列”。
// 注意:这里只做快速移动和核心摘要打印,不在锁内执行耗时业务逻辑。
PqdifStatBase64FileBatch batch ;
{
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
if ( g_pqdif_stat_base64_queue . empty ( ) )
return false ;
batch = std : : move ( g_pqdif_stat_base64_queue . front ( ) ) ;
g_pqdif_stat_base64_queue . pop_front ( ) ;
}
const std : : string moved_file = batch . pqdif_file_path ;
const size_t moved_time_points = batch . time_point_count ;
const size_t moved_records = batch . total_record_count ;
const size_t moved_float_count = batch . total_float_count ;
const size_t moved_placeholder_count = batch . total_placeholder_count ;
{
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_ready_mutex ) ;
if ( g_pqdif_stat_base64_ready_queue . size ( ) > = kPqdifStatBase64QueueLimit )
{
std : : cout < < " [PQDIF BASE64 READY] ready queue full, drop oldest file batch: file= "
< < g_pqdif_stat_base64_ready_queue . front ( ) . pqdif_file_path
< < " , time_points= " < < g_pqdif_stat_base64_ready_queue . front ( ) . time_point_count
< < " , records= " < < g_pqdif_stat_base64_ready_queue . front ( ) . total_record_count
< < std : : endl ;
g_pqdif_stat_base64_ready_queue . pop_front ( ) ;
}
g_pqdif_stat_base64_ready_queue . emplace_back ( std : : move ( batch ) ) ;
}
std : : cout < < " [PQDIF BASE64 READY] moved one file batch for next step "
< < " , file= " < < moved_file
< < " , time_points= " < < moved_time_points
< < " , records= " < < moved_records
< < " , float_count= " < < moved_float_count
< < " , placeholders= " < < moved_placeholder_count
< < std : : endl ;
return true ;
}
std : : string pqdif_absolute_path_text ( const std : : string & path )
{
try
{
return fs : : absolute ( fs : : path ( path ) ) . string ( ) ;
}
catch ( . . . )
{
return path ;
}
}
bool bucket_has_metric_value_for_kind ( const AggregatedStatValues & values , StatValueKind kind , float & out )
{
switch ( kind )
{
case StatValueKind : : Min :
if ( ! values . has_min ) return false ;
out = static_cast < float > ( values . min_value ) ;
return true ;
case StatValueKind : : Max :
if ( ! values . has_max ) return false ;
out = static_cast < float > ( values . max_value ) ;
return true ;
case StatValueKind : : Avg :
if ( ! values . has_avg ) return false ;
out = static_cast < float > ( values . avg_value ) ;
return true ;
case StatValueKind : : P95 :
if ( ! values . has_p95 ) return false ;
out = static_cast < float > ( values . p95_value ) ;
return true ;
default :
return false ;
}
}
struct PqdifBase64BuildContext
{
PqdifBase64BuildContext (
const ParsedPqdifFile & file_ref ,
const TimeAggregatedStatBucket & bucket_ref ,
StatValueKind value_kind ,
std : : vector < float > & value_buffer )
: parsed_file ( file_ref ) ,
bucket ( bucket_ref ) ,
kind ( value_kind ) ,
values ( value_buffer ) ,
placeholder_count ( 0 ) ,
missing_metric_count ( 0 )
{
}
const ParsedPqdifFile & parsed_file ;
const TimeAggregatedStatBucket & bucket ;
StatValueKind kind ;
std : : vector < float > & values ;
size_t placeholder_count ;
size_t missing_metric_count ;
size_t delta_line_phase_fallback_count = 0 ;
std : : vector < std : : string > missing_metric_names ;
} ;
bool pqdif_base64_try_get_metric_value (
const TimeAggregatedStatBucket & bucket ,
StatMetricId metric_id ,
StatValueKind kind ,
float & out_value )
{
auto it = bucket . metrics . find ( metric_id ) ;
if ( it = = bucket . metrics . end ( ) )
return false ;
return bucket_has_metric_value_for_kind ( it - > second , kind , out_value ) ;
}
StatMetricId pqdif_base64_delta_line_to_phase_metric_id ( StatMetricId metric_id )
{
switch ( metric_id )
{
case StatMetricId : : UabRms : return StatMetricId : : UaRms ;
case StatMetricId : : UbcRms : return StatMetricId : : UbRms ;
case StatMetricId : : UcaRms : return StatMetricId : : UcRms ;
case StatMetricId : : UabDeviation : return StatMetricId : : UaDeviation ;
case StatMetricId : : UbcDeviation : return StatMetricId : : UbDeviation ;
case StatMetricId : : UcaDeviation : return StatMetricId : : UcDeviation ;
case StatMetricId : : UabThd : return StatMetricId : : UaThd ;
case StatMetricId : : UbcThd : return StatMetricId : : UbThd ;
case StatMetricId : : UcaThd : return StatMetricId : : UcThd ;
case StatMetricId : : UabDvc : return StatMetricId : : UaDvc ;
case StatMetricId : : UbcDvc : return StatMetricId : : UbDvc ;
case StatMetricId : : UcaDvc : return StatMetricId : : UcDvc ;
case StatMetricId : : UabPst : return StatMetricId : : UaPst ;
case StatMetricId : : UbcPst : return StatMetricId : : UbPst ;
case StatMetricId : : UcaPst : return StatMetricId : : UcPst ;
case StatMetricId : : UabPlt : return StatMetricId : : UaPlt ;
case StatMetricId : : UbcPlt : return StatMetricId : : UbPlt ;
case StatMetricId : : UcaPlt : return StatMetricId : : UcPlt ;
case StatMetricId : : UabFundRms : return StatMetricId : : UaFundRms ;
case StatMetricId : : UbcFundRms : return StatMetricId : : UbFundRms ;
case StatMetricId : : UcaFundRms : return StatMetricId : : UcFundRms ;
case StatMetricId : : UabFundAngle : return StatMetricId : : UaFundAngle ;
case StatMetricId : : UbcFundAngle : return StatMetricId : : UbFundAngle ;
case StatMetricId : : UcaFundAngle : return StatMetricId : : UcFundAngle ;
default : break ;
}
const StatDynamicMetricRange * r = stat_find_dynamic_metric_range ( metric_id ) ;
if ( r = = nullptr )
return StatMetricId : : Unknown ;
const int offset = stat_dynamic_metric_order_or_slot ( metric_id ) ;
if ( r - > group = = StatDynamicMetricGroup : : LineVoltageHarmonic )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : VoltageHarmonic , r - > phase_index , offset ) ;
if ( r - > group = = StatDynamicMetricGroup : : LineVoltageHarmonicAngle )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : VoltageHarmonicAngle , r - > phase_index , offset ) ;
if ( r - > group = = StatDynamicMetricGroup : : LineVoltageInterharmonic )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : VoltageInterharmonic , r - > phase_index , offset ) ;
if ( r - > group = = StatDynamicMetricGroup : : LineVoltageHarmonicRatio )
return stat_dynamic_metric_id ( StatDynamicMetricGroup : : VoltageHarmonicRatio , r - > phase_index , offset ) ;
return StatMetricId : : Unknown ;
}
void pqdif_base64_push_metric ( PqdifBase64BuildContext & ctx , StatMetricId metric_id )
{
float value = kPqdifBase64MissingFloat ;
bool ok = false ;
ok = pqdif_base64_try_get_metric_value ( ctx . bucket , metric_id , ctx . kind , value ) ;
if ( ! ok )
{
value = kPqdifBase64MissingFloat ;
+ + ctx . placeholder_count ;
+ + ctx . missing_metric_count ;
const std : : string name = stat_metric_name ( metric_id ) ;
ctx . missing_metric_names . push_back ( name ) ;
if ( pqdif_log_enabled ( PqdifLogLevel : : Debug ) )
{
std : : cout < < " [BASE64 MISSING METRIC] time= " < < ctx . bucket . timestamp_text
< < " , kind= " < < stat_value_kind_name ( ctx . kind )
< < " , metric= " < < name
< < " , placeholder= " < < kPqdifBase64MissingFloat
< < std : : endl ;
}
}
ctx . values . push_back ( value ) ;
}
void pqdif_base64_push_delta_line_metric_with_phase_fallback ( PqdifBase64BuildContext & ctx , StatMetricId line_metric_id )
{
float value = kPqdifBase64MissingFloat ;
if ( pqdif_base64_try_get_metric_value ( ctx . bucket , line_metric_id , ctx . kind , value ) )
{
ctx . values . push_back ( value ) ;
return ;
}
const StatMetricId phase_metric_id = pqdif_base64_delta_line_to_phase_metric_id ( line_metric_id ) ;
if ( phase_metric_id ! = StatMetricId : : Unknown & &
pqdif_base64_try_get_metric_value ( ctx . bucket , phase_metric_id , ctx . kind , value ) )
{
ctx . values . push_back ( value ) ;
+ + ctx . delta_line_phase_fallback_count ;
if ( pqdif_log_enabled ( PqdifLogLevel : : Info ) )
{
std : : cout < < " [BASE64 DELTA LINE FALLBACK] time= " < < ctx . bucket . timestamp_text
< < " , kind= " < < stat_value_kind_name ( ctx . kind )
< < " , line_metric= " < < stat_metric_name ( line_metric_id )
< < " , fallback_phase_metric= " < < stat_metric_name ( phase_metric_id )
< < " , value= " < < value
< < std : : endl ;
}
return ;
}
ctx . values . push_back ( kPqdifBase64MissingFloat ) ;
+ + ctx . placeholder_count ;
+ + ctx . missing_metric_count ;
const std : : string line_name = stat_metric_name ( line_metric_id ) ;
ctx . missing_metric_names . push_back ( line_name ) ;
if ( pqdif_log_enabled ( PqdifLogLevel : : Debug ) )
{
std : : cout < < " [BASE64 MISSING METRIC] time= " < < ctx . bucket . timestamp_text
< < " , kind= " < < stat_value_kind_name ( ctx . kind )
< < " , metric= " < < line_name
< < " , placeholder= " < < kPqdifBase64MissingFloat
< < " , reason=delta_line_metric_and_phase_fallback_missing " ;
if ( phase_metric_id ! = StatMetricId : : Unknown )
std : : cout < < " , fallback_phase_metric= " < < stat_metric_name ( phase_metric_id ) ;
std : : cout < < std : : endl ;
}
}
void pqdif_base64_push_placeholder ( PqdifBase64BuildContext & ctx , const std : : string & label )
{
ctx . values . push_back ( kPqdifBase64MissingFloat ) ;
+ + ctx . placeholder_count ;
if ( pqdif_log_enabled ( PqdifLogLevel : : Trace ) )
{
std : : cout < < " [BASE64 PLACEHOLDER] time= " < < ctx . bucket . timestamp_text
< < " , kind= " < < stat_value_kind_name ( ctx . kind )
< < " , label= " < < label
< < " , value= " < < kPqdifBase64MissingFloat
< < std : : endl ;
}
}
void pqdif_base64_push_placeholder_block ( PqdifBase64BuildContext & ctx , const std : : string & label , size_t count )
{
for ( size_t i = 0 ; i < count ; + + i )
{
ctx . values . push_back ( kPqdifBase64MissingFloat ) ;
+ + ctx . placeholder_count ;
}
if ( pqdif_log_enabled ( PqdifLogLevel : : Info ) )
{
std : : cout < < " [BASE64 PLACEHOLDER BLOCK] time= " < < ctx . bucket . timestamp_text
< < " , kind= " < < stat_value_kind_name ( ctx . kind )
< < " , label= " < < label
< < " , count= " < < count
< < " , value= " < < kPqdifBase64MissingFloat
< < std : : endl ;
}
}
void pqdif_base64_push_dynamic_order_range (
PqdifBase64BuildContext & ctx ,
StatDynamicMetricGroup group ,
int phase_index ,
int first_offset ,
int last_offset )
{
for ( int offset = first_offset ; offset < = last_offset ; + + offset )
{
pqdif_base64_push_metric ( ctx , stat_dynamic_metric_id ( group , phase_index , offset ) ) ;
}
}
void pqdif_base64_push_dynamic_three_phases (
PqdifBase64BuildContext & ctx ,
StatDynamicMetricGroup group ,
int first_offset ,
int last_offset )
{
for ( int phase = 0 ; phase < 3 ; + + phase )
pqdif_base64_push_dynamic_order_range ( ctx , group , phase , first_offset , last_offset ) ;
}
void pqdif_base64_push_dynamic_order_range_delta_line_fallback (
PqdifBase64BuildContext & ctx ,
StatDynamicMetricGroup line_group ,
int phase_index ,
int first_offset ,
int last_offset )
{
for ( int offset = first_offset ; offset < = last_offset ; + + offset )
{
pqdif_base64_push_delta_line_metric_with_phase_fallback (
ctx ,
stat_dynamic_metric_id ( line_group , phase_index , offset ) ) ;
}
}
void pqdif_base64_push_dynamic_three_line_phases_with_delta_fallback (
PqdifBase64BuildContext & ctx ,
StatDynamicMetricGroup line_group ,
int first_offset ,
int last_offset )
{
for ( int phase = 0 ; phase < 3 ; + + phase )
{
pqdif_base64_push_dynamic_order_range_delta_line_fallback (
ctx ,
line_group ,
phase ,
first_offset ,
last_offset ) ;
}
}
void pqdif_base64_push_harmonic_power_phase (
PqdifBase64BuildContext & ctx ,
int phase_index )
{
pqdif_base64_push_dynamic_order_range ( ctx , StatDynamicMetricGroup : : HarmonicActivePower , phase_index , 2 , 50 ) ;
pqdif_base64_push_dynamic_order_range ( ctx , StatDynamicMetricGroup : : HarmonicReactivePower , phase_index , 2 , 50 ) ;
pqdif_base64_push_dynamic_order_range ( ctx , StatDynamicMetricGroup : : HarmonicApparentPower , phase_index , 2 , 50 ) ;
}
std : : vector < float > pqdif_build_star_float_buffer_for_bucket (
const ParsedPqdifFile & parsed_file ,
const TimeAggregatedStatBucket & bucket ,
StatValueKind kind ,
size_t & placeholder_count ,
size_t & missing_metric_count ,
std : : vector < std : : string > & missing_metric_names )
{
std : : vector < float > float_buffer ;
float_buffer . reserve ( 2463 ) ;
PqdifBase64BuildContext ctx { parsed_file , bucket , kind , float_buffer } ;
// 1) 三相电压有效值、三相电流有效值、三线电压有效值
pqdif_base64_push_metric ( ctx , StatMetricId : : UaRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IaRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IbRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IcRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UabRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbcRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcaRms ) ;
// 2) 三相电压偏差 + 三线电压偏差占位
pqdif_base64_push_metric ( ctx , StatMetricId : : UaDeviation ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbDeviation ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcDeviation ) ;
pqdif_base64_push_placeholder_block ( ctx , " line voltage deviation placeholder(Uab/Ubc/Uca) " , 3 ) ;
// 3) 频率偏差 + 频率
pqdif_base64_push_metric ( ctx , StatMetricId : : FrequencyDeviation ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : Frequency ) ;
// 4) 电压零/负/正序和负序不平衡
pqdif_base64_push_metric ( ctx , StatMetricId : : UZeroSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UNegSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UPosSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UNegSeqUnbalance ) ;
// 5) 电流零/负/正序和负序不平衡
pqdif_base64_push_metric ( ctx , StatMetricId : : IZeroSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : INegSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IPosSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : INegSeqUnbalance ) ;
// 6) 三相电压谐波 2-50 次指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : VoltageHarmonic , 2 , 50 ) ;
// 7) 三相电流谐波 2-50 次指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : CurrentHarmonic , 2 , 50 ) ;
// 8) 三线电压谐波占位
pqdif_base64_push_placeholder_block ( ctx , " line voltage harmonic placeholder(Uab/Ubc/Uca,2-50) " , 49 * 3 ) ;
// 9) 三相电压谐波 2-50 次相角指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : VoltageHarmonicAngle , 2 , 50 ) ;
// 10) 三相电流谐波 2-50 次相角指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : CurrentHarmonicAngle , 2 , 50 ) ;
// 11) 三线电压谐波相角占位
pqdif_base64_push_placeholder_block ( ctx , " line voltage harmonic angle placeholder(Uab/Ubc/Uca,2-50) " , 49 * 3 ) ;
// 12) 三相电压间谐波 0.5-49.5 次指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : VoltageInterharmonic , 0 , 49 ) ;
// 13) 三相电流间谐波 0.5-49.5 次指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : CurrentInterharmonic , 0 , 49 ) ;
// 14) 三线电压间谐波占位
pqdif_base64_push_placeholder_block ( ctx , " line voltage interharmonic placeholder(Uab/Ubc/Uca,0.5-49.5) " , 50 * 3 ) ;
// 15) A/B/C/总 有功/无功/视在功率
pqdif_base64_push_metric ( ctx , StatMetricId : : PaPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QaPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : SaPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PbPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QbPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : SbPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PcPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QcPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : ScPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PTotalPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QTotalPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : STotalPower ) ;
// 16) A/B/C/总 谐波 2-50 次 有功/无功/视在功率
pqdif_base64_push_harmonic_power_phase ( ctx , 0 ) ;
pqdif_base64_push_harmonic_power_phase ( ctx , 1 ) ;
pqdif_base64_push_harmonic_power_phase ( ctx , 2 ) ;
pqdif_base64_push_harmonic_power_phase ( ctx , 3 ) ;
// 17) 谐波含有率:三相电压、三相电流、三线电压占位
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : VoltageHarmonicRatio , 2 , 50 ) ;
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : CurrentHarmonicRatio , 2 , 50 ) ;
pqdif_base64_push_placeholder_block ( ctx , " line voltage harmonic ratio placeholder(Uab/Ubc/Uca,2-50) " , 49 * 3 ) ;
// 18) 谐波总畸变率:三相电压、三相电流、三线电压占位
pqdif_base64_push_metric ( ctx , StatMetricId : : UaThd ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbThd ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcThd ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IaThd ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IbThd ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IcThd ) ;
pqdif_base64_push_placeholder_block ( ctx , " line voltage THD placeholder(Uab/Ubc/Uca) " , 3 ) ;
// 19) 三相功率因数和总功率因数
pqdif_base64_push_metric ( ctx , StatMetricId : : PFa ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PFb ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PFc ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PFTotal ) ;
// 20) 三相基波功率因数和总基波功率因数
pqdif_base64_push_metric ( ctx , StatMetricId : : FundPFa ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : FundPFb ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : FundPFc ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : FundPFTotal ) ;
// 21) 三相电压变动幅值 + 三线占位
pqdif_base64_push_metric ( ctx , StatMetricId : : UaDvc ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbDvc ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcDvc ) ;
pqdif_base64_push_placeholder_block ( ctx , " line voltage DVC placeholder(Uab/Ubc/Uca) " , 3 ) ;
// 22) 三相短闪 + 三线占位
pqdif_base64_push_metric ( ctx , StatMetricId : : UaPst ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbPst ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcPst ) ;
pqdif_base64_push_placeholder_block ( ctx , " line voltage Pst placeholder(Uab/Ubc/Uca) " , 3 ) ;
// 23) 三相长闪 + 三线占位
pqdif_base64_push_metric ( ctx , StatMetricId : : UaPlt ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbPlt ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcPlt ) ;
pqdif_base64_push_placeholder_block ( ctx , " line voltage Plt placeholder(Uab/Ubc/Uca) " , 3 ) ;
// 24) A/B/C/总 基波有功/无功/视在功率
pqdif_base64_push_metric ( ctx , StatMetricId : : PaFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QaFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : SaFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PbFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QbFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : SbFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PcFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QcFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : ScFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PTotalFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QTotalFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : STotalFundPower ) ;
// 25) 基波有效值:三相电压、三相电流、三线电压占位
pqdif_base64_push_metric ( ctx , StatMetricId : : UaFundRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbFundRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcFundRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IaFundRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IbFundRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IcFundRms ) ;
pqdif_base64_push_placeholder_block ( ctx , " line voltage fundamental RMS placeholder(Uab/Ubc/Uca) " , 3 ) ;
// 26) 基波相角:三相电压、三相电流、三线电压相角占位
pqdif_base64_push_metric ( ctx , StatMetricId : : UaFundAngle ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbFundAngle ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcFundAngle ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IaFundAngle ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IbFundAngle ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IcFundAngle ) ;
pqdif_base64_push_placeholder_block ( ctx , " line voltage fundamental angle placeholder(Uab/Ubc/Uca) " , 3 ) ;
placeholder_count = ctx . placeholder_count ;
missing_metric_count = ctx . missing_metric_count ;
missing_metric_names = ctx . missing_metric_names ;
return float_buffer ;
}
std : : vector < float > pqdif_build_delta_float_buffer_for_bucket (
const ParsedPqdifFile & parsed_file ,
const TimeAggregatedStatBucket & bucket ,
StatValueKind kind ,
size_t & placeholder_count ,
size_t & missing_metric_count ,
std : : vector < std : : string > & missing_metric_names )
{
std : : vector < float > float_buffer ;
// Delta 角型与 Wye 星型必须保持完全相同的 float 数量,便于后续统一解码。
float_buffer . reserve ( 2463 ) ;
PqdifBase64BuildContext ctx ( parsed_file , bucket , kind , float_buffer ) ;
// 1) 三相电压有效值、三相电流有效值、三线电压有效值
pqdif_base64_push_metric ( ctx , StatMetricId : : UaRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UbRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UcRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IaRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IbRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IcRms ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UabRms ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UbcRms ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UcaRms ) ;
// 2) 三相电压偏差占位 + 三线电压偏差
pqdif_base64_push_placeholder_block ( ctx , " phase voltage deviation placeholder(Ua/Ub/Uc) " , 3 ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UabDeviation ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UbcDeviation ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UcaDeviation ) ;
// 3) 频率偏差 + 频率
pqdif_base64_push_metric ( ctx , StatMetricId : : FrequencyDeviation ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : Frequency ) ;
// 4) 电压零/负/正序和负序不平衡
pqdif_base64_push_metric ( ctx , StatMetricId : : UZeroSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UNegSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UPosSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : UNegSeqUnbalance ) ;
// 5) 电流零/负/正序和负序不平衡
pqdif_base64_push_metric ( ctx , StatMetricId : : IZeroSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : INegSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IPosSeq ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : INegSeqUnbalance ) ;
// 6) 三相电压谐波 2-50 次指标占位
pqdif_base64_push_placeholder_block ( ctx , " phase voltage harmonic placeholder(Ua/Ub/Uc,2-50) " , 49 * 3 ) ;
// 7) 三相电流谐波 2-50 次指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : CurrentHarmonic , 2 , 50 ) ;
// 8) 三线电压谐波 2-50 次指标
pqdif_base64_push_dynamic_three_line_phases_with_delta_fallback ( ctx , StatDynamicMetricGroup : : LineVoltageHarmonic , 2 , 50 ) ;
// 9) 三相电压谐波 2-50 次相角指标占位
pqdif_base64_push_placeholder_block ( ctx , " phase voltage harmonic angle placeholder(Ua/Ub/Uc,2-50) " , 49 * 3 ) ;
// 10) 三相电流谐波 2-50 次相角指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : CurrentHarmonicAngle , 2 , 50 ) ;
// 11) 三线电压谐波 2-50 次相角指标
pqdif_base64_push_dynamic_three_line_phases_with_delta_fallback ( ctx , StatDynamicMetricGroup : : LineVoltageHarmonicAngle , 2 , 50 ) ;
// 12) 三相电压间谐波 0.5-49.5 次指标占位
pqdif_base64_push_placeholder_block ( ctx , " phase voltage interharmonic placeholder(Ua/Ub/Uc,0.5-49.5) " , 50 * 3 ) ;
// 13) 三相电流间谐波 0.5-49.5 次指标
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : CurrentInterharmonic , 0 , 49 ) ;
// 14) 三线电压间谐波 0.5-49.5 次指标
pqdif_base64_push_dynamic_three_line_phases_with_delta_fallback ( ctx , StatDynamicMetricGroup : : LineVoltageInterharmonic , 0 , 49 ) ;
// 15) A/B/C/总 有功/无功/视在功率
pqdif_base64_push_metric ( ctx , StatMetricId : : PaPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QaPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : SaPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PbPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QbPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : SbPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PcPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QcPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : ScPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PTotalPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QTotalPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : STotalPower ) ;
// 16) A/B/C/总 谐波 2-50 次 有功/无功/视在功率
pqdif_base64_push_harmonic_power_phase ( ctx , 0 ) ;
pqdif_base64_push_harmonic_power_phase ( ctx , 1 ) ;
pqdif_base64_push_harmonic_power_phase ( ctx , 2 ) ;
pqdif_base64_push_harmonic_power_phase ( ctx , 3 ) ;
// 17) 谐波含有率:三相电压占位、三相电流、三线电压
pqdif_base64_push_placeholder_block ( ctx , " phase voltage harmonic ratio placeholder(Ua/Ub/Uc,2-50) " , 49 * 3 ) ;
pqdif_base64_push_dynamic_three_phases ( ctx , StatDynamicMetricGroup : : CurrentHarmonicRatio , 2 , 50 ) ;
pqdif_base64_push_dynamic_three_line_phases_with_delta_fallback ( ctx , StatDynamicMetricGroup : : LineVoltageHarmonicRatio , 2 , 50 ) ;
// 18) 谐波总畸变率:三相电压占位、三相电流、三线电压
pqdif_base64_push_placeholder_block ( ctx , " phase voltage THD placeholder(Ua/Ub/Uc) " , 3 ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IaThd ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IbThd ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IcThd ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UabThd ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UbcThd ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UcaThd ) ;
// 19) 三相功率因数和总功率因数
pqdif_base64_push_metric ( ctx , StatMetricId : : PFa ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PFb ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PFc ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PFTotal ) ;
// 20) 三相基波功率因数和总基波功率因数
pqdif_base64_push_metric ( ctx , StatMetricId : : FundPFa ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : FundPFb ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : FundPFc ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : FundPFTotal ) ;
// 21) 三相电压变动幅值占位 + 三线电压变动幅值
pqdif_base64_push_placeholder_block ( ctx , " phase voltage DVC placeholder(Ua/Ub/Uc) " , 3 ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UabDvc ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UbcDvc ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UcaDvc ) ;
// 22) 三相短闪占位 + 三线短闪
pqdif_base64_push_placeholder_block ( ctx , " phase voltage Pst placeholder(Ua/Ub/Uc) " , 3 ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UabPst ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UbcPst ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UcaPst ) ;
// 23) 三相长闪占位 + 三线长闪
pqdif_base64_push_placeholder_block ( ctx , " phase voltage Plt placeholder(Ua/Ub/Uc) " , 3 ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UabPlt ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UbcPlt ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UcaPlt ) ;
// 24) A/B/C/总 基波有功/无功/视在功率
pqdif_base64_push_metric ( ctx , StatMetricId : : PaFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QaFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : SaFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PbFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QbFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : SbFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PcFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QcFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : ScFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : PTotalFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : QTotalFundPower ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : STotalFundPower ) ;
// 25) 基波有效值:三相基波电压占位、三相基波电流、三线基波电压
pqdif_base64_push_placeholder_block ( ctx , " phase voltage fundamental RMS placeholder(Ua/Ub/Uc) " , 3 ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IaFundRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IbFundRms ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IcFundRms ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UabFundRms ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UbcFundRms ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UcaFundRms ) ;
// 26) 基波相角:三相基波电压相角占位、三相基波电流相角、三线基波电压相角
pqdif_base64_push_placeholder_block ( ctx , " phase voltage fundamental angle placeholder(Ua/Ub/Uc) " , 3 ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IaFundAngle ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IbFundAngle ) ;
pqdif_base64_push_metric ( ctx , StatMetricId : : IcFundAngle ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UabFundAngle ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UbcFundAngle ) ;
pqdif_base64_push_delta_line_metric_with_phase_fallback ( ctx , StatMetricId : : UcaFundAngle ) ;
if ( ctx . delta_line_phase_fallback_count > 0 & & pqdif_log_enabled ( PqdifLogLevel : : Core ) )
{
std : : cout < < " [BASE64 DELTA LINE FALLBACK SUMMARY] time= " < < ctx . bucket . timestamp_text
< < " , kind= " < < stat_value_kind_name ( ctx . kind )
< < " , fallback_count= " < < ctx . delta_line_phase_fallback_count
< < std : : endl ;
}
placeholder_count = ctx . placeholder_count ;
missing_metric_count = ctx . missing_metric_count ;
missing_metric_names = ctx . missing_metric_names ;
return float_buffer ;
}
void pqdif_build_and_queue_base64_records ( ParsedPqdifFile & parsed_file )
{
if ( parsed_file . aggregated_stat_buckets . empty ( ) )
{
std : : cout < < " [PQDIF BASE64] skip: no aggregated buckets, file= "
< < parsed_file . source_file < < std : : endl ;
return ;
}
// Wye 星型按星型顺序组装; Delta 角型按角型顺序组装。
// Unknown 暂按星型顺序组装,避免因为接线方式缺失直接丢弃数据。
const bool use_delta_assembly = ( parsed_file . connection_kind = = ParsedConnectionKind : : Delta ) ;
const char * assembly_name = use_delta_assembly ? " DELTA " : " STAR " ;
const std : : string full_path = pqdif_absolute_path_text ( parsed_file . source_file ) ;
const std : : vector < StatValueKind > kind_order = {
StatValueKind : : Max ,
StatValueKind : : Min ,
StatValueKind : : Avg ,
StatValueKind : : P95
} ;
PqdifStatBase64FileBatch file_batch ;
file_batch . pqdif_file_path = full_path ;
file_batch . source_file = parsed_file . source_file ;
file_batch . mac = parsed_file . mac ;
file_batch . parsed_at = parsed_file . parsed_at ;
file_batch . parsed_at_text = format_time_text ( parsed_file . parsed_at ) ;
file_batch . connection_kind = parsed_file . connection_kind ;
file_batch . time_points . reserve ( parsed_file . aggregated_stat_buckets . size ( ) ) ;
size_t generated = 0 ;
size_t total_placeholders = 0 ;
size_t total_missing_metrics = 0 ;
size_t expected_float_count = 0 ;
std : : cout < < " ========== PQDIF BASE64 ASSEMBLY ========== " < < std : : endl ;
std : : cout < < " file= " < < full_path
< < " , buckets= " < < parsed_file . aggregated_stat_buckets . size ( )
< < " , connection_kind= " < < stat_connection_kind_name ( parsed_file . connection_kind )
< < " , assembly= " < < assembly_name
< < " , kind_order=Max/Min/Avg/P95 "
< < " , queue_mode=file_batch/time_point/records "
< < std : : endl ;
for ( const auto & bucket : parsed_file . aggregated_stat_buckets )
{
PqdifStatBase64TimePointPacket time_packet ;
time_packet . timestamp = bucket . timestamp ;
time_packet . timestamp_text = bucket . timestamp_text ;
time_packet . records . reserve ( kind_order . size ( ) ) ;
for ( StatValueKind kind : kind_order )
{
size_t placeholder_count = 0 ;
size_t missing_metric_count = 0 ;
std : : vector < std : : string > missing_metric_names ;
std : : vector < float > floats = use_delta_assembly ?
pqdif_build_delta_float_buffer_for_bucket (
parsed_file ,
bucket ,
kind ,
placeholder_count ,
missing_metric_count ,
missing_metric_names ) :
pqdif_build_star_float_buffer_for_bucket (
parsed_file ,
bucket ,
kind ,
placeholder_count ,
missing_metric_count ,
missing_metric_names ) ;
if ( expected_float_count = = 0 )
expected_float_count = floats . size ( ) ;
PqdifStatBase64Record rec ;
rec . pqdif_file_path = full_path ;
rec . value_kind = kind ;
rec . value_kind_name = stat_value_kind_name ( kind ) ;
rec . value_kind_code = stat_value_kind_code_for_base64 ( kind ) ;
rec . timestamp = bucket . timestamp ;
rec . timestamp_text = bucket . timestamp_text ;
rec . float_count = floats . size ( ) ;
rec . placeholder_count = placeholder_count ;
rec . connection_kind = parsed_file . connection_kind ;
rec . base64_payload = pqdif_base64_encode_float_vector ( floats ) ;
time_packet . total_float_count + = rec . float_count ;
time_packet . total_placeholder_count + = rec . placeholder_count ;
time_packet . total_base64_chars + = rec . base64_payload . size ( ) ;
time_packet . records . emplace_back ( std : : move ( rec ) ) ;
+ + generated ;
total_placeholders + = placeholder_count ;
total_missing_metrics + = missing_metric_count ;
if ( pqdif_log_enabled ( PqdifLogLevel : : Info ) | | generated < = 4 )
{
const PqdifStatBase64Record & log_rec = time_packet . records . back ( ) ;
std : : cout < < " [BASE64 SUB RECORD] time= " < < bucket . timestamp_text
< < " , kind= " < < log_rec . value_kind_name
< < " , kind_code= " < < log_rec . value_kind_code
< < " , floats= " < < log_rec . float_count
< < " , placeholders= " < < log_rec . placeholder_count
< < " , missing_metrics= " < < missing_metric_count
< < " , base64_len= " < < log_rec . base64_payload . size ( )
< < std : : endl ;
}
if ( pqdif_log_enabled ( PqdifLogLevel : : Info ) & & ! missing_metric_names . empty ( ) )
{
std : : cout < < " [BASE64 MISSING SUMMARY] time= " < < bucket . timestamp_text
< < " , kind= " < < stat_value_kind_name ( kind )
< < " , count= " < < missing_metric_names . size ( )
< < " , first_metrics= " ;
const size_t limit = std : : min < size_t > ( missing_metric_names . size ( ) , 12 ) ;
for ( size_t i = 0 ; i < limit ; + + i )
{
if ( i ! = 0 ) std : : cout < < " / " ;
std : : cout < < missing_metric_names [ i ] ;
}
if ( missing_metric_names . size ( ) > limit )
std : : cout < < " /... " ;
std : : cout < < std : : endl ;
}
}
time_packet . record_count = time_packet . records . size ( ) ;
file_batch . total_record_count + = time_packet . record_count ;
file_batch . total_float_count + = time_packet . total_float_count ;
file_batch . total_placeholder_count + = time_packet . total_placeholder_count ;
file_batch . total_base64_chars + = time_packet . total_base64_chars ;
file_batch . time_points . emplace_back ( std : : move ( time_packet ) ) ;
}
file_batch . time_point_count = file_batch . time_points . size ( ) ;
std : : cout < < " [BASE64 FILE BATCH SUMMARY] generated_records= " < < generated
< < " , time_points= " < < file_batch . time_point_count
< < " , expected_float_count_per_record= " < < expected_float_count
< < " , total_placeholders= " < < total_placeholders
< < " , total_missing_metrics= " < < total_missing_metrics
< < " , total_base64_chars= " < < file_batch . total_base64_chars
< < std : : endl ;
// 按你的要求,入队前完整打印保存对象内部结构:文件批次 -> 时间点 -> Max/Min/Avg/P95 子记录 -> Base64 内容。
// 日志会很长;确认完成后可以临时注释掉这一行,或改成 if (pqdif_log_enabled(PqdifLogLevel::Info)) 包裹。
//pqdif_dump_stat_base64_file_batch_full(file_batch);
push_pqdif_stat_base64_file_batch ( std : : move ( file_batch ) ) ;
std : : cout < < " [BASE64 QUEUE SUMMARY] file_batch_queue_size= " < < GetPqdifStatBase64QueueSize ( )
< < " , total_sub_records_in_queue= " < < GetPqdifStatBase64RecordCountInQueue ( )
< < std : : endl ;
std : : cout < < " ================================================ " < < std : : endl ;
}
bool process_single_pqdif_file ( const fs : : path & file_path , const std : : string & mac )
{
ParsedPqdifFile parsed_file ;
parsed_file . mac = mac ;
parsed_file . source_file = file_path . string ( ) ;
parsed_file . parsed_at = std : : time ( nullptr ) ;
std : : string err ;
if ( ! parse_pqdif_file_full ( file_path . string ( ) , parsed_file . logical_file , err ) )
{
std : : cout < < " [PQDIF] parse failed: " < < file_path . string ( )
< < " reason= " < < err < < std : : endl ;
return false ;
}
dump_logical_summary ( parsed_file ) ;
dump_monitor_settings_probe ( parsed_file ) ;
if ( pqdif_log_enabled ( PqdifLogLevel : : Debug ) )
{
// Debug 级别:打印全部 observation 概览,帮助确认指标是否位于非 Trend observation 中。
dump_observation_list ( parsed_file ) ;
// Debug 级别:打印动态谱类候选通道,用于确认线电压谐波、相角、间谐波是否存在。
dump_dynamic_spectrum_candidate_probe ( parsed_file ) ;
// Debug 级别:打印闪变候选通道,用于确认 Plt 通道是否存在数据点。
dump_flicker_candidate_probe ( parsed_file ) ;
}
if ( pqdif_is_trace_log_enabled ( ) )
{
// Trace 级别:保留旧的疑似谐波全量探针;日志量更大,仅排查复杂问题时打开。
dump_harmonic_channel_probe ( parsed_file ) ;
}
// 1) 识别接线方式。
// 后续指标判断需要先区分星型 / 角型两套规则。
parsed_file . connection_kind = stat_classify_connection_kind ( parsed_file . logical_file ) ;
// 2) 只筛选“统计类 observation”, 并对其展开目标统计指标。
// 当前阶段不处理全部 observation, 避免不同 observation 混入同一时间聚合结果。
parsed_file . expanded_stat_points = stat_expand_selected_statistical_observation (
parsed_file . logical_file ,
parsed_file . connection_kind ,
parsed_file . selected_observation_index ,
parsed_file . selected_observation_name ) ;
// 3) 对已筛选出的统计 observation, 按 timestamp 聚合。
parsed_file . aggregated_stat_buckets =
stat_group_points_by_timestamp ( parsed_file . expanded_stat_points ) ;
if ( pqdif_is_trace_log_enabled ( ) )
dump_semantic_probe ( parsed_file ) ;
dump_expanded_stat_preview ( parsed_file ) ;
// 分组结果平时也要打印核心摘要;开启详细日志时才会打印完整明细。
dump_grouped_bucket_preview ( parsed_file ) ;
// 4) 将每个时间桶按接线方式组装为 Max/Min/Avg/P95 四条 Base64 暂存记录。
// Wye 使用星型顺序, Delta 使用角型顺序;两者 float 数量保持一致。
pqdif_build_and_queue_base64_records ( parsed_file ) ;
if ( ! push_parsed_result_to_cache ( std : : move ( parsed_file ) ) )
{
std : : cout < < " [PQDIF] push cache failed: " < < file_path . string ( ) < < std : : endl ;
return false ;
}
std : : cout < < " [PQDIF] processed ok: " < < file_path . string ( )
< < " , cache_size= " < < GetParsedPqdifCacheSize ( )
< < std : : endl ;
return true ;
}
struct PendingPqdifFile
{
fs : : path path ;
std : : string mac ;
} ;
void scan_once ( )
{
ensure_dir ( kPqdRootDir ) ;
ensure_dir ( kDoneRootDir ) ;
ensure_dir ( kFailRootDir ) ;
std : : error_code ec ;
if ( ! fs : : exists ( kPqdRootDir , ec ) | | ! fs : : is_directory ( kPqdRootDir , ec ) )
return ;
std : : vector < PendingPqdifFile > pending_files ;
// 先收集所有 MAC 目录下的 PQDIF 文件,但本轮不会全部处理。
// 后面会按文件修改时间排序,并最多处理 kMaxPqdifFilesPerScan 个文件。
for ( fs : : directory_iterator mac_it ( kPqdRootDir , ec ) , mac_end ; ! ec & & mac_it ! = mac_end ; mac_it . increment ( ec ) )
{
if ( ec | | ! fs : : is_directory ( mac_it - > path ( ) ) )
continue ;
const std : : string mac = mac_it - > path ( ) . filename ( ) . string ( ) ;
std : : error_code file_ec ;
for ( fs : : directory_iterator file_it ( mac_it - > path ( ) , file_ec ) , file_end ; ! file_ec & & file_it ! = file_end ; file_it . increment ( file_ec ) )
{
if ( file_ec | | ! is_pqdif_file ( file_it - > path ( ) ) )
continue ;
PendingPqdifFile item ;
item . path = file_it - > path ( ) ;
item . mac = mac ;
pending_files . push_back ( item ) ;
}
}
if ( pending_files . empty ( ) )
return ;
// 旧文件优先处理,避免目录中长期积压的旧 PQDIF 一直排在后面。
std : : sort ( pending_files . begin ( ) , pending_files . end ( ) , [ ] ( const PendingPqdifFile & a , const PendingPqdifFile & b ) {
std : : error_code ea , eb ;
const auto ta = fs : : last_write_time ( a . path , ea ) ;
const auto tb = fs : : last_write_time ( b . path , eb ) ;
if ( ea & & ! eb )
return false ;
if ( ! ea & & eb )
return true ;
if ( ! ea & & ! eb & & ta ! = tb )
return ta < tb ;
return a . path . string ( ) < b . path . string ( ) ;
} ) ;
int processed_count = 0 ;
for ( const auto & item : pending_files )
{
if ( processed_count > = kMaxPqdifFilesPerScan )
break ;
std : : cout < < " [PQDIF] scan selected file: " < < item . path . string ( )
< < " , pending_count= " < < pending_files . size ( )
< < " , max_per_scan= " < < kMaxPqdifFilesPerScan
< < std : : endl ;
const bool ok = process_single_pqdif_file ( item . path , item . mac ) ;
const fs : : path target_root = ok ? fs : : path ( kDoneRootDir ) : fs : : path ( kFailRootDir ) ;
const fs : : path dst = target_root / item . mac / item . path . filename ( ) ;
// 处理完成后移动文件,避免下一轮 scan_once() 重复解析同一个 PQDIF。
// 解析成功: download/<mac>/<file>.pqd -> download_done/<mac>/<file>.pqd
// 解析失败: download/<mac>/<file>.pqd -> download_fail/<mac>/<file>.pqd
//
// 调试时如果想让文件保留在 download 目录中、方便反复解析同一个文件,
// 可以临时注释掉下面这个 if 块;调试结束后建议恢复,否则每一轮都会重复解析。
if ( ! move_file_with_fallback ( item . path , dst ) )
{
std : : cout < < " [PQDIF] move failed: "
< < item . path . string ( ) < < " -> " < < dst . string ( )
< < std : : endl ;
}
cleanup_backup_dir ( fs : : path ( kDoneRootDir ) / item . mac , kBackupLimit ) ;
cleanup_backup_dir ( fs : : path ( kFailRootDir ) / item . mac , kBackupLimit ) ;
+ + processed_count ;
}
}
} // namespace
bool PopOldestParsedPqdifFile ( ParsedPqdifFile & out )
{
std : : lock_guard < std : : mutex > guard ( g_parsed_cache_mutex ) ;
if ( g_parsed_cache . empty ( ) )
return false ;
out = std : : move ( g_parsed_cache . front ( ) ) ;
g_parsed_cache . pop_front ( ) ;
return true ;
}
bool PeekOldestParsedPqdifFile ( ParsedPqdifFile & out )
{
std : : lock_guard < std : : mutex > guard ( g_parsed_cache_mutex ) ;
if ( g_parsed_cache . empty ( ) )
return false ;
out = g_parsed_cache . front ( ) ;
return true ;
}
size_t GetParsedPqdifCacheSize ( )
{
std : : lock_guard < std : : mutex > guard ( g_parsed_cache_mutex ) ;
return g_parsed_cache . size ( ) ;
}
void ClearParsedPqdifCache ( )
{
2026-04-22 10:40:19 +08:00
std : : lock_guard < std : : mutex > guard ( g_parsed_cache_mutex ) ;
g_parsed_cache . clear ( ) ;
}
2026-04-29 13:35:49 +08:00
bool PopOldestPqdifStatBase64FileBatch ( PqdifStatBase64FileBatch & out )
{
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
if ( g_pqdif_stat_base64_queue . empty ( ) )
return false ;
out = std : : move ( g_pqdif_stat_base64_queue . front ( ) ) ;
g_pqdif_stat_base64_queue . pop_front ( ) ;
return true ;
}
bool PeekOldestPqdifStatBase64FileBatch ( PqdifStatBase64FileBatch & out )
{
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
if ( g_pqdif_stat_base64_queue . empty ( ) )
return false ;
out = g_pqdif_stat_base64_queue . front ( ) ;
return true ;
}
bool PopOldestPqdifStatBase64Record ( PqdifStatBase64Record & out )
{
// 兼容旧接口:从“文件级批次队列”的最早文件、最早时间点中拆出一条子记录。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
while ( ! g_pqdif_stat_base64_queue . empty ( ) )
{
PqdifStatBase64FileBatch & batch = g_pqdif_stat_base64_queue . front ( ) ;
while ( ! batch . time_points . empty ( ) & & batch . time_points . front ( ) . records . empty ( ) )
batch . time_points . erase ( batch . time_points . begin ( ) ) ;
if ( batch . time_points . empty ( ) )
{
g_pqdif_stat_base64_queue . pop_front ( ) ;
continue ;
}
PqdifStatBase64TimePointPacket & tp = batch . time_points . front ( ) ;
out = std : : move ( tp . records . front ( ) ) ;
const size_t out_base64_len = out . base64_payload . size ( ) ;
if ( tp . record_count > 0 ) - - tp . record_count ;
if ( tp . total_float_count > = out . float_count ) tp . total_float_count - = out . float_count ;
if ( tp . total_placeholder_count > = out . placeholder_count ) tp . total_placeholder_count - = out . placeholder_count ;
if ( tp . total_base64_chars > = out_base64_len ) tp . total_base64_chars - = out_base64_len ;
if ( batch . total_record_count > 0 ) - - batch . total_record_count ;
if ( batch . total_float_count > = out . float_count ) batch . total_float_count - = out . float_count ;
if ( batch . total_placeholder_count > = out . placeholder_count ) batch . total_placeholder_count - = out . placeholder_count ;
if ( batch . total_base64_chars > = out_base64_len ) batch . total_base64_chars - = out_base64_len ;
tp . records . erase ( tp . records . begin ( ) ) ;
if ( tp . records . empty ( ) )
{
batch . time_points . erase ( batch . time_points . begin ( ) ) ;
if ( batch . time_point_count > 0 ) - - batch . time_point_count ;
}
if ( batch . time_points . empty ( ) )
g_pqdif_stat_base64_queue . pop_front ( ) ;
return true ;
}
return false ;
}
bool PeekOldestPqdifStatBase64Record ( PqdifStatBase64Record & out )
{
// 兼容旧接口:只查看“文件级批次队列”的最早文件、最早时间点中的第一条子记录。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
for ( const auto & batch : g_pqdif_stat_base64_queue )
{
for ( const auto & tp : batch . time_points )
{
if ( ! tp . records . empty ( ) )
{
out = tp . records . front ( ) ;
return true ;
}
}
}
return false ;
}
size_t GetPqdifStatBase64QueueSize ( )
{
// v20 起返回文件级批次数量,不再是子记录数量。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
return g_pqdif_stat_base64_queue . size ( ) ;
}
size_t GetPqdifStatBase64RecordCountInQueue ( )
{
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
return pqdif_stat_base64_count_records_in_queue_unlocked ( ) ;
}
void ClearPqdifStatBase64Queue ( )
{
// 清空 Base64 生成队列。
// 对象用途:调试或重置解析状态时使用;不会清空待后续处理队列。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_mutex ) ;
g_pqdif_stat_base64_queue . clear ( ) ;
}
bool PopReadyPqdifStatBase64FileBatch ( PqdifStatBase64FileBatch & out )
{
// 从“待后续处理队列”取出一个完整 PQDIF 文件批次。
// 对象用途:后续入库/上传/推送逻辑应优先调用这个接口,而不是直接访问全局 deque。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_ready_mutex ) ;
if ( g_pqdif_stat_base64_ready_queue . empty ( ) )
return false ;
out = std : : move ( g_pqdif_stat_base64_ready_queue . front ( ) ) ;
g_pqdif_stat_base64_ready_queue . pop_front ( ) ;
return true ;
}
bool PeekReadyPqdifStatBase64FileBatch ( PqdifStatBase64FileBatch & out )
{
// 查看“待后续处理队列”最早的 PQDIF 文件批次,但不移除。
// 对象用途:仅检查当前准备处理的数据内容,适合调试或状态展示。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_ready_mutex ) ;
if ( g_pqdif_stat_base64_ready_queue . empty ( ) )
return false ;
out = g_pqdif_stat_base64_ready_queue . front ( ) ;
return true ;
}
size_t GetReadyPqdifStatBase64QueueSize ( )
{
// 返回待后续处理队列中的文件级批次数量。
// 注意:返回的是文件数量,不是 Max/Min/Avg/P95 子记录数量。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_ready_mutex ) ;
return g_pqdif_stat_base64_ready_queue . size ( ) ;
}
size_t GetReadyPqdifStatBase64RecordCountInQueue ( )
{
// 返回待后续处理队列中全部文件批次包含的 Base64 子记录数量。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_ready_mutex ) ;
return pqdif_stat_base64_count_records_in_queue_unlocked ( g_pqdif_stat_base64_ready_queue ) ;
}
void ClearReadyPqdifStatBase64Queue ( )
{
// 清空待后续处理队列。
// 对象用途:调试或重置后续处理状态时使用;不会清空解析生成队列。
std : : lock_guard < std : : mutex > guard ( g_pqdif_stat_base64_ready_mutex ) ;
g_pqdif_stat_base64_ready_queue . clear ( ) ;
}
2026-04-22 10:40:19 +08:00
void RunPqdifScanLoop ( )
{
std : : cout < < " [PQDIF] scan loop started, root= " < < kPqdRootDir
< < " , interval= " < < kScanIntervalSec < < " s "
< < std : : endl ;
while ( true )
{
try
{
scan_once ( ) ;
}
catch ( const std : : exception & ex )
{
std : : cout < < " [PQDIF] scan exception: " < < ex . what ( ) < < std : : endl ;
}
catch ( . . . )
{
std : : cout < < " [PQDIF] scan exception: unknown " < < std : : endl ;
}
2026-04-29 13:35:49 +08:00
try
{
// 循环最后,检查 Base64 生成队列中是否已经存在一个完整 PQDIF 文件批次。
// 如果存在,则取出最多一个并移动到“待后续处理队列”。
// 后续业务处理请调用 PopReadyPqdifStatBase64FileBatch() 从待处理队列取数据。
pqdif_move_one_generated_base64_batch_to_ready_queue ( ) ;
PqdifStatBase64FileBatch batch ;
if ( PopReadyPqdifStatBase64FileBatch ( batch ) ) {
// batch 就是一个 PQDIF 文件完整的 Base64 组装结果
// 在此处处理上送逻辑
}
}
catch ( const std : : exception & ex )
{
std : : cout < < " [PQDIF BASE64 READY] move exception: " < < ex . what ( ) < < std : : endl ;
}
catch ( . . . )
{
std : : cout < < " [PQDIF BASE64 READY] move exception: unknown " < < std : : endl ;
}
2026-04-22 10:40:19 +08:00
std : : this_thread : : sleep_for ( std : : chrono : : seconds ( kScanIntervalSec ) ) ;
}
2026-04-29 13:35:49 +08:00
}