完成工具

This commit is contained in:
lnk
2026-07-13 15:12:55 +08:00
parent b61388443f
commit 9181e2d4e9
77 changed files with 1847801 additions and 20269 deletions

288
source/icd_mapper.cpp Normal file
View File

@@ -0,0 +1,288 @@
#include "pq_app.h"
#include "tinyxml2.h"
#include <windows.h>
#include <algorithm>
#include <cctype>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <sstream>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
namespace fs = std::filesystem;
namespace {
std::string readAllBinary(const fs::path& path) {
std::ifstream ifs(path, std::ios::binary);
if (!ifs) throw std::runtime_error("Failed to open file: " + path.string());
std::ostringstream ss;
ss << ifs.rdbuf();
return ss.str();
}
void writeAllBinary(const fs::path& path, const std::string& text) {
std::ofstream ofs(path, std::ios::binary);
if (!ofs) throw std::runtime_error("Failed to write file: " + path.string());
ofs << text;
}
std::string lowerAscii(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
return s;
}
bool declaresGbEncoding(const std::string& text) {
std::string head = lowerAscii(text.substr(0, std::min<size_t>(text.size(), 256)));
return head.find("gb2312") != std::string::npos ||
head.find("gbk") != std::string::npos ||
head.find("gb18030") != std::string::npos;
}
bool isLikelyUtf8(const std::string& text) {
int remaining = 0;
for (unsigned char c : text) {
if (remaining == 0) {
if ((c & 0x80) == 0) continue;
if ((c & 0xE0) == 0xC0) remaining = 1;
else if ((c & 0xF0) == 0xE0) remaining = 2;
else if ((c & 0xF8) == 0xF0) remaining = 3;
else return false;
} else {
if ((c & 0xC0) != 0x80) return false;
--remaining;
}
}
return remaining == 0;
}
std::string ansiToUtf8(const std::string& text) {
if (text.empty()) return "";
int wideLen = MultiByteToWideChar(936, 0, text.data(), (int)text.size(), nullptr, 0);
if (wideLen <= 0) return text;
std::wstring wide(wideLen, L'\0');
MultiByteToWideChar(936, 0, text.data(), (int)text.size(), wide.data(), wideLen);
int utf8Len = WideCharToMultiByte(CP_UTF8, 0, wide.data(), wideLen, nullptr, 0, nullptr, nullptr);
std::string out(utf8Len, '\0');
WideCharToMultiByte(CP_UTF8, 0, wide.data(), wideLen, out.data(), utf8Len, nullptr, nullptr);
return out;
}
std::string loadXmlText(const fs::path& path) {
std::string raw = readAllBinary(path);
if (declaresGbEncoding(raw) || !isLikelyUtf8(raw)) return ansiToUtf8(raw);
return raw;
}
std::string localName(const char* name) {
if (!name) return "";
std::string s(name);
size_t pos = s.find(':');
return pos == std::string::npos ? s : s.substr(pos + 1);
}
std::string attr(const tinyxml2::XMLElement* e, const char* name) {
const char* v = e ? e->Attribute(name) : nullptr;
return v ? v : "";
}
void collectIcdDescriptions(const tinyxml2::XMLElement* element,
std::unordered_map<std::string, std::string>& descByDo) {
if (!element) return;
if (localName(element->Name()) == "LN") {
std::string lnClass = attr(element, "lnClass");
std::string inst = attr(element, "inst");
if (!lnClass.empty()) {
for (const tinyxml2::XMLElement* child = element->FirstChildElement();
child;
child = child->NextSiblingElement()) {
if (localName(child->Name()) != "DOI") continue;
std::string doiName = attr(child, "name");
std::string desc = attr(child, "desc");
if (doiName.empty() || desc.empty()) continue;
std::string key = lnClass + inst + "$MX$" + doiName;
descByDo[key] = desc;
}
}
}
for (const tinyxml2::XMLElement* child = element->FirstChildElement();
child;
child = child->NextSiblingElement()) {
collectIcdDescriptions(child, descByDo);
}
}
std::unordered_map<std::string, std::string> parseIcdDescriptions(const fs::path& icdPath) {
tinyxml2::XMLDocument doc;
std::string xml = loadXmlText(icdPath);
tinyxml2::XMLError rc = doc.Parse(xml.c_str(), xml.size());
if (rc != tinyxml2::XML_SUCCESS) {
throw std::runtime_error("Failed to parse ICD XML: " + std::to_string((int)rc));
}
std::unordered_map<std::string, std::string> descByDo;
collectIcdDescriptions(doc.RootElement(), descByDo);
return descByDo;
}
std::string cellText(const tinyxml2::XMLElement* cell) {
const tinyxml2::XMLElement* data = cell ? cell->FirstChildElement("Data") : nullptr;
const char* text = data ? data->GetText() : nullptr;
return text ? text : "";
}
int cellIndex(const tinyxml2::XMLElement* cell, int fallback) {
const char* idx = cell ? cell->Attribute("ss:Index") : nullptr;
return idx && *idx ? std::max(1, std::atoi(idx)) : fallback;
}
tinyxml2::XMLElement* cellAt(tinyxml2::XMLElement* row, int col) {
int current = 1;
for (tinyxml2::XMLElement* cell = row ? row->FirstChildElement("Cell") : nullptr;
cell;
cell = cell->NextSiblingElement("Cell")) {
current = cellIndex(cell, current);
if (current == col) return cell;
++current;
}
return nullptr;
}
tinyxml2::XMLElement* ensureCell(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* row, int col) {
if (tinyxml2::XMLElement* existing = cellAt(row, col)) return existing;
tinyxml2::XMLElement* cell = doc.NewElement("Cell");
cell->SetAttribute("ss:Index", col);
row->InsertEndChild(cell);
return cell;
}
void setCellText(tinyxml2::XMLDocument& doc, tinyxml2::XMLElement* cell, const std::string& text) {
tinyxml2::XMLElement* data = cell->FirstChildElement("Data");
if (!data) {
data = doc.NewElement("Data");
data->SetAttribute("ss:Type", "String");
cell->InsertEndChild(data);
}
data->SetText(text.c_str());
}
std::vector<std::string> sortedKeysByLength(const std::unordered_map<std::string, std::string>& values) {
std::vector<std::string> keys;
keys.reserve(values.size());
for (const auto& kv : values) keys.push_back(kv.first);
std::sort(keys.begin(), keys.end(), [](const std::string& a, const std::string& b) {
if (a.size() != b.size()) return a.size() > b.size();
return a < b;
});
return keys;
}
bool pathMatchesDo(const std::string& rawPath, const std::string& doKey) {
return rawPath == doKey ||
(rawPath.size() > doKey.size() &&
rawPath.compare(0, doKey.size(), doKey) == 0 &&
rawPath[doKey.size()] == '$');
}
std::string descForPath(const std::string& rawPath,
const std::unordered_map<std::string, std::string>& descByDo,
const std::vector<std::string>& orderedKeys) {
for (const auto& key : orderedKeys) {
if (pathMatchesDo(rawPath, key)) {
auto it = descByDo.find(key);
return it == descByDo.end() ? "" : it->second;
}
}
return "";
}
tinyxml2::XMLElement* findWorksheet(tinyxml2::XMLDocument& doc, const char* sheetName) {
tinyxml2::XMLElement* workbook = doc.FirstChildElement("Workbook");
for (tinyxml2::XMLElement* ws = workbook ? workbook->FirstChildElement("Worksheet") : nullptr;
ws;
ws = ws->NextSiblingElement("Worksheet")) {
const char* name = ws->Attribute("ss:Name");
if (name && std::string(name) == sheetName) return ws;
}
return nullptr;
}
int headerColumn(tinyxml2::XMLElement* headerRow, const std::string& title) {
int current = 1;
for (tinyxml2::XMLElement* cell = headerRow ? headerRow->FirstChildElement("Cell") : nullptr;
cell;
cell = cell->NextSiblingElement("Cell")) {
current = cellIndex(cell, current);
if (cellText(cell) == title) return current;
++current;
}
return 0;
}
} // namespace
bool pqApplyIcdDescriptionsToWorkbook(const std::string& icdPath,
const std::string& workbookPath,
std::string* message) {
try {
auto descByDo = parseIcdDescriptions(fs::u8path(icdPath));
if (descByDo.empty()) {
if (message) *message = "ICD 中没有解析到 LN/DOI desc";
return false;
}
std::vector<std::string> orderedKeys = sortedKeysByLength(descByDo);
tinyxml2::XMLDocument doc;
std::string workbookXml = readAllBinary(fs::u8path(workbookPath));
tinyxml2::XMLError rc = doc.Parse(workbookXml.c_str(), workbookXml.size());
if (rc != tinyxml2::XML_SUCCESS) {
if (message) *message = "表格文件解析失败: " + std::to_string((int)rc);
return false;
}
tinyxml2::XMLElement* sheet = findWorksheet(doc, "未匹配的原始数据");
if (!sheet) {
if (message) *message = "没有找到 sheet: 未匹配的原始数据";
return false;
}
tinyxml2::XMLElement* table = sheet->FirstChildElement("Table");
tinyxml2::XMLElement* header = table ? table->FirstChildElement("Row") : nullptr;
int pathCol = headerColumn(header, "原始路径");
int icdCol = headerColumn(header, "icd中代表的字段");
if (pathCol <= 0 || icdCol <= 0) {
if (message) *message = "未匹配 sheet 缺少 原始路径 或 icd中代表的字段 列";
return false;
}
int matched = 0;
for (tinyxml2::XMLElement* row = header ? header->NextSiblingElement("Row") : nullptr;
row;
row = row->NextSiblingElement("Row")) {
tinyxml2::XMLElement* pathCell = cellAt(row, pathCol);
std::string rawPath = cellText(pathCell);
if (rawPath.empty()) continue;
std::string desc = descForPath(rawPath, descByDo, orderedKeys);
if (desc.empty()) continue;
tinyxml2::XMLElement* icdCell = ensureCell(doc, row, icdCol);
setCellText(doc, icdCell, desc);
++matched;
}
tinyxml2::XMLPrinter printer;
doc.Print(&printer);
writeAllBinary(fs::u8path(workbookPath), printer.CStr());
if (message) {
*message = "ICD 导入完成DOI=" + std::to_string(descByDo.size()) +
",填入未匹配原始数据=" + std::to_string(matched);
}
return true;
} catch (const std::exception& e) {
if (message) *message = e.what();
return false;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -7,9 +7,11 @@
#include <fstream>
#include <iomanip>
#include <functional>
#include <limits>
#include <iostream>
#include <map>
#include <regex>
#include <set>
#include <sstream>
#include <string>
#include <unordered_map>
@@ -593,23 +595,30 @@ static const ValueMeta* findMetaForKey(
if (exactCandidates.empty()) collectByName(metas, secondary, wiring, filter, exactCandidates);
return bestMeta(exactCandidates, pk);
}
// 把 XML 中序列 DA如 phs*Har[%-2]$mag$f改显示成 phs*Har[0-48]$mag$f
static bool parseDaValueOffset(const std::string& daPath, int& offset) {
std::smatch m;
std::regex re(R"(%(-?\d+))");
if (!std::regex_search(daPath, m, re)) return false;
offset = std::stoi(m[1].str());
return true;
}
// 把 XML 中序列 DA如 phs*Har[%-2]$mag$f按“数组基准 + 取值偏移量”改显示成 phs*Har[0-48]$mag$f
static std::string rewriteDaRangeForDisplay(const std::string& daPath,
const ValueMeta& vm,
int keyStart,
int keyEnd) {
if (!vm.isSequence) return daPath;
// 规则:
// XML 模板里是 [%-2]
// key 是 [2-50]
// 显示要变成 [0-48]
// 即keyStart - vm.seqStart, keyEnd - vm.seqStart
int dispStart = keyStart - vm.seqStart;
int dispEnd = keyEnd - vm.seqStart;
(void)keyStart;
(void)keyEnd;
int valueOffset = 0;
parseDaValueOffset(daPath, valueOffset);
int dispStart = vm.seqStart + valueOffset;
int dispEnd = vm.seqEnd + valueOffset;
std::smatch m;
std::regex re(R"(\[%-(\d+)\])");
std::regex re(R"(\[%(-?\d+)\])");
if (std::regex_search(daPath, m, re)) {
std::ostringstream oss;
oss << "[" << dispStart << "-" << dispEnd << "]";
@@ -693,6 +702,13 @@ struct SheetSpec {
std::string frontType;
};
struct SheetStats {
std::string sheetName;
int normal = 0;
int abnormal = 0;
int missing = 0;
};
struct OriginRecord {
std::string path;
std::string value;
@@ -700,8 +716,16 @@ struct OriginRecord {
int sourceOrder = 0;
};
struct OriginHeader {
std::string title;
std::string fileName;
int sourceOrder = 0;
std::vector<HeaderItem> fields;
};
struct OriginData {
std::vector<OriginRecord> records;
std::vector<OriginHeader> headers;
std::unordered_map<std::string, std::vector<size_t>> pathToRecords;
std::unordered_set<size_t> used;
};
@@ -710,6 +734,15 @@ struct OriginMatch {
std::string path;
std::string value;
std::string reportName;
int sourceOrder = -1;
int rangeStart = std::numeric_limits<int>::min();
int rangeEnd = std::numeric_limits<int>::min();
std::vector<int> valueIndexes;
};
struct ComparePlan {
int originPickStart = std::numeric_limits<int>::min();
int originPickEnd = std::numeric_limits<int>::min();
};
struct JsonDataBucket {
@@ -741,6 +774,51 @@ static std::string reportNameFromRoot(const json& root, const fs::path& originPa
return originPath.filename().u8string();
}
static const std::vector<std::string>& originHeaderKeys() {
static const std::vector<std::string> keys = {
"rpt_no",
"data_time",
"mp_id",
"rpt_id",
"voltage_level",
"v_wiring_type"
};
return keys;
}
static const std::vector<std::string>& originHeaderDisplayKeys() {
static const std::vector<std::string> keys = {
"rpt_no",
"data_time",
"mp_id",
"voltage_level",
"v_wiring_type"
};
return keys;
}
static std::string rootHeaderValue(const json& root, const std::string& key) {
if (!root.is_object()) return "";
auto it = root.find(key);
if (it == root.end()) return "";
return jsonScalarForOutput(*it);
}
static void appendOriginHeader(OriginData& data,
const fs::path& originPath,
int sourceOrder,
const json& root,
const std::string& reportName) {
OriginHeader header;
header.title = reportName.empty() ? originPath.filename().u8string() : reportName;
header.fileName = originPath.filename().u8string();
header.sourceOrder = sourceOrder;
for (const auto& key : originHeaderKeys()) {
header.fields.push_back({key, rootHeaderValue(root, key)});
}
data.headers.push_back(std::move(header));
}
static std::vector<json> parseJsonRoots(const std::string& text) {
std::vector<json> roots;
size_t pos = 0;
@@ -787,6 +865,7 @@ static std::vector<json> parseJsonRoots(const std::string& text) {
static void appendOriginRoot(OriginData& data, const fs::path& originPath, int sourceOrder, const json& root) {
std::string reportName = reportNameFromRoot(root, originPath, sourceOrder);
appendOriginHeader(data, originPath, sourceOrder, root, reportName);
const json* mms = &root;
if (root.is_object() && root.contains("mms_json") && root["mms_json"].is_object()) {
@@ -908,7 +987,9 @@ static std::vector<std::string> makeOriginPathCandidates(const std::string& doPa
std::string primary = makeOriginPath(doPath, daPath, stat, phase, wiring);
if (!primary.empty()) paths.push_back(primary);
return paths;
}static bool parseOriginRangePath(const std::string& path,
}
static bool parseOriginRangePath(const std::string& path,
std::string& before,
int& start,
int& end,
@@ -923,12 +1004,38 @@ static std::vector<std::string> makeOriginPathCandidates(const std::string& doPa
return start <= end;
}
static bool parseOriginIndexedPath(const std::string& path,
const std::string& before,
const std::string& after,
int& index) {
if (path.size() < before.size() + after.size() + 3) return false;
if (path.compare(0, before.size(), before) != 0) return false;
if (!after.empty() &&
path.compare(path.size() - after.size(), after.size(), after) != 0) {
return false;
}
size_t midStart = before.size();
size_t midEnd = after.empty() ? path.size() : path.size() - after.size();
if (midEnd <= midStart + 2 || path[midStart] != '[' || path[midEnd - 1] != ']') return false;
std::string number = path.substr(midStart + 1, midEnd - midStart - 2);
if (number.empty() || !std::all_of(number.begin(), number.end(), [](unsigned char ch) {
return std::isdigit(ch) != 0;
})) {
return false;
}
index = std::stoi(number);
return true;
}
static OriginMatch matchOrigin(OriginData* origin,
const std::string& doPath,
const std::string& daPath,
const std::string& stat,
const std::string& phase,
const std::string& wiring) {
const std::string& wiring,
int pickStartOverride = std::numeric_limits<int>::min(),
int pickEndOverride = std::numeric_limits<int>::min()) {
OriginMatch match;
if (!origin) return match;
@@ -938,25 +1045,75 @@ static OriginMatch matchOrigin(OriginData* origin,
std::string before, after;
int start = 0, end = 0;
if (parseOriginRangePath(path, before, start, end, after)) {
std::ostringstream values;
bool any = false;
std::string reportName;
for (int i = start; i <= end; ++i) {
if (i != start) values << ",";
std::string onePath = before + "[" + std::to_string(i) + "]" + after;
const OriginRecord* rec = firstUnusedRecord(origin, onePath);
if (rec) {
any = true;
values << rec->value;
if (reportName.empty()) reportName = rec->reportName;
markRecordUsed(origin, rec);
int pickStart = pickStartOverride == std::numeric_limits<int>::min() ? start : pickStartOverride;
int pickEnd = pickEndOverride == std::numeric_limits<int>::min() ? end : pickEndOverride;
struct IndexedRecord {
int index = 0;
size_t recordIndex = 0;
};
std::map<int, std::vector<IndexedRecord>> bySource;
for (size_t recIndex = 0; recIndex < origin->records.size(); ++recIndex) {
int indexedPath = 0;
if (!parseOriginIndexedPath(origin->records[recIndex].path, before, after, indexedPath)) continue;
bySource[origin->records[recIndex].sourceOrder].push_back({indexedPath, recIndex});
}
int bestSource = -1;
int bestIntersection = 0;
bool bestHasPickStart = false;
for (const auto& kv : bySource) {
int intersection = 0;
bool hasPickStart = false;
for (const auto& rec : kv.second) {
if (rec.index >= pickStart && rec.index <= pickEnd) ++intersection;
if (rec.index == pickStart) hasPickStart = true;
}
if (intersection <= 0) continue;
if (bestSource < 0 ||
(hasPickStart && !bestHasPickStart) ||
(hasPickStart == bestHasPickStart && intersection > bestIntersection)) {
bestSource = kv.first;
bestIntersection = intersection;
bestHasPickStart = hasPickStart;
}
}
if (any) {
match.path = path;
match.value = values.str();
match.reportName = reportName;
return match;
if (bestSource >= 0) {
std::map<int, size_t> indexToRecord;
int displayStart = std::numeric_limits<int>::max();
int displayEnd = std::numeric_limits<int>::min();
for (const auto& rec : bySource[bestSource]) {
if (indexToRecord.count(rec.index)) continue;
indexToRecord[rec.index] = rec.recordIndex;
displayStart = std::min(displayStart, rec.index);
displayEnd = std::max(displayEnd, rec.index);
}
std::ostringstream values;
bool any = false;
std::string reportName;
for (int i = displayStart; i <= displayEnd; ++i) {
if (i != displayStart) values << ",";
match.valueIndexes.push_back(i);
auto it = indexToRecord.find(i);
if (it == indexToRecord.end()) continue;
const OriginRecord& rec = origin->records[it->second];
any = true;
values << rec.value;
if (reportName.empty()) reportName = rec.reportName;
markRecordUsed(origin, &rec);
}
if (any) {
match.path = before + "[" + std::to_string(displayStart) + "-" + std::to_string(displayEnd) + "]" + after;
match.value = values.str();
match.reportName = reportName;
match.sourceOrder = bestSource;
match.rangeStart = displayStart;
match.rangeEnd = displayEnd;
return match;
}
}
continue;
}
@@ -966,13 +1123,16 @@ static OriginMatch matchOrigin(OriginData* origin,
match.path = path;
match.value = rec->value;
match.reportName = rec->reportName;
match.sourceOrder = rec->sourceOrder;
markRecordUsed(origin, rec);
return match;
}
}
return match;
}static size_t utf8CharLen(unsigned char c) {
}
static size_t utf8CharLen(unsigned char c) {
if (c < 0x80) return 1;
if ((c & 0xE0) == 0xC0) return 2;
if ((c & 0xF0) == 0xE0) return 3;
@@ -1367,20 +1527,87 @@ static bool hasNullValue(const std::vector<std::string>& values) {
}
static bool valuesEqualForCompare(const std::vector<std::string>& finalValues,
const std::vector<std::string>& originValues) {
if (finalValues.size() != originValues.size()) return false;
const std::vector<std::string>& originValues,
int originStartIndex = 0) {
if (originStartIndex < 0) return false;
if (originValues.size() < static_cast<size_t>(originStartIndex) + finalValues.size()) return false;
for (size_t i = 0; i < finalValues.size(); ++i) {
if (compareValueKey(finalValues[i]) != compareValueKey(originValues[i])) return false;
if (compareValueKey(finalValues[i]) != compareValueKey(originValues[originStartIndex + i])) return false;
}
return true;
}
static std::string matchStyleForValues(bool hasOrigin,
static int displayIndexForOriginIndex(const std::vector<int>& originIndexes, int originIndex) {
auto it = std::find(originIndexes.begin(), originIndexes.end(), originIndex);
if (it == originIndexes.end()) return -1;
return static_cast<int>(std::distance(originIndexes.begin(), it));
}
static bool valuesEqualForCompareByOriginIndex(const std::vector<std::string>& finalValues,
const std::vector<std::string>& originValues,
const std::vector<int>& originIndexes,
int originPickStart) {
if (originPickStart == std::numeric_limits<int>::min()) {
return valuesEqualForCompare(finalValues, originValues, 0);
}
if (originIndexes.empty()) {
int originStartIndex = originPickStart < 0 ? -1 : originPickStart;
return valuesEqualForCompare(finalValues, originValues, originStartIndex);
}
for (size_t i = 0; i < finalValues.size(); ++i) {
int displayIndex = displayIndexForOriginIndex(originIndexes, originPickStart + static_cast<int>(i));
if (displayIndex < 0 || displayIndex >= static_cast<int>(originValues.size())) return false;
if (compareValueKey(finalValues[i]) != compareValueKey(originValues[displayIndex])) return false;
}
return true;
}
static bool parseArrayBaseRange(const std::string& arrayBase, int& start, int& end) {
std::smatch m;
static const std::regex re(R"(^\s*(-?\d+)\s*-->\s*(-?\d+)\s*$)");
if (!std::regex_match(arrayBase, m, re)) return false;
start = std::stoi(m[1].str());
end = std::stoi(m[2].str());
return start <= end;
}
static int parseOffsetText(const std::string& text, int fallback = 0) {
try {
if (trimValue(text).empty()) return fallback;
return std::stoi(trimValue(text));
} catch (...) {
return fallback;
}
}
static ComparePlan comparePlanForItem(const std::string& finalKey, const OutItem& item) {
ComparePlan plan;
(void)finalKey;
int baseStart = 0;
int baseEnd = 0;
if (!parseArrayBaseRange(item.arrayBase, baseStart, baseEnd)) return plan;
int valueOffset = parseOffsetText(item.valueOffset, 0);
plan.originPickStart = baseStart + valueOffset;
plan.originPickEnd = baseEnd + valueOffset;
return plan;
}
static std::string matchStyleForValues(bool hasOriginContext,
bool hasOriginMatch,
const std::vector<std::string>& finalValues,
const std::vector<std::string>& originDisplayValues) {
const std::vector<std::string>& originDisplayValues,
int originStartIndex = 0) {
if (hasNullValue(finalValues)) return "MatchRed";
if (!hasOrigin) return "";
return valuesEqualForCompare(finalValues, originDisplayValues) ? "MatchGreen" : "MatchOrange";
if (!hasOriginContext) return "";
if (!hasOriginMatch) return "MatchRed";
return valuesEqualForCompare(finalValues, originDisplayValues, originStartIndex) ? "MatchGreen" : "MatchOrange";
}
static void addRowStats(SheetStats* stats, const std::string& rowStyle) {
if (!stats) return;
if (rowStyle == "MatchGreen") ++stats->normal;
else if (rowStyle == "MatchOrange") ++stats->abnormal;
else if (rowStyle == "MatchRed") ++stats->missing;
}
static std::string cellAttrs(const std::string& styleId, const std::string& extra = "") {
@@ -1422,6 +1649,16 @@ static std::string originSeriesKey(const std::string& finalKey, const OutItem& i
return replaceKeyRange(finalKey, start, end);
}
static std::string originSeriesKeyForMatch(const std::string& finalKey,
const OutItem& item,
const OriginMatch& match) {
if (match.rangeStart != std::numeric_limits<int>::min() &&
match.rangeEnd != std::numeric_limits<int>::min()) {
return replaceKeyRange(finalKey, match.rangeStart, match.rangeEnd);
}
return originSeriesKey(finalKey, item);
}
static void writeSsCell(std::ofstream& ofs, const std::string& value, const std::string& attrs = "") {
ofs << "<Cell" << attrs << "><Data ss:Type=\"String\">" << htmlEscape(value) << "</Data></Cell>";
}
@@ -1438,6 +1675,33 @@ static std::string displayLabelForSheet(const OutItem& item) {
return item.label;
}
static std::string originHeaderFieldValue(const OriginHeader& header, const std::string& key) {
for (const auto& field : header.fields) {
if (field.key == key) return field.val;
}
return "";
}
static std::vector<OriginHeader> visibleOriginHeadersForSheet(OriginData* origin,
const std::vector<OutItem>& items,
const std::string& wiring) {
std::vector<OriginHeader> visible;
if (!origin) return visible;
OriginData probe = *origin;
std::set<int> usedOrders;
for (const auto& item : items) {
if (item.wiring != wiring) continue;
OriginMatch om = matchOrigin(&probe, item.doPath, item.daPath, item.stat, item.phase, item.wiring);
if (!om.value.empty() && om.sourceOrder >= 0) usedOrders.insert(om.sourceOrder);
}
for (const auto& header : origin->headers) {
if (usedOrders.count(header.sourceOrder)) visible.push_back(header);
}
return visible;
}
static void writeDataWorksheet(std::ofstream& ofs,
const std::string& sheetName,
const std::string& wiring,
@@ -1445,9 +1709,11 @@ static void writeDataWorksheet(std::ofstream& ofs,
const std::vector<std::string>& blocks,
const std::vector<OutItem>& items,
OriginData* origin,
bool showStatBlocks = true) {
const int maxValues = 50;
const int totalCols = 9 + maxValues;
bool showStatBlocks,
SheetStats* stats) {
const int maxValues = 64;
std::vector<OriginHeader> visibleOriginHeaders = visibleOriginHeadersForSheet(origin, items, wiring);
const int totalCols = std::max(9 + maxValues, 3 + static_cast<int>(visibleOriginHeaders.size()));
ofs << "<Worksheet ss:Name=\"" << htmlEscape(sheetName) << "\"><Table>\n";
ofs << "<Column ss:Width=\"210\"/><Column ss:Width=\"75\"/><Column ss:Width=\"85\"/><Column ss:Width=\"85\"/>";
@@ -1460,10 +1726,34 @@ static void writeDataWorksheet(std::ofstream& ofs,
writeSsCell(ofs, "头部信息", " ss:StyleID=\"Section\" ss:MergeAcross=\"" + std::to_string(totalCols - 1) + "\"");
ofs << "</Row>\n";
for (const auto& h : headers) {
size_t originHeaderRows = visibleOriginHeaders.empty() ? 0 : originHeaderDisplayKeys().size() + 1;
size_t headerRows = std::max(headers.size(), originHeaderRows);
for (size_t row = 0; row < headerRows; ++row) {
writeSsRowStart(ofs);
writeSsCell(ofs, h.key, " ss:StyleID=\"Default\"");
writeSsCell(ofs, h.val, " ss:StyleID=\"Default\" ss:MergeAcross=\"" + std::to_string(totalCols - 2) + "\"");
if (row < headers.size()) {
writeSsCell(ofs, headers[row].key, " ss:StyleID=\"Default\"");
writeSsCell(ofs, headers[row].val, " ss:StyleID=\"Default\"");
} else {
writeSsCell(ofs, "", " ss:StyleID=\"Default\"");
writeSsCell(ofs, "", " ss:StyleID=\"Default\"");
}
if (!visibleOriginHeaders.empty()) {
if (row == 0) {
writeSsCell(ofs, "原始报告", " ss:StyleID=\"Section\"");
for (const auto& oh : visibleOriginHeaders) {
writeSsCell(ofs, oh.fileName.empty() ? oh.title : oh.fileName, " ss:StyleID=\"Section\"");
}
} else {
size_t fieldIndex = row - 1;
const auto& displayKeys = originHeaderDisplayKeys();
std::string key = fieldIndex < displayKeys.size() ? displayKeys[fieldIndex] : "";
writeSsCell(ofs, key, " ss:StyleID=\"Default\"");
for (const auto& oh : visibleOriginHeaders) {
writeSsCell(ofs, key.empty() ? "" : originHeaderFieldValue(oh, key), " ss:StyleID=\"Default\"");
}
}
}
ofs << "</Row>\n";
}
@@ -1499,16 +1789,34 @@ static void writeDataWorksheet(std::ofstream& ofs,
ofs << "</Row>\n";
}
OriginMatch om = matchOrigin(origin, item.doPath, item.daPath, item.stat, item.phase, item.wiring);
std::string finalKey, finalValue;
splitFinalText(item.finalText, finalKey, finalValue);
finalKey = offsetFinalKey(finalKey, item);
ComparePlan comparePlan = comparePlanForItem(finalKey, item);
OriginMatch om = matchOrigin(origin, item.doPath, item.daPath, item.stat, item.phase, item.wiring,
comparePlan.originPickStart, comparePlan.originPickEnd);
std::vector<std::string> finalValues = splitCommaValues(finalValue);
std::vector<std::string> originValuesRaw = splitCommaValues(om.value);
std::vector<std::string> originValues = roundOriginValuesForDisplay(originValuesRaw);
bool hasOriginMatch = origin && !om.value.empty();
std::string rowStyle = matchStyleForValues(hasOriginMatch, finalValues, originValues);
std::string originKey = originSeriesKey(finalKey, item);
int finalRangeStart = 0;
int finalRangeEnd = 0;
bool isSequenceRow = parseKeyRange(finalKey, finalRangeStart, finalRangeEnd);
int originStartIndex = -1;
if (comparePlan.originPickStart != std::numeric_limits<int>::min() &&
om.rangeStart != std::numeric_limits<int>::min()) {
originStartIndex = displayIndexForOriginIndex(om.valueIndexes, comparePlan.originPickStart);
}
std::string rowStyle;
if (hasNullValue(finalValues)) rowStyle = "MatchRed";
else if (!origin) rowStyle = "";
else if (!hasOriginMatch) rowStyle = "MatchRed";
else rowStyle = valuesEqualForCompareByOriginIndex(finalValues, originValues, om.valueIndexes,
comparePlan.originPickStart)
? "MatchGreen"
: "MatchOrange";
addRowStats(stats, rowStyle);
std::string originKey = originSeriesKeyForMatch(finalKey, item, om);
writeSsRowStart(ofs);
writeSsCell(ofs, displayLabelForSheet(item), cellAttrs(rowStyle, " ss:MergeDown=\"1\""));
@@ -1526,7 +1834,12 @@ static void writeDataWorksheet(std::ofstream& ofs,
writeSsRowStart(ofs);
writeSsCell(ofs, "原始值", cellAttrs(rowStyle, " ss:Index=\"8\""));
writeSsCell(ofs, om.value.empty() ? "" : originKey, cellAttrs(rowStyle));
for (int i = 0; i < maxValues; ++i) writeSsCell(ofs, i < (int)originValues.size() ? originValues[i] : "", cellAttrs(rowStyle));
for (int i = 0; i < maxValues; ++i) {
std::string attrs = (isSequenceRow && hasOriginMatch && i == originStartIndex)
? " ss:StyleID=\"OriginPickBlue\""
: cellAttrs(rowStyle);
writeSsCell(ofs, i < (int)originValues.size() ? originValues[i] : "", attrs);
}
ofs << "</Row>\n";
lastCategory = item.category;
@@ -1555,7 +1868,7 @@ static void writeOriginRecordWorksheet(std::ofstream& ofs,
bool roundValues) {
const int maxValues = 50;
ofs << "<Worksheet ss:Name=\"" << htmlEscape(sheetName) << "\"><Table>\n";
ofs << "<Column ss:Width=\"320\"/><Column ss:Width=\"120\"/><Column ss:Width=\"160\"/>";
ofs << "<Column ss:Width=\"320\"/><Column ss:Width=\"120\"/><Column ss:Width=\"160\"/><Column ss:Width=\"220\"/>";
for (int i = 0; i < maxValues; ++i) ofs << "<Column ss:Width=\"90\"/>";
ofs << "\n";
@@ -1563,6 +1876,7 @@ static void writeOriginRecordWorksheet(std::ofstream& ofs,
writeSsCell(ofs, "原始路径", " ss:StyleID=\"Head\"");
writeSsCell(ofs, "报告名", " ss:StyleID=\"Head\"");
writeSsCell(ofs, "icd中代表的字段", " ss:StyleID=\"Head\"");
writeSsCell(ofs, "原始值", " ss:StyleID=\"Head\"");
for (int i = 1; i <= maxValues; ++i) writeSsCell(ofs, "" + std::to_string(i), " ss:StyleID=\"Head\"");
ofs << "</Row>\n";
@@ -1576,6 +1890,7 @@ static void writeOriginRecordWorksheet(std::ofstream& ofs,
writeSsCell(ofs, rec.path);
writeSsCell(ofs, rec.reportName);
writeSsCell(ofs, "");
writeSsCell(ofs, rec.value);
for (int j = 0; j < maxValues; ++j) writeSsCell(ofs, j < (int)originValues.size() ? originValues[j] : "");
ofs << "</Row>\n";
}
@@ -1583,13 +1898,49 @@ static void writeOriginRecordWorksheet(std::ofstream& ofs,
}
static void writeMatchedWorksheet(std::ofstream& ofs, const OriginData& origin) {
writeOriginRecordWorksheet(ofs, origin, "已匹配的原始数据", true, false);
writeOriginRecordWorksheet(ofs, origin, "已匹配的原始数据", true, true);
}
static void writeUnmatchedWorksheet(std::ofstream& ofs, const OriginData& origin) {
writeOriginRecordWorksheet(ofs, origin, "未匹配的原始数据", false, true);
}
static void writeSummaryWorksheet(std::ofstream& ofs, const std::vector<SheetStats>& stats) {
ofs << "<Worksheet ss:Name=\"统计信息\"><Table>\n";
ofs << "<Column ss:Width=\"180\"/><Column ss:Width=\"110\"/><Column ss:Width=\"110\"/><Column ss:Width=\"110\"/><Column ss:Width=\"90\"/>\n";
writeSsRowStart(ofs);
writeSsCell(ofs, "工作表", " ss:StyleID=\"Head\"");
writeSsCell(ofs, "数据正常个数", " ss:StyleID=\"Head\"");
writeSsCell(ofs, "数据异常个数", " ss:StyleID=\"Head\"");
writeSsCell(ofs, "数据缺失个数", " ss:StyleID=\"Head\"");
writeSsCell(ofs, "总数", " ss:StyleID=\"Head\"");
ofs << "</Row>\n";
int totalNormal = 0;
int totalAbnormal = 0;
int totalMissing = 0;
for (const auto& item : stats) {
int total = item.normal + item.abnormal + item.missing;
totalNormal += item.normal;
totalAbnormal += item.abnormal;
totalMissing += item.missing;
writeSsRowStart(ofs);
writeSsCell(ofs, item.sheetName);
writeSsCell(ofs, std::to_string(item.normal));
writeSsCell(ofs, std::to_string(item.abnormal));
writeSsCell(ofs, std::to_string(item.missing));
writeSsCell(ofs, std::to_string(total));
ofs << "</Row>\n";
}
writeSsRowStart(ofs);
writeSsCell(ofs, "合计", " ss:StyleID=\"Section\"");
writeSsCell(ofs, std::to_string(totalNormal), " ss:StyleID=\"Section\"");
writeSsCell(ofs, std::to_string(totalAbnormal), " ss:StyleID=\"Section\"");
writeSsCell(ofs, std::to_string(totalMissing), " ss:StyleID=\"Section\"");
writeSsCell(ofs, std::to_string(totalNormal + totalAbnormal + totalMissing), " ss:StyleID=\"Section\"");
ofs << "</Row>\n";
ofs << "</Table></Worksheet>\n";
}
static std::vector<HeaderItem> headersForSheet(const std::vector<HeaderItem>& headers, const std::string& dataType) {
std::vector<HeaderItem> out = headers;
std::string displayDataType = dataType;
@@ -1660,6 +2011,7 @@ static void writeExcelWorkbook(const fs::path& outPath,
ofs << "<Style ss:ID=\"MatchGreen\" ss:Parent=\"Default\"><Interior ss:Color=\"#D9EAD3\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"MatchOrange\" ss:Parent=\"Default\"><Interior ss:Color=\"#FCE4D6\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"MatchRed\" ss:Parent=\"Default\"><Interior ss:Color=\"#F4CCCC\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "<Style ss:ID=\"OriginPickBlue\" ss:Parent=\"Default\"><Interior ss:Color=\"#CFE2F3\" ss:Pattern=\"Solid\"/></Style>\n";
ofs << "</Styles>\n";
std::vector<SheetSpec> sheets = {
@@ -1679,6 +2031,7 @@ static void writeExcelWorkbook(const fs::path& outPath,
const std::vector<HeaderItem> emptyHeaders;
const std::unordered_map<std::string, std::string> emptyValues;
std::vector<SheetStats> summaryStats;
for (const auto& spec : sheets) {
if (!specAllowsFrontType(spec, frontType)) continue;
auto bucketIt = dataByType.find(spec.dataType);
@@ -1687,12 +2040,16 @@ static void writeExcelWorkbook(const fs::path& outPath,
bucketIt == dataByType.end() ? emptyValues : bucketIt->second.valueByKey;
std::vector<OutItem> sheetItems = buildItemsForSheet(valueMetas, spec, valueByKey);
sortOutItems(sheetItems);
writeDataWorksheet(ofs, spec.name, spec.wiring, headersForSheet(headers, spec.dataType), blocks, sheetItems, origin, spec.showStatBlocks);
}
if (origin) {
writeMatchedWorksheet(ofs, *origin);
writeUnmatchedWorksheet(ofs, *origin);
SheetStats stats;
stats.sheetName = spec.name;
writeDataWorksheet(ofs, spec.name, spec.wiring, headersForSheet(headers, spec.dataType), blocks, sheetItems, origin, spec.showStatBlocks, &stats);
summaryStats.push_back(stats);
}
writeSummaryWorksheet(ofs, summaryStats);
OriginData emptyOrigin;
const OriginData& originForRawSheets = origin ? *origin : emptyOrigin;
writeMatchedWorksheet(ofs, originForRawSheets);
writeUnmatchedWorksheet(ofs, originForRawSheets);
ofs << "</Workbook>\n";
}

View File

@@ -477,6 +477,114 @@ std::string reportNameFromJsonText(const std::string& text) {
return "";
}
std::string lowerAscii(std::string value);
bool isMonitorIdKey(const std::string& key) {
std::string k = lowerAscii(trimCopy(key));
return k == "monitor" ||
k == "monitorid" ||
k == "monitor_id" ||
k == "mp_id" ||
k == "mpid" ||
k == "measurepointid" ||
k == "measure_point_id" ||
k == "measureid" ||
k == "measure_id";
}
bool looksLikeJsonText(const std::string& text) {
std::string s = trimCopy(text);
return !s.empty() && (s.front() == '{' || s.front() == '[');
}
bool findMonitorIdValue(const json& value, std::string& out) {
if (value.is_object()) {
for (auto it = value.begin(); it != value.end(); ++it) {
if (isMonitorIdKey(it.key())) {
out = trimCopy(jsonScalarToString(it.value()));
if (!out.empty()) return true;
}
}
for (auto it = value.begin(); it != value.end(); ++it) {
if ((it.key() == "messageBody" || it.key() == "body") && it.value().is_string()) {
std::string embedded = it.value().get<std::string>();
if (looksLikeJsonText(embedded)) {
try {
if (findMonitorIdValue(json::parse(embedded), out)) return true;
} catch (...) {
}
}
}
if (findMonitorIdValue(it.value(), out)) return true;
}
} else if (value.is_array()) {
for (const auto& item : value) {
if (findMonitorIdValue(item, out)) return true;
}
}
return false;
}
std::string monitorIdFromJsonText(const std::string& text) {
try {
std::string monitorId;
if (findMonitorIdValue(json::parse(text), monitorId)) return monitorId;
} catch (...) {
}
return "";
}
bool findGuidValue(const json& value, std::string& out) {
if (value.is_object()) {
for (auto it = value.begin(); it != value.end(); ++it) {
std::string key = lowerAscii(trimCopy(it.key()));
if (key == "guid" || key == "traceguid" || key == "trace_guid") {
out = trimCopy(jsonScalarToString(it.value()));
if (!out.empty()) return true;
}
}
for (auto it = value.begin(); it != value.end(); ++it) {
if ((it.key() == "messageBody" || it.key() == "body") && it.value().is_string()) {
std::string embedded = it.value().get<std::string>();
if (looksLikeJsonText(embedded)) {
try {
if (findGuidValue(json::parse(embedded), out)) return true;
} catch (...) {
}
}
}
if (findGuidValue(it.value(), out)) return true;
}
} else if (value.is_array()) {
for (const auto& item : value) {
if (findGuidValue(item, out)) return true;
}
}
return false;
}
std::string guidFromJsonText(const std::string& text) {
try {
std::string guid;
if (findGuidValue(json::parse(text), guid)) return guid;
json root = json::parse(text);
if (root.is_object()) {
auto it = root.find("key");
if (it != root.end()) return trimCopy(jsonScalarToString(*it));
}
} catch (...) {
}
return "";
}
std::string normalizeMonitorId(std::string value) {
value = trimCopy(value);
value.erase(std::remove_if(value.begin(), value.end(), [](unsigned char ch) {
return std::isspace(ch) != 0 || ch == '"' || ch == '\'' || ch == '{' || ch == '}';
}), value.end());
return lowerAscii(value);
}
std::string safePathPart(std::string value, const std::string& fallback) {
if (value.empty()) value = fallback;
for (char& ch : value) {
@@ -566,6 +674,18 @@ class RocketMqRuntime {
int (*DestroyPushConsumer)(CPushConsumer*) = nullptr;
};
struct TraceSession {
std::string monitorId;
std::string monitorName;
std::string traceDir;
std::string xmlPath;
std::string frontType;
std::string guid;
std::unordered_set<std::string> receivedDataTypes;
long long dataTimestamp = 0;
int originCounter = 0;
};
public:
~RocketMqRuntime() {
DetachedHandles old;
@@ -624,6 +744,10 @@ public:
dataTimestamp_ = 0;
originCounter_ = 0;
currentTraceDir_.clear();
currentMonitorId_.clear();
currentMonitorName_.clear();
sessions_.clear();
sessionsByGuid_.clear();
lastError_.clear();
if (!loadLibraryLocked()) {
@@ -703,16 +827,36 @@ public:
void setTraceTarget(const PqToolConfig& cfg,
const PqLedgerDevice& device,
const PqLedgerMonitor& monitor) {
const PqLedgerMonitor& monitor,
const std::string& frontType,
const std::string& guid) {
std::lock_guard<std::mutex> lock(mu_);
cfg_ = cfg;
currentTraceDir_ = pqTraceDirectoryFor(cfg, device, monitor);
currentXmlPath_ = pqFindDeviceXmlPath(cfg, device);
currentMonitorId_ = monitor.id;
currentMonitorName_ = monitor.name;
fs::path dir = fs::u8path(currentTraceDir_);
fs::create_directories(dir);
clearTraceTextRecords(dir);
dataTimestamp_ = 0;
originCounter_ = 0;
std::string monitorKey = normalizeMonitorId(monitor.id);
auto old = sessions_.find(monitorKey);
if (old != sessions_.end() && !old->second.guid.empty()) {
sessionsByGuid_.erase(normalizeMonitorId(old->second.guid));
}
TraceSession& session = sessions_[monitorKey];
session.monitorId = monitor.id;
session.monitorName = monitor.name;
session.traceDir = currentTraceDir_;
session.xmlPath = currentXmlPath_;
session.frontType = frontType;
session.guid = guid;
session.receivedDataTypes.clear();
session.dataTimestamp = 0;
session.originCounter = 0;
if (!guid.empty()) sessionsByGuid_[normalizeMonitorId(guid)] = monitorKey;
json context = {
{"deviceId", device.id},
@@ -721,6 +865,8 @@ public:
{"processNo", device.processNo},
{"monitorId", monitor.id},
{"monitorName", monitor.name},
{"guid", guid},
{"frontType", frontType},
{"xmlPath", currentXmlPath_},
{"jsonDataPath", (dir / "jsondata_01.txt").u8string()},
{"jsonDataPaths", json::array({
@@ -776,6 +922,37 @@ private:
return instance().onMessage(msg);
}
TraceSession* findSessionForMessageLocked(const std::string& body,
const std::string& key,
std::string& actualMonitor,
std::string& reason) {
actualMonitor.clear();
reason.clear();
actualMonitor = monitorIdFromJsonText(body);
std::string normalized = normalizeMonitorId(actualMonitor);
if (!normalized.empty()) {
auto it = sessions_.find(normalized);
if (it != sessions_.end()) return &it->second;
reason = "monitor not being traced, actual=" + actualMonitor;
}
std::string guid = guidFromJsonText(body);
if (guid.empty()) guid = trimCopy(key);
std::string normalizedGuid = normalizeMonitorId(guid);
if (!normalizedGuid.empty()) {
auto git = sessionsByGuid_.find(normalizedGuid);
if (git != sessionsByGuid_.end()) {
auto sit = sessions_.find(git->second);
if (sit != sessions_.end()) return &sit->second;
}
if (reason.empty()) reason = "guid not being traced, actual=" + guid;
}
if (reason.empty()) reason = "missing monitor id/guid";
return nullptr;
}
int onMessage(CMessageExt* msg) {
std::string topic = GetMessageTopic && GetMessageTopic(msg) ? GetMessageTopic(msg) : "";
std::string tag = GetMessageTags && GetMessageTags(msg) ? GetMessageTags(msg) : "";
@@ -790,25 +967,58 @@ private:
+ " tag=" + tag
+ " key=" + key
+ " bodyLen=" + std::to_string(body.size()) + "\n");
bool isData = topic == cfg_.dataTopic && tagMatches(cfg_.dataTag, tag);
std::string dataFrontType = dataFrontTypeForMessageLocked(topic, tag);
bool isData = !dataFrontType.empty();
bool isTrace = topic == cfg_.traceTopic && tagMatches(cfg_.traceTag, tag);
TraceSession* session = nullptr;
if (isData || isTrace) {
std::string actualMonitor;
std::string reason;
session = findSessionForMessageLocked(body, key, actualMonitor, reason);
if (!session) {
appendTextFile(runtimeLogPath("mq_messages.log"), nowIsoMillis()
+ " skipped " + (isData ? "DATATopic" : "TraceTopic")
+ " by monitor router: " + reason
+ " topic=" + topic
+ " tag=" + tag + "\n");
return 0;
}
if (isData && !session->frontType.empty() && session->frontType != dataFrontType) {
appendTextFile(runtimeLogPath("mq_messages.log"), nowIsoMillis()
+ " skipped DATATopic by frontType router: session="
+ session->frontType + ", message=" + dataFrontType
+ " topic=" + topic + " tag=" + tag + "\n");
return 0;
}
}
if (isData) {
std::string dataType = dataTypeFromJsonText(body);
fs::path out = jsonDataOutputPathLocked(dataType);
std::string normalizedType = normalizeDataType(dataType);
if (!session->receivedDataTypes.insert(normalizedType).second) {
appendTextFile(runtimeLogPath("mq_messages.log"), nowIsoMillis()
+ " skipped DATATopic duplicate DATA_TYPE=" + normalizedType
+ " monitor=" + session->monitorId
+ " topic=" + topic + " tag=" + tag + "\n");
return 0;
}
fs::path out = jsonDataOutputPathLocked(*session, dataType);
writeTextFile(out, body);
long long bodyTs = timestampFromJsonText(body);
dataTimestamp_ = bodyTs > 0 ? bodyTs : storeTs;
session->dataTimestamp = bodyTs > 0 ? bodyTs : storeTs;
if (normalizeMonitorId(session->monitorId) == normalizeMonitorId(currentMonitorId_)) {
dataTimestamp_ = session->dataTimestamp;
}
appendTextFile(runtimeLogPath("mq_messages.log"), nowIsoMillis() + " DATATopic DATA_TYPE=" + dataType + " tag=" + tag + " -> " + out.u8string() + "\n");
if (event_) event_({"data", eventTitle("DATATopic", out.u8string()), body, out.u8string()});
if (event_) event_({"data", eventTitle("DATATopic", out.u8string()), body, out.u8string(), session->monitorId, session->traceDir});
} else if (isTrace) {
long long bodyTs = timestampFromJsonText(body);
if (dataTimestamp_ > 0 && bodyTs > 0 && bodyTs < dataTimestamp_) {
if (session->dataTimestamp > 0 && bodyTs > 0 && bodyTs < session->dataTimestamp) {
appendTextFile(runtimeLogPath("mq_messages.log"), nowIsoMillis() + " TraceTopic tag=" + tag + " skipped by timestamp\n");
} else {
fs::path out = nextOriginOutputPathLocked(reportNameFromJsonText(body));
fs::path out = nextOriginOutputPathLocked(*session, reportNameFromJsonText(body));
writeTextFile(out, body);
appendTextFile(runtimeLogPath("mq_messages.log"), nowIsoMillis() + " TraceTopic tag=" + tag + " -> " + out.u8string() + "\n");
if (event_) event_({"trace", eventTitle("TraceTopic", out.u8string()), body, out.u8string()});
if (event_) event_({"trace", eventTitle("TraceTopic", out.u8string()), body, out.u8string(), session->monitorId, session->traceDir});
}
} else {
appendTextFile(runtimeLogPath("mq_messages.log"), nowIsoMillis() + " skipped topic=" + topic + " tag=" + tag + "\n");
@@ -833,9 +1043,16 @@ private:
return fs::u8path("origin.txt");
}
fs::path jsonDataOutputPathLocked(const std::string& dataType) const {
std::string dataFrontTypeForMessageLocked(const std::string& topic, const std::string& tag) const {
if (topic == cfg_.dataStatTopic && tagMatches(cfg_.dataStatTag, tag)) return "cfg_stat_data";
if (topic == cfg_.dataRealtimeTopic && tagMatches(cfg_.dataRealtimeTag, tag)) return "cfg_3s_data";
return "";
}
fs::path jsonDataOutputPathLocked(const TraceSession& session, const std::string& dataType) const {
std::string suffix = normalizeDataType(dataType);
std::string filename = "jsondata_" + safePathPart(suffix, "unknown") + ".txt";
if (!session.traceDir.empty()) return fs::u8path(session.traceDir) / fs::u8path(filename);
if (!currentTraceDir_.empty()) return fs::u8path(currentTraceDir_) / fs::u8path(filename);
if (!cfg_.jsonDataPath.empty()) {
std::vector<std::string> paths;
@@ -852,18 +1069,18 @@ private:
return runtimeJsonPath(filename);
}
fs::path nextOriginOutputPathLocked(const std::string& reportName) {
if (currentTraceDir_.empty()) return originOutputPath();
fs::path nextOriginOutputPathLocked(TraceSession& session, const std::string& reportName) {
if (session.traceDir.empty() && currentTraceDir_.empty()) return originOutputPath();
std::string safeReport = reportName.empty() ? "" : safePathPart(reportName, "report");
std::string baseName;
if (safeReport.empty()) {
++originCounter_;
baseName = "origin" + std::to_string(originCounter_);
++session.originCounter;
baseName = "origin" + std::to_string(session.originCounter);
} else {
baseName = "origin_" + safeReport;
}
fs::path dir = fs::u8path(currentTraceDir_);
fs::path dir = session.traceDir.empty() ? fs::u8path(currentTraceDir_) : fs::u8path(session.traceDir);
fs::path out = dir / fs::u8path(baseName + ".txt");
int suffix = 2;
while (fs::exists(out)) {
@@ -1063,6 +1280,10 @@ private:
std::string lastError_;
std::string currentTraceDir_;
std::string currentXmlPath_;
std::string currentMonitorId_;
std::string currentMonitorName_;
std::unordered_map<std::string, TraceSession> sessions_;
std::unordered_map<std::string, std::string> sessionsByGuid_;
bool active_ = false;
long long dataTimestamp_ = 0;
int originCounter_ = 0;
@@ -1383,14 +1604,15 @@ bool pqSendTraceRequest(const PqToolConfig& cfg,
const std::string& frontType,
PqLogFn log,
PqMqEventFn event) {
rocketRuntime().setTraceTarget(cfg, device, monitor);
std::string payload = pqBuildTracePayload(cfg, device, monitor, frontType);
std::string guid = guidFromJsonText(payload);
rocketRuntime().setTraceTarget(cfg, device, monitor, frontType, guid);
fs::path traceDir = fs::u8path(pqTraceDirectoryFor(cfg, device, monitor));
fs::create_directories(traceDir);
fs::path sentPath = traceDir / "sent_trace_request.json";
writeTextFile(sentPath, prettyJsonOrRaw(payload));
writeTextFile(runtimeJsonPath("last_trace_request.json"), json::parse(payload).dump(2));
if (event) event({"sent", eventTitle("Send", sentPath.u8string()), payload, sentPath.u8string()});
if (event) event({"sent", eventTitle("Send", sentPath.u8string()), payload, sentPath.u8string(), monitor.id, traceDir.u8string()});
std::string mqError;
if (rocketRuntime().sendTrace(cfg, payload, mqError, log)) {
if (log) log("Trace target monitor=" + monitor.name + "(" + monitor.id + "), frontType=" + frontType);

View File

@@ -88,6 +88,8 @@ struct PqMqEvent {
std::string title;
std::string body;
std::string path;
std::string monitorId;
std::string traceDir;
};
using PqMqEventFn = std::function<void(const PqMqEvent&)>;
@@ -128,5 +130,8 @@ bool pqRunParser(const std::string& xmlPath,
const std::string& outPath,
std::string* err,
const std::string& frontType = "");
bool pqApplyIcdDescriptionsToWorkbook(const std::string& icdPath,
const std::string& workbookPath,
std::string* message = nullptr);
int runPqGuiApp();