You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmSubWxs/nmWellPressureFlowPage.cpp

1933 lines
66 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmWellPressureFlowPage.h"
#include "iUnitGroup.h"
#include "iUnitHelper.h"
#include "iUnitItem.h"
#include "nmDataAnalyzeContext.h"
#include "nmDataAnalyzeContextProvider.h"
#include "nmDataAnalyzeManager.h"
#include "nmDataWellBase.h"
#include "nmWxPressFlowChartWidget.h"
#include "ZxBaseUtil.h"
#include "ZxBaHelper.h"
#include "ZxDataGaugeF.h"
#include "ZxDataProject.h"
#include "ZxDataWell.h"
#include "ZxTableHeaderViewUnit.h"
#include "zxSysUtils.h"
#include <QAbstractItemView>
#include <QComboBox>
#include <QDialog>
#include <QHBoxLayout>
#include <QHeaderView>
#include <QLabel>
#include <QList>
#include <QPalette>
#include <QPlainTextEdit>
#include <QPushButton>
#include <QSplitter>
#include <QStackedWidget>
#include <QStandardItem>
#include <QStandardItemModel>
#include <QStringList>
#include <QTableWidget>
#include <QTableWidgetItem>
#include <QVBoxLayout>
namespace
{
QString preferredTimeUnit()
{
return QLatin1String("h");
}
QString preferredFlowUnit()
{
return QString::fromUtf8("m\xC2\xB3/d");
}
iUnitGroup* resolveUnitGroup(const QStringList& listCandidates,
QString& sResolvedUnit)
{
for (int nIndex = 0; nIndex < listCandidates.size(); ++nIndex)
{
const QString& sCandidate = listCandidates[nIndex];
iUnitGroup* pUnitGroup =
iUnitHelper::getUnitGroupByUnit(sCandidate);
if (pUnitGroup == nullptr)
{
continue;
}
int nUnitIndex = pUnitGroup->indexOf(sCandidate);
QStringList listUnitNames = pUnitGroup->getAllUnitNames();
if (nUnitIndex < 0 || nUnitIndex >= listUnitNames.size())
{
continue;
}
sResolvedUnit = listUnitNames[nUnitIndex];
return pUnitGroup;
}
sResolvedUnit = listCandidates.isEmpty()
? QString() : listCandidates.first();
return nullptr;
}
QLabel* createEmptyLabel(const QString& sText, QWidget* pParent)
{
QLabel* pLabel = new QLabel(sText, pParent);
pLabel->setAlignment(Qt::AlignCenter);
QPalette oPalette = pLabel->palette();
oPalette.setColor(QPalette::WindowText,
pParent->palette().color(QPalette::Mid));
pLabel->setPalette(oPalette);
return pLabel;
}
QString displayValueText(double dValue, int nDigit)
{
if (nDigit <= 0)
{
return QString::number(dValue, 'f', 0);
}
return ZxBaseUtil::getValidStr(dValue, nDigit);
}
// 显示相下拉框只列出实际出现过非零流量的相;多相表中的固定全零列
// 仍保留在井记录和求解输入中,但不应显示成该井存在对应相产量。
bool hasRealPhaseData(const QVector<QPointF>& vecPoints)
{
int nStartIndex = 0;
if (!vecPoints.isEmpty() &&
vecPoints.first().x() == 0.0 &&
vecPoints.first().y() == 0.0)
{
nStartIndex = 1;
}
for (int nIndex = nStartIndex; nIndex < vecPoints.size(); ++nIndex)
{
if (vecPoints[nIndex].y() != 0.0)
{
return true;
}
}
return false;
}
// 压力/流量记录下拉框只显示稳定的 Gauge Code避免顶部工具栏过宽。
// 异常旧数据没有 Code 时,才回退到框架名称。
QString formatGaugeRecordLabel(const QString& sGaugeName,
const QString& sGaugeCode,
const QString& sGaugeTime)
{
Q_UNUSED(sGaugeTime);
return sGaugeCode.isEmpty() ? sGaugeName : sGaugeCode;
}
// 统一向压力/流量记录下拉框追加一项:身份无效的记录禁用不可选,空/损坏记录仍可选但追加提示后缀。
void appendGaugeComboItem(QComboBox* pCombo,
const QString& sGaugeName,
const QString& sGaugeCode,
const QString& sGaugeTime,
NM_GAUGE_RECORD_STATUS eStatus)
{
QString sLabel = formatGaugeRecordLabel(sGaugeName, sGaugeCode, sGaugeTime);
switch (eStatus)
{
case NM_GaugeRecord_InvalidIdentity:
sLabel += nmWellPressureFlowPage::tr(" (Invalid)");
break;
case NM_GaugeRecord_Empty:
sLabel += nmWellPressureFlowPage::tr(" (Empty)");
break;
case NM_GaugeRecord_Corrupt:
sLabel += nmWellPressureFlowPage::tr(" (Corrupt)");
break;
default:
break;
}
pCombo->addItem(sLabel, sGaugeCode);
if (eStatus == NM_GaugeRecord_InvalidIdentity)
{
QStandardItemModel* pModel =
qobject_cast<QStandardItemModel*>(pCombo->model());
if (pModel != nullptr)
{
QStandardItem* pItem = pModel->item(pCombo->count() - 1);
if (pItem != nullptr)
{
pItem->setFlags(pItem->flags() & ~Qt::ItemIsEnabled);
}
}
}
}
// 诊断文本统一使用约 15 位有效数字,避免浮点差异被显示精度掩盖。
QString diagNumberText(double dValue)
{
return QString::number(dValue, 'g', 15);
}
// 诊断文本中的单个数据点,括号与逗号为固定格式不参与翻译。
QString diagPointText(const QPointF& oPoint)
{
return QString("(%1, %2)")
.arg(diagNumberText(oPoint.x()))
.arg(diagNumberText(oPoint.y()));
}
// 空字符串在诊断文本中显示为“(none)”,避免与真实空白混淆。
QString diagTextOrNone(const QString& sText)
{
return sText.isEmpty()
? nmWellPressureFlowPage::tr("(none)") : sText;
}
// 记录状态的诊断文本,与下拉框后缀语义一致,单独成词便于阅读。
QString diagStatusText(NM_GAUGE_RECORD_STATUS eStatus)
{
switch (eStatus)
{
case NM_GaugeRecord_Usable:
return nmWellPressureFlowPage::tr("Usable");
case NM_GaugeRecord_Empty:
return nmWellPressureFlowPage::tr("Empty");
case NM_GaugeRecord_Corrupt:
return nmWellPressureFlowPage::tr("Corrupt");
case NM_GaugeRecord_InvalidIdentity:
return nmWellPressureFlowPage::tr("Invalid identity");
default:
return nmWellPressureFlowPage::tr("Unknown");
}
}
// 井别的诊断文本。
QString diagWellCategoryText(NM_WELL_CATEGORY eWellCategory)
{
switch (eWellCategory)
{
case NM_WellCategory_Oil:
return nmWellPressureFlowPage::tr("Oil well");
case NM_WellCategory_Gas:
return nmWellPressureFlowPage::tr("Gas well");
case NM_WellCategory_Water:
return nmWellPressureFlowPage::tr("Water well");
default:
return nmWellPressureFlowPage::tr("Unknown");
}
}
// 相名的诊断文本,与显示相下拉框文本保持一致。
QString diagPhaseText(NM_PHASE_TYPE ePhase)
{
switch (ePhase)
{
case PHASE_Oil:
return nmWellPressureFlowPage::tr("Oil");
case PHASE_Gas:
return nmWellPressureFlowPage::tr("Gas");
case PHASE_Water:
return nmWellPressureFlowPage::tr("Water");
default:
return nmWellPressureFlowPage::tr("Unknown");
}
}
// 当前分析窗口的PVT相态决定实际参与分析的相不能仅依赖多相布尔标志判断。
QString diagFluidTypeText(PvtFluidType eFluidType)
{
switch (eFluidType)
{
case WFT_Oil:
return nmWellPressureFlowPage::tr("Oil");
case WFT_Gas:
return nmWellPressureFlowPage::tr("Gas");
case WFT_Water:
return nmWellPressureFlowPage::tr("Water");
case WFT_Oil_Gas:
return nmWellPressureFlowPage::tr("Oil + Gas");
case WFT_Oil_Water:
return nmWellPressureFlowPage::tr("Oil + Water");
case WFT_Gas_Water:
return nmWellPressureFlowPage::tr("Gas + Water");
case WTF_Oil_Gas_Water:
return nmWellPressureFlowPage::tr("Oil + Gas + Water");
case WFT_Null:
return nmWellPressureFlowPage::tr("None");
default:
return nmWellPressureFlowPage::tr("Other (%1)")
.arg(static_cast<int>(eFluidType));
}
}
// 判断首个点是否为 (0,0) 占位点,与 hasRealPhaseData 的占位点语义一致。
bool hasLeadingPlaceholderPoint(const QVector<QPointF>& vecPoints)
{
return !vecPoints.isEmpty() &&
vecPoints.first().x() == 0.0 &&
vecPoints.first().y() == 0.0;
}
// 去除可选的首个 (0,0) 占位点后返回真实流量段列表。
QVector<QPointF> realPhaseSegments(const QVector<QPointF>& vecPoints)
{
QVector<QPointF> vecSegments = vecPoints;
if (hasLeadingPlaceholderPoint(vecSegments))
{
vecSegments.remove(0);
}
return vecSegments;
}
// 生成单个相的诊断摘要行:原始点数、真实段数、累计时长、首末点与占位点标识。
void appendPhaseDiagLines(QStringList& listLines,
const QString& sPhaseName,
const QVector<QPointF>& vecPoints)
{
if (vecPoints.isEmpty())
{
listLines << QString(" %1: %2")
.arg(sPhaseName)
.arg(nmWellPressureFlowPage::tr("no data"));
return;
}
// 累计时长只统计真实段,流量点 X 值是段持续时间而非绝对时刻。
QVector<QPointF> vecSegments = realPhaseSegments(vecPoints);
double dTotalDuration = 0.0;
for (int nIndex = 0; nIndex < vecSegments.size(); ++nIndex)
{
dTotalDuration += vecSegments[nIndex].x();
}
listLines << QString(" %1: %2")
.arg(sPhaseName)
.arg(nmWellPressureFlowPage::tr(
"raw points = %1, real segments = %2, total duration = %3")
.arg(vecPoints.size())
.arg(vecSegments.size())
.arg(diagNumberText(dTotalDuration)));
listLines << QLatin1String(" ") +
nmWellPressureFlowPage::tr("First point = %1, last point = %2")
.arg(diagPointText(vecPoints.first()))
.arg(diagPointText(vecPoints.last()));
// 点数较少时列出全部原始点,便于核对各相中间点数值是否分相正确。
const int nMaxListedPoints = 16;
if (vecPoints.size() <= nMaxListedPoints)
{
QStringList listPointTexts;
for (int nIndex = 0; nIndex < vecPoints.size(); ++nIndex)
{
listPointTexts << diagPointText(vecPoints[nIndex]);
}
listLines << QLatin1String(" ") +
nmWellPressureFlowPage::tr("All points = %1")
.arg(listPointTexts.join(QLatin1String(", ")));
}
if (hasLeadingPlaceholderPoint(vecPoints))
{
listLines << QLatin1String(" ") +
nmWellPressureFlowPage::tr("first point is a (0,0) placeholder");
}
}
// 检查多相记录有效相之间段数与每段时间是否完全一致,返回结论文本。
QString diagPhaseConsistencyText(const nmFlowGaugeRecord& oRecord)
{
// 只统计有真实数据的相,与显示相下拉框的判定保持一致;比较前先去除占位点。
QStringList listPhaseNames;
QVector<QVector<QPointF> > listPhaseSegments;
if (hasRealPhaseData(oRecord.vecOilPoints))
{
listPhaseNames.append(nmWellPressureFlowPage::tr("Oil"));
listPhaseSegments.append(realPhaseSegments(oRecord.vecOilPoints));
}
if (hasRealPhaseData(oRecord.vecGasPoints))
{
listPhaseNames.append(nmWellPressureFlowPage::tr("Gas"));
listPhaseSegments.append(realPhaseSegments(oRecord.vecGasPoints));
}
if (hasRealPhaseData(oRecord.vecWaterPoints))
{
listPhaseNames.append(nmWellPressureFlowPage::tr("Water"));
listPhaseSegments.append(realPhaseSegments(oRecord.vecWaterPoints));
}
if (listPhaseSegments.size() < 2)
{
return nmWellPressureFlowPage::tr(
"less than two phases with real data, no comparison");
}
// 先比较段数,段数不同时直接列出各相段数。
for (int nPhase = 1; nPhase < listPhaseSegments.size(); ++nPhase)
{
if (listPhaseSegments[nPhase].size() !=
listPhaseSegments.first().size())
{
QStringList listCounts;
for (int nItem = 0; nItem < listPhaseSegments.size(); ++nItem)
{
listCounts << QString("%1 = %2")
.arg(listPhaseNames[nItem])
.arg(listPhaseSegments[nItem].size());
}
return nmWellPressureFlowPage::tr("segment counts differ: %1")
.arg(listCounts.join(QLatin1String(", ")));
}
}
// 段数一致后逐段严格按位比较时间,不引入容差,浮点差异按不一致处理。
for (int nSegment = 0;
nSegment < listPhaseSegments.first().size();
++nSegment)
{
for (int nPhase = 1; nPhase < listPhaseSegments.size(); ++nPhase)
{
if (listPhaseSegments[nPhase][nSegment].x() !=
listPhaseSegments.first()[nSegment].x())
{
return nmWellPressureFlowPage::tr(
"segment counts equal (%1), but segment %2 time differs: %3 = %4, %5 = %6")
.arg(listPhaseSegments.first().size())
.arg(nSegment + 1)
.arg(listPhaseNames.first())
.arg(diagNumberText(
listPhaseSegments.first()[nSegment].x()))
.arg(listPhaseNames[nPhase])
.arg(diagNumberText(
listPhaseSegments[nPhase][nSegment].x()));
}
}
}
return nmWellPressureFlowPage::tr(
"segment counts equal (%1) and all segment times identical")
.arg(listPhaseSegments.first().size());
}
// 输出一个原始字节块按 VVecVariant 解码后的形状和少量样本值。
// 诊断只读取数据,不参与正式的流量记录状态判定。
void appendRawVVecDiagnostic(QStringList& listLines,
const QString& sBlockName,
const QByteArray& baSource)
{
listLines << QString(" %1 bytes: %2")
.arg(sBlockName)
.arg(baSource.size());
if (baSource.isEmpty())
{
listLines << QString(" %1 is empty").arg(sBlockName);
return;
}
QByteArray baData = baSource;
VVecVariant vvecData;
const bool bDecoded = ZxBaHelper::convertBa2VVec(vvecData, baData);
listLines << QString(" VVec decode: %1")
.arg(bDecoded ? QLatin1String("success")
: QLatin1String("failed"));
if (!bDecoded)
{
return;
}
QStringList listSizes;
for (int nVectorIndex = 0;
nVectorIndex < vvecData.size();
++nVectorIndex)
{
listSizes << QString("[%1]=%2")
.arg(nVectorIndex)
.arg(vvecData[nVectorIndex].size());
}
listLines << QString(" Vector count: %1; sizes: %2")
.arg(vvecData.size())
.arg(listSizes.isEmpty()
? QLatin1String("(none)")
: listSizes.join(QLatin1String(", ")));
// GaugeDataEx2 在框架中的正式布局是 N 行 x 4 列,直接按业务字段展示。
if (sBlockName == QLatin1String("GaugeDataEx2"))
{
bool bRowLayoutValid = !vvecData.isEmpty();
for (int nStoredRow = 0;
bRowLayoutValid && nStoredRow < vvecData.size();
++nStoredRow)
{
bRowLayoutValid = vvecData[nStoredRow].size() == 4;
}
listLines << QString(" N x 4 row layout valid: %1")
.arg(bRowLayoutValid ? QLatin1String("Yes")
: QLatin1String("No"));
if (bRowLayoutValid)
{
const int nMaxStoredRows = 8;
const int nStoredRowCount = qMin(
vvecData.size(), nMaxStoredRows);
for (int nStoredRow = 0;
nStoredRow < nStoredRowCount;
++nStoredRow)
{
listLines << QString(" Stored row %1: duration=%2, oil=%3, gas=%4, water=%5")
.arg(nStoredRow + 1)
.arg(vvecData[nStoredRow][0].toString())
.arg(vvecData[nStoredRow][1].toString())
.arg(vvecData[nStoredRow][2].toString())
.arg(vvecData[nStoredRow][3].toString());
}
return;
}
}
// 其他未知形状的扩展块按矩阵公共范围输出样本,便于继续排查。
const int nMaxSampleRows = 8;
int nCommonSize = vvecData.isEmpty() ? 0 : vvecData.first().size();
for (int nVectorIndex = 1;
nVectorIndex < vvecData.size();
++nVectorIndex)
{
nCommonSize = qMin(nCommonSize, vvecData[nVectorIndex].size());
}
const int nSampleRows = qMin(nCommonSize, nMaxSampleRows);
for (int nRowIndex = 0; nRowIndex < nSampleRows; ++nRowIndex)
{
QStringList listValues;
for (int nVectorIndex = 0;
nVectorIndex < vvecData.size();
++nVectorIndex)
{
listValues << QString("[%1]=%2")
.arg(nVectorIndex)
.arg(vvecData[nVectorIndex][nRowIndex].toString());
}
listLines << QString(" Row %1: %2")
.arg(nRowIndex + 1)
.arg(listValues.join(QLatin1String(", ")));
}
}
// 直接定位当前数值井对应的框架井,检查每条流量记录的三个存储块及
// getGaugeDataOf() 返回值,区分“数值层解析错误”和“框架原始数据形状异常”。
void appendFrameworkFlowStorageDiagnostic(
QStringList& listLines,
const QString& sWellCode,
const QString& sSelectedFlowGaugeCode)
{
listLines << QString();
listLines << QLatin1String("=== Framework raw flow storage ===");
listLines << QString("Requested well code: %1")
.arg(sWellCode.isEmpty() ? QLatin1String("(none)") : sWellCode);
listLines << QString("Selected flow gauge code: %1")
.arg(sSelectedFlowGaugeCode.isEmpty()
? QLatin1String("(none)") : sSelectedFlowGaugeCode);
if (zxCurProject == nullptr)
{
listLines << QLatin1String("Current framework project: unavailable");
return;
}
const ZxDataObjectList listWells =
zxCurProject->getChildren(iDataModelType::sTypeWell);
ZxDataWell* pMatchedWell = nullptr;
int nMatchedWellCount = 0;
for (int nWellIndex = 0;
nWellIndex < listWells.size();
++nWellIndex)
{
ZxDataWell* pFrameworkWell =
dynamic_cast<ZxDataWell*>(listWells[nWellIndex]);
if (pFrameworkWell != nullptr &&
pFrameworkWell->getCode() == sWellCode)
{
++nMatchedWellCount;
if (pMatchedWell == nullptr)
{
pMatchedWell = pFrameworkWell;
}
}
}
listLines << QString("Framework wells matched by code: %1")
.arg(nMatchedWellCount);
if (pMatchedWell == nullptr)
{
return;
}
listLines << QString("Matched framework well name: %1")
.arg(pMatchedWell->getName());
listLines << QString("Framework well type index: %1")
.arg(pMatchedWell->getWellTypeIndex());
const ZxDataObjectList listGaugeF = pMatchedWell->getChildren(
iDataModelType::sTypeDataGaugeF);
listLines << QString("Framework flow-record count: %1")
.arg(listGaugeF.size());
bool bSelectedGaugeFound = false;
for (int nGaugeIndex = 0;
nGaugeIndex < listGaugeF.size();
++nGaugeIndex)
{
ZxDataGaugeF* pGaugeF =
dynamic_cast<ZxDataGaugeF*>(listGaugeF[nGaugeIndex]);
if (pGaugeF == nullptr)
{
listLines << QString("[%1] Object is not ZxDataGaugeF")
.arg(nGaugeIndex + 1);
continue;
}
const bool bIsSelected =
pGaugeF->getCode() == sSelectedFlowGaugeCode;
bSelectedGaugeFound = bSelectedGaugeFound || bIsSelected;
const QByteArray baGaugeData = pGaugeF->getGaugeData();
const QByteArray baGaugeDataEx2 = pGaugeF->getGaugeDataEx2();
const QByteArray baGaugeDataEx3 = pGaugeF->getGaugeDataEx3();
const QByteArray baDataOf0 = pGaugeF->getGaugeDataOf(0);
const QByteArray baDataOf1 = pGaugeF->getGaugeDataOf(1);
const QByteArray baDataOf2 = pGaugeF->getGaugeDataOf(2);
listLines << QString();
listLines << QString("[%1] Code: %2; name: %3; selected: %4")
.arg(nGaugeIndex + 1)
.arg(pGaugeF->getCode())
.arg(pGaugeF->getGaugeName())
.arg(bIsSelected ? QLatin1String("Yes")
: QLatin1String("No"));
listLines << QString(" Time: %1; multiphase flag: %2")
.arg(pGaugeF->getGaugeTime())
.arg(pGaugeF->getMultiPhase()
? QLatin1String("Yes") : QLatin1String("No"));
// 主块按单相 XY 格式再解一次,确认单相数据是否仍然存在。
QByteArray baMainCopy = baGaugeData;
VecDouble vecMainX;
VecDouble vecMainY;
const bool bMainDecoded = !baGaugeData.isEmpty() &&
ZxBaHelper::convertBa2VecXY(
vecMainX, vecMainY, baMainCopy);
listLines << QString(" GaugeData bytes: %1; XY decode: %2; X/Y sizes: %3/%4")
.arg(baGaugeData.size())
.arg(bMainDecoded ? QLatin1String("success")
: QLatin1String("failed"))
.arg(static_cast<int>(vecMainX.size()))
.arg(static_cast<int>(vecMainY.size()));
appendRawVVecDiagnostic(
listLines, QLatin1String("GaugeDataEx2"), baGaugeDataEx2);
appendRawVVecDiagnostic(
listLines, QLatin1String("GaugeDataEx3"), baGaugeDataEx3);
listLines << QString(" getGaugeDataOf sizes: [0]=%1, [1]=%2, [2]=%3")
.arg(baDataOf0.size())
.arg(baDataOf1.size())
.arg(baDataOf2.size());
listLines << QString(" getGaugeDataOf equality: [0]=GaugeData %1, [1]=Ex2 %2, [2]=Ex3 %3")
.arg(baDataOf0 == baGaugeData
? QLatin1String("Yes") : QLatin1String("No"))
.arg(baDataOf1 == baGaugeDataEx2
? QLatin1String("Yes") : QLatin1String("No"))
.arg(baDataOf2 == baGaugeDataEx3
? QLatin1String("Yes") : QLatin1String("No"));
}
listLines << QString();
listLines << QString("Selected gauge found under matched framework well: %1")
.arg(bSelectedGaugeFound
? QLatin1String("Yes") : QLatin1String("No"));
}
}
nmWellPressureFlowPage::nmWellPressureFlowPage(QWidget* pParent)
: QWidget(pParent),
m_pWell(nullptr),
m_pContentStack(nullptr),
m_pHorizontalSplitter(nullptr),
m_pChartSplitter(nullptr),
m_pPressureChartStack(nullptr),
m_pFlowChartStack(nullptr),
m_pFlowTableStack(nullptr),
m_pPressureChart(nullptr),
m_pFlowChart(nullptr),
m_pFlowTable(nullptr),
m_pFlowHeader(nullptr),
m_pTimeUnitGroup(nullptr),
m_pFlowUnitGroup(nullptr),
m_sTimeBaseUnit(preferredTimeUnit()),
m_sFlowBaseUnit(preferredFlowUnit()),
m_sTimeUnit(preferredTimeUnit()),
m_sFlowUnit(preferredFlowUnit()),
m_nTimeDigit(6),
m_nFlowDigit(6),
m_nSelectedFlowSegment(-1),
m_pRecordSelectionBar(nullptr),
m_pPressureGaugeCombo(nullptr),
m_pFlowGaugeCombo(nullptr),
m_pDisplayPhaseCombo(nullptr),
m_pDisplayPhaseContainer(nullptr),
m_pCheckDataButton(nullptr),
m_eDisplayPhase(PHASE_UNKNOWN)
{
createUi();
initializeUnitSupport();
resetDataViews();
}
void nmWellPressureFlowPage::setWell(nmDataWellBase* pWell)
{
if (m_pWell == pWell)
{
return;
}
m_pWell = pWell;
resetDataViews();
}
void nmWellPressureFlowPage::refreshData()
{
if (m_pWell == nullptr)
{
resetDataViews();
return;
}
// 第一步:先刷新记录下拉框,确保当前选中 Code、显示相与后续取点保持一致。
populatePressureGaugeCombo();
populateFlowGaugeCombo();
populateDisplayPhaseCombo(currentFlowRecord());
// 第二步:井数据只复制到页面基准副本,后续显示换算不会回写井对象;
// 流量点按当前展示相读取切换展示相不影响井别、m_nIndexF 或求解器输入。
QVector<QPointF> vecRawPressurePoints =
m_pWell->getPressurePoints();
QVector<QPointF> vecRawFlowPoints =
m_pWell->getFlowPoints(m_eDisplayPhase);
// 第三步:单位服务可能晚于页面构造完成,再刷新一次可用单位组。
initializeUnitSupport();
m_vecRawPressurePoints = vecRawPressurePoints;
m_vecRawFlowPoints = vecRawFlowPoints;
QVector<QPointF> vecTableFlowPoints;
int nTimeDigit = m_nTimeDigit;
int nFlowDigit = m_nFlowDigit;
if (!buildFlowTableData(m_sTimeUnit,
m_sFlowUnit,
vecTableFlowPoints,
nTimeDigit,
nFlowDigit))
{
// 当前显示单位不可用时回退基准单位,禁止继续展示旧井快照。
m_sTimeUnit = m_sTimeBaseUnit;
m_sFlowUnit = m_sFlowBaseUnit;
if (!buildFlowTableData(m_sTimeUnit,
m_sFlowUnit,
vecTableFlowPoints,
nTimeDigit,
nFlowDigit))
{
resetDataViews();
updateUnitHeader();
return;
}
}
// 第三步:换算与精度读取全部成功后再提交,曲线始终读取原始数据。
m_vecTableFlowPoints = vecTableFlowPoints;
m_nTimeDigit = nTimeDigit;
m_nFlowDigit = nFlowDigit;
updateUnitHeader();
updateViews();
}
void nmWellPressureFlowPage::clearData()
{
m_pWell = nullptr;
resetDataViews();
}
void nmWellPressureFlowPage::slotUnitChanged(
int nColumn,
const iUnitItem* pSourceUnit,
const iUnitItem* pDestinationUnit)
{
Q_UNUSED(pSourceUnit);
if (pDestinationUnit == nullptr ||
(nColumn != 0 && nColumn != 1))
{
updateUnitHeader();
return;
}
QString sTimeUnit = m_sTimeUnit;
QString sFlowUnit = m_sFlowUnit;
if (nColumn == 0)
{
if (m_pTimeUnitGroup == nullptr)
{
updateUnitHeader();
return;
}
sTimeUnit = pDestinationUnit->m_sName;
}
else
{
if (m_pFlowUnitGroup == nullptr)
{
updateUnitHeader();
return;
}
sFlowUnit = pDestinationUnit->m_sName;
}
// 每次都从基准副本换算,禁止使用当前显示值继续换算。
QVector<QPointF> vecTableFlowPoints;
int nTimeDigit = m_nTimeDigit;
int nFlowDigit = m_nFlowDigit;
if (!buildFlowTableData(sTimeUnit,
sFlowUnit,
vecTableFlowPoints,
nTimeDigit,
nFlowDigit))
{
updateUnitHeader();
return;
}
m_sTimeUnit = sTimeUnit;
m_sFlowUnit = sFlowUnit;
m_vecTableFlowPoints = vecTableFlowPoints;
m_nTimeDigit = nTimeDigit;
m_nFlowDigit = nFlowDigit;
updateUnitHeader();
updateFlowTable();
}
void nmWellPressureFlowPage::slotFlowRowChanged(
int nCurrentRow,
int nCurrentColumn,
int nPreviousRow,
int nPreviousColumn)
{
Q_UNUSED(nCurrentColumn);
Q_UNUSED(nPreviousRow);
Q_UNUSED(nPreviousColumn);
applyFlowSegmentSelection(nCurrentRow);
}
void nmWellPressureFlowPage::slotFlowChartPointClicked(double dTimePoint)
{
// 曲线保持基准单位,点击时间先换算为表格当前单位再定位行。
double dTableTimePoint = 0.0;
if (!convertValue(m_pTimeUnitGroup,
m_sTimeBaseUnit,
m_sTimeUnit,
dTimePoint,
dTableTimePoint))
{
applyFlowSegmentSelection(-1);
return;
}
applyFlowSegmentSelection(
findFlowSegmentByTableTime(dTableTimePoint));
}
void nmWellPressureFlowPage::slotDisplayPhaseChanged(int nIndex)
{
if (m_pWell == nullptr || nIndex < 0)
{
return;
}
NM_PHASE_TYPE eNewPhase = static_cast<NM_PHASE_TYPE>(
m_pDisplayPhaseCombo->itemData(nIndex).toInt());
if (eNewPhase == m_eDisplayPhase)
{
return;
}
// 仅重新按新相取点刷新视图不改变记录内容、井别、m_nIndexF 或求解器输入。
m_eDisplayPhase = eNewPhase;
refreshData();
}
void nmWellPressureFlowPage::slotCheckDataClicked()
{
// 诊断窗口为模态只读展示,允许缩放、滚动与复制文本,不修改任何业务数据。
QDialog oDialog(this);
oDialog.setWindowTitle(tr("Pressure/Flow Data Check"));
oDialog.setWindowFlags(oDialog.windowFlags() |
Qt::WindowMaximizeButtonHint);
oDialog.resize(820, 600);
QVBoxLayout* pDialogLayout = new QVBoxLayout(&oDialog);
QPlainTextEdit* pTextEdit = new QPlainTextEdit(&oDialog);
pTextEdit->setReadOnly(true);
pTextEdit->setLineWrapMode(QPlainTextEdit::NoWrap);
pTextEdit->setPlainText(buildDiagnosticText());
QPushButton* pCloseButton = new QPushButton(tr("Close"), &oDialog);
QHBoxLayout* pButtonLayout = new QHBoxLayout();
pButtonLayout->addStretch(1);
pButtonLayout->addWidget(pCloseButton);
pDialogLayout->addWidget(pTextEdit, 1);
pDialogLayout->addLayout(pButtonLayout);
connect(pCloseButton, SIGNAL(clicked()), &oDialog, SLOT(accept()));
oDialog.exec();
}
void nmWellPressureFlowPage::createUi()
{
QVBoxLayout* pMainLayout = new QVBoxLayout(this);
pMainLayout->setContentsMargins(8, 6, 8, 8);
pMainLayout->setSpacing(0);
m_pContentStack = new QStackedWidget(this);
m_pHorizontalSplitter = new QSplitter(
Qt::Horizontal, m_pContentStack);
m_pHorizontalSplitter->setChildrenCollapsible(false);
// 左侧上下两图共享时间范围,但各自保留独立空状态。
m_pChartSplitter = new QSplitter(
Qt::Vertical, m_pHorizontalSplitter);
m_pChartSplitter->setChildrenCollapsible(false);
m_pPressureChartStack = new QStackedWidget(m_pChartSplitter);
m_pPressureChart = new nmWxPressFlowChartWidget(
m_pPressureChartStack);
m_pPressureChart->setReadOnly(true);
m_pPressureChart->setTimeSelectionEnabled(false);
m_pPressureChart->setAxisLabels(tr("Time"), tr("Pressure"));
m_pPressureChart->setAxisUnits(
m_sTimeBaseUnit, tr("MPa"));
m_pPressureChart->setLineColor(QColor(50, 200, 50));
m_pPressureChart->setChartType(CHART_TYPE_LINE);
m_pPressureChartStack->addWidget(m_pPressureChart);
m_pPressureChartStack->addWidget(createEmptyLabel(
tr("No pressure data"), m_pPressureChartStack));
m_pFlowChartStack = new QStackedWidget(m_pChartSplitter);
m_pFlowChart = new nmWxPressFlowChartWidget(m_pFlowChartStack);
m_pFlowChart->setReadOnly(true);
m_pFlowChart->setTimeSelectionEnabled(true);
m_pFlowChart->setAxisLabels(tr("Time"), tr("Flow"));
m_pFlowChart->setAxisUnits(m_sTimeBaseUnit, m_sFlowBaseUnit);
m_pFlowChart->setLineColor(QColor(255, 0, 0));
m_pFlowChart->setChartType(CHART_TYPE_STEP);
m_pFlowChartStack->addWidget(m_pFlowChart);
m_pFlowChartStack->addWidget(createEmptyLabel(
tr("No flow data"), m_pFlowChartStack));
m_pChartSplitter->addWidget(m_pPressureChartStack);
m_pChartSplitter->addWidget(m_pFlowChartStack);
m_pChartSplitter->setStretchFactor(0, 1);
m_pChartSplitter->setStretchFactor(1, 1);
// 右侧仅显示原始流量段对应的时间和流量,不提供任何编辑入口。
m_pFlowTableStack = new QStackedWidget(m_pHorizontalSplitter);
m_pFlowTable = new QTableWidget(m_pFlowTableStack);
m_pFlowTable->setColumnCount(2);
m_pFlowTable->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_pFlowTable->setSelectionBehavior(QAbstractItemView::SelectRows);
m_pFlowTable->setSelectionMode(QAbstractItemView::SingleSelection);
m_pFlowTable->setSortingEnabled(false);
m_pFlowTable->setDragEnabled(false);
m_pFlowTable->setAcceptDrops(false);
m_pFlowTable->setDropIndicatorShown(false);
m_pFlowTable->setAlternatingRowColors(true);
m_pFlowTable->setStyleSheet(
"QTableWidget {"
" background-color: palette(base);"
" alternate-background-color: rgb(232, 232, 232);"
" gridline-color: palette(midlight);"
" border: 1px solid palette(mid);"
"}"
"QHeaderView {"
" background-color: palette(button);"
" color: palette(button-text);"
"}"
"QHeaderView::section {"
" background-color: palette(button);"
" color: palette(button-text);"
" border: none;"
" border-right: 1px solid palette(mid);"
" border-bottom: 1px solid palette(mid);"
"}"
"QTableCornerButton::section {"
" background-color: palette(base);"
" border: none;"
" border-right: 1px solid palette(mid);"
" border-bottom: 1px solid palette(mid);"
"}");
m_pFlowHeader = new ZxTableHeaderViewUnit(m_pFlowTable);
m_pFlowTable->setHorizontalHeader(m_pFlowHeader);
m_pFlowTable->horizontalHeader()->setMovable(false);
m_pFlowTable->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
m_pFlowTable->verticalHeader()->setMovable(false);
m_pFlowTable->verticalHeader()->setDefaultAlignment(Qt::AlignCenter);
m_pFlowTable->verticalHeader()->setMinimumWidth(18);
m_pFlowTableStack->addWidget(m_pFlowTable);
m_pFlowTableStack->addWidget(createEmptyLabel(
tr("No flow data"), m_pFlowTableStack));
m_pHorizontalSplitter->addWidget(m_pChartSplitter);
m_pHorizontalSplitter->addWidget(m_pFlowTableStack);
m_pHorizontalSplitter->setStretchFactor(0, 7);
m_pHorizontalSplitter->setStretchFactor(1, 3);
QList<int> listHorizontalSizes;
listHorizontalSizes << 700 << 300;
m_pHorizontalSplitter->setSizes(listHorizontalSizes);
m_pContentStack->addWidget(m_pHorizontalSplitter);
m_pContentStack->addWidget(createEmptyLabel(
tr("No pressure or flow data"), m_pContentStack));
// 顶部插入压力/流量记录与显示相三个下拉框所在行,不改变下方图表/表格结构。
createRecordSelectionBar();
pMainLayout->addWidget(m_pRecordSelectionBar);
pMainLayout->addWidget(m_pContentStack, 1);
connect(m_pFlowHeader,
SIGNAL(sigCbxValueChanged(int,const iUnitItem*,const iUnitItem*)),
this,
SLOT(slotUnitChanged(int,const iUnitItem*,const iUnitItem*)));
connect(m_pFlowTable,
SIGNAL(currentCellChanged(int,int,int,int)),
this,
SLOT(slotFlowRowChanged(int,int,int,int)));
connect(m_pFlowChart,
SIGNAL(chartPointClicked(double)),
this,
SLOT(slotFlowChartPointClicked(double)));
}
void nmWellPressureFlowPage::createRecordSelectionBar()
{
m_pRecordSelectionBar = new QWidget(this);
QHBoxLayout* pBarLayout = new QHBoxLayout(m_pRecordSelectionBar);
pBarLayout->setContentsMargins(0, 0, 0, 6);
pBarLayout->setSpacing(6);
QLabel* pPressureLabel = new QLabel(
tr("Pressure Record"), m_pRecordSelectionBar);
m_pPressureGaugeCombo = new QComboBox(m_pRecordSelectionBar);
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->setMinimumWidth(160);
m_pPressureGaugeCombo->setToolTip(
tr("This page only displays the selected record."));
QLabel* pFlowLabel = new QLabel(
tr("Flow Record"), m_pRecordSelectionBar);
m_pFlowGaugeCombo = new QComboBox(m_pRecordSelectionBar);
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->setMinimumWidth(160);
m_pFlowGaugeCombo->setToolTip(
tr("This page only displays the selected record."));
// 显示相是纯展示状态,整体容器随记录是否为多相含数据而隐藏/显示。
m_pDisplayPhaseContainer = new QWidget(m_pRecordSelectionBar);
QHBoxLayout* pPhaseLayout = new QHBoxLayout(m_pDisplayPhaseContainer);
pPhaseLayout->setContentsMargins(0, 0, 0, 0);
pPhaseLayout->setSpacing(6);
QLabel* pPhaseLabel = new QLabel(
tr("Display Phase"), m_pDisplayPhaseContainer);
m_pDisplayPhaseCombo = new QComboBox(m_pDisplayPhaseContainer);
m_pDisplayPhaseCombo->setMinimumWidth(80);
pPhaseLayout->addWidget(pPhaseLabel);
pPhaseLayout->addWidget(m_pDisplayPhaseCombo);
m_pDisplayPhaseContainer->setVisible(false);
// 临时诊断按钮:只读展示井对象解析结果,验证完成后再决定删除或保留。
m_pCheckDataButton = new QPushButton(
tr("Check Data"), m_pRecordSelectionBar);
pBarLayout->addWidget(pPressureLabel);
pBarLayout->addWidget(m_pPressureGaugeCombo);
pBarLayout->addSpacing(12);
pBarLayout->addWidget(pFlowLabel);
pBarLayout->addWidget(m_pFlowGaugeCombo);
pBarLayout->addSpacing(12);
pBarLayout->addWidget(m_pDisplayPhaseContainer);
pBarLayout->addStretch(1);
pBarLayout->addWidget(m_pCheckDataButton);
connect(m_pDisplayPhaseCombo,
SIGNAL(currentIndexChanged(int)),
this,
SLOT(slotDisplayPhaseChanged(int)));
connect(m_pCheckDataButton,
SIGNAL(clicked()),
this,
SLOT(slotCheckDataClicked()));
}
void nmWellPressureFlowPage::populatePressureGaugeCombo()
{
if (m_pPressureGaugeCombo == nullptr)
{
return;
}
bool bSignalsWereBlocked = m_pPressureGaugeCombo->blockSignals(true);
m_pPressureGaugeCombo->clear();
if (m_pWell == nullptr)
{
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
return;
}
QVector<nmPressureGaugeRecord> vecRecords =
m_pWell->getPressureRecords();
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->addItem(tr("None"), QString());
const QString sSelectedCode = m_pWell->getSelectedPressureGaugeCode();
int nCurrentIndex = 0;
for (int nIndex = 0; nIndex < vecRecords.size(); ++nIndex)
{
const nmPressureGaugeRecord& oRecord = vecRecords[nIndex];
appendGaugeComboItem(m_pPressureGaugeCombo,
oRecord.sGaugeName, oRecord.sGaugeCode,
oRecord.sGaugeTime, oRecord.eStatus);
if (oRecord.sGaugeCode == sSelectedCode)
{
nCurrentIndex = nIndex + 1;
}
}
m_pPressureGaugeCombo->setCurrentIndex(nCurrentIndex);
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
}
void nmWellPressureFlowPage::populateFlowGaugeCombo()
{
if (m_pFlowGaugeCombo == nullptr)
{
return;
}
bool bSignalsWereBlocked = m_pFlowGaugeCombo->blockSignals(true);
m_pFlowGaugeCombo->clear();
if (m_pWell == nullptr)
{
m_vecFlowGaugeRecords.clear();
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
return;
}
// 缓存整份记录列表供 currentFlowRecord() 安全返回内部指针,记录内容切换记录时不变。
m_vecFlowGaugeRecords = m_pWell->getFlowRecords();
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->addItem(tr("None"), QString());
const QString sSelectedCode = m_pWell->getSelectedFlowGaugeCode();
int nCurrentIndex = 0;
for (int nIndex = 0; nIndex < m_vecFlowGaugeRecords.size(); ++nIndex)
{
const nmFlowGaugeRecord& oRecord = m_vecFlowGaugeRecords[nIndex];
appendGaugeComboItem(m_pFlowGaugeCombo,
oRecord.sGaugeName, oRecord.sGaugeCode,
oRecord.sGaugeTime, oRecord.eStatus);
if (oRecord.sGaugeCode == sSelectedCode)
{
nCurrentIndex = nIndex + 1;
}
}
m_pFlowGaugeCombo->setCurrentIndex(nCurrentIndex);
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
}
void nmWellPressureFlowPage::populateDisplayPhaseCombo(
const nmFlowGaugeRecord* pRecord)
{
if (m_pDisplayPhaseCombo == nullptr ||
m_pDisplayPhaseContainer == nullptr)
{
return;
}
bool bSignalsWereBlocked = m_pDisplayPhaseCombo->blockSignals(true);
m_pDisplayPhaseCombo->clear();
if (pRecord == nullptr)
{
m_pDisplayPhaseContainer->setVisible(false);
m_eDisplayPhase = PHASE_UNKNOWN;
m_pDisplayPhaseCombo->blockSignals(bSignalsWereBlocked);
return;
}
// 仅列出记录中真实含数据的相;若记录只有 1 个相有数据,整行隐藏但仍取该相的点。
QVector<NM_PHASE_TYPE> vecAvailablePhases;
QStringList listAvailableLabels;
if (hasRealPhaseData(pRecord->vecOilPoints))
{
vecAvailablePhases.append(PHASE_Oil);
listAvailableLabels.append(tr("Oil"));
}
if (hasRealPhaseData(pRecord->vecGasPoints))
{
vecAvailablePhases.append(PHASE_Gas);
listAvailableLabels.append(tr("Gas"));
}
if (hasRealPhaseData(pRecord->vecWaterPoints))
{
vecAvailablePhases.append(PHASE_Water);
listAvailableLabels.append(tr("Water"));
}
if (vecAvailablePhases.size() <= 1)
{
m_pDisplayPhaseContainer->setVisible(false);
m_eDisplayPhase = vecAvailablePhases.isEmpty()
? PHASE_UNKNOWN : vecAvailablePhases.first();
m_pDisplayPhaseCombo->blockSignals(bSignalsWereBlocked);
return;
}
m_pDisplayPhaseContainer->setVisible(true);
int nCurrentIndex = -1;
for (int nIndex = 0; nIndex < vecAvailablePhases.size(); ++nIndex)
{
m_pDisplayPhaseCombo->addItem(listAvailableLabels[nIndex],
static_cast<int>(vecAvailablePhases[nIndex]));
if (vecAvailablePhases[nIndex] == m_eDisplayPhase)
{
nCurrentIndex = nIndex;
}
}
if (nCurrentIndex < 0)
{
NM_PHASE_TYPE eDefaultPhase = defaultDisplayPhase(pRecord);
for (int nIndex = 0; nIndex < vecAvailablePhases.size(); ++nIndex)
{
if (vecAvailablePhases[nIndex] == eDefaultPhase)
{
nCurrentIndex = nIndex;
break;
}
}
}
if (nCurrentIndex < 0)
{
nCurrentIndex = 0;
}
m_pDisplayPhaseCombo->setCurrentIndex(nCurrentIndex);
m_eDisplayPhase = static_cast<NM_PHASE_TYPE>(
m_pDisplayPhaseCombo->itemData(nCurrentIndex).toInt());
m_pDisplayPhaseCombo->blockSignals(bSignalsWereBlocked);
}
NM_PHASE_TYPE nmWellPressureFlowPage::defaultDisplayPhase(
const nmFlowGaugeRecord* pRecord) const
{
if (pRecord == nullptr)
{
return PHASE_UNKNOWN;
}
// 优先使用井别对应的参考相,前提是该相在本条记录中确有真实数据。
NM_PHASE_TYPE eReferencePhase = (m_pWell != nullptr)
? m_pWell->getReferenceFlowPhase() : PHASE_UNKNOWN;
if (eReferencePhase == PHASE_Oil && hasRealPhaseData(pRecord->vecOilPoints))
{
return PHASE_Oil;
}
if (eReferencePhase == PHASE_Gas && hasRealPhaseData(pRecord->vecGasPoints))
{
return PHASE_Gas;
}
if (eReferencePhase == PHASE_Water && hasRealPhaseData(pRecord->vecWaterPoints))
{
return PHASE_Water;
}
// 参考相无数据时,退回记录中第一个有真实数据的相,固定按油、气、水顺序。
if (hasRealPhaseData(pRecord->vecOilPoints))
{
return PHASE_Oil;
}
if (hasRealPhaseData(pRecord->vecGasPoints))
{
return PHASE_Gas;
}
if (hasRealPhaseData(pRecord->vecWaterPoints))
{
return PHASE_Water;
}
return PHASE_UNKNOWN;
}
const nmFlowGaugeRecord* nmWellPressureFlowPage::currentFlowRecord() const
{
if (m_pWell == nullptr)
{
return nullptr;
}
const QString sSelectedCode = m_pWell->getSelectedFlowGaugeCode();
for (int nIndex = 0; nIndex < m_vecFlowGaugeRecords.size(); ++nIndex)
{
if (m_vecFlowGaugeRecords[nIndex].sGaugeCode == sSelectedCode)
{
return &m_vecFlowGaugeRecords[nIndex];
}
}
return nullptr;
}
void nmWellPressureFlowPage::initializeUnitSupport()
{
QStringList listTimeCandidates;
listTimeCandidates << preferredTimeUnit() << QLatin1String("hr");
QStringList listFlowCandidates;
listFlowCandidates << preferredFlowUnit()
<< QLatin1String("m^3/d")
<< QLatin1String("m^3/D");
// 优先使用当前单位包的h、m³/d旧英文单位包自动兼容hr、m^3/d。
m_pTimeUnitGroup = resolveUnitGroup(
listTimeCandidates, m_sTimeBaseUnit);
m_pFlowUnitGroup = resolveUnitGroup(
listFlowCandidates, m_sFlowBaseUnit);
// 单位组缺失或当前单位不再存在时回退基准单位,入口由框架表头禁用。
if (m_pTimeUnitGroup == nullptr ||
m_pTimeUnitGroup->indexOf(m_sTimeUnit) < 0)
{
m_sTimeUnit = m_sTimeBaseUnit;
}
if (m_pFlowUnitGroup == nullptr ||
m_pFlowUnitGroup->indexOf(m_sFlowUnit) < 0)
{
m_sFlowUnit = m_sFlowBaseUnit;
}
updateUnitHeader();
}
void nmWellPressureFlowPage::updateUnitHeader()
{
if (m_pFlowHeader == nullptr)
{
return;
}
QString sFlowTitle = tr("Flow");
switch (m_eDisplayPhase)
{
case PHASE_Oil:
sFlowTitle = tr("Oil phase flow");
break;
case PHASE_Gas:
sFlowTitle = tr("Gas phase flow");
break;
case PHASE_Water:
sFlowTitle = tr("Water phase flow");
break;
default:
break;
}
QStringList listTitles;
listTitles << tr("Time") << sFlowTitle;
QStringList listUnits;
listUnits << m_sTimeUnit << m_sFlowUnit;
m_pFlowHeader->setHeaderData(listTitles, listUnits);
}
bool nmWellPressureFlowPage::buildFlowTableData(
const QString& sTimeUnit,
const QString& sFlowUnit,
QVector<QPointF>& vecFlowPoints,
int& nTimeDigit,
int& nFlowDigit) const
{
vecFlowPoints = m_vecRawFlowPoints;
// 空数据也必须验证目标单位,避免表头切换后留下不可用单位状态。
double dConvertedZero = 0.0;
if (!convertValue(m_pTimeUnitGroup,
m_sTimeBaseUnit,
sTimeUnit,
0.0,
dConvertedZero,
&nTimeDigit) ||
!convertValue(m_pFlowUnitGroup,
m_sFlowBaseUnit,
sFlowUnit,
0.0,
dConvertedZero,
&nFlowDigit))
{
return false;
}
// 表格每次从原始副本换算,禁止使用当前显示值继续换算。
for (int nIndex = 0; nIndex < vecFlowPoints.size(); ++nIndex)
{
double dDuration = 0.0;
double dFlow = 0.0;
if (!convertValue(m_pTimeUnitGroup,
m_sTimeBaseUnit,
sTimeUnit,
m_vecRawFlowPoints[nIndex].x(),
dDuration) ||
!convertValue(m_pFlowUnitGroup,
m_sFlowBaseUnit,
sFlowUnit,
m_vecRawFlowPoints[nIndex].y(),
dFlow))
{
return false;
}
vecFlowPoints[nIndex] = QPointF(dDuration, dFlow);
}
return true;
}
bool nmWellPressureFlowPage::convertValue(
iUnitGroup* pUnitGroup,
const QString& sBaseUnit,
const QString& sDestinationUnit,
double dSourceValue,
double& dDestinationValue,
int* pDestinationDigit) const
{
int nDigit = 6;
if (pUnitGroup != nullptr)
{
iUnitItem* pUnitItem =
pUnitGroup->getUnitItem(sDestinationUnit);
if (pUnitItem != nullptr && pUnitItem->m_nDigit >= 0)
{
nDigit = pUnitItem->m_nDigit;
}
}
if (sDestinationUnit == sBaseUnit)
{
dDestinationValue = dSourceValue;
if (pDestinationDigit != nullptr)
{
*pDestinationDigit = nDigit;
}
return true;
}
if (pUnitGroup == nullptr ||
pUnitGroup->indexOf(sBaseUnit) < 0 ||
pUnitGroup->indexOf(sDestinationUnit) < 0)
{
return false;
}
int nFallbackDigit = nDigit;
bool bConverted = pUnitGroup->convert(sBaseUnit,
dSourceValue,
sDestinationUnit,
dDestinationValue,
nDigit);
if (bConverted && pDestinationDigit != nullptr)
{
*pDestinationDigit = nDigit >= 0
? nDigit : nFallbackDigit;
}
return bConverted;
}
void nmWellPressureFlowPage::updateViews()
{
QVector<QPointF> vecFlowChartPoints = createFlowChartPoints();
m_pPressureChart->setAxisUnits(
m_sTimeBaseUnit, tr("MPa"));
m_pFlowChart->setAxisUnits(m_sTimeBaseUnit, m_sFlowBaseUnit);
m_pPressureChart->setData(m_vecRawPressurePoints);
m_pFlowChart->setData(vecFlowChartPoints);
updateXAxisRange(vecFlowChartPoints);
updateFlowTable();
bool bPressureEmpty = m_vecRawPressurePoints.isEmpty();
bool bFlowEmpty = m_vecRawFlowPoints.isEmpty();
m_pPressureChartStack->setCurrentIndex(bPressureEmpty ? 1 : 0);
m_pFlowChartStack->setCurrentIndex(bFlowEmpty ? 1 : 0);
m_pFlowTableStack->setCurrentIndex(bFlowEmpty ? 1 : 0);
m_pContentStack->setCurrentIndex(
bPressureEmpty && bFlowEmpty ? 1 : 0);
updateSelectedFlowSegment();
}
QVector<QPointF> nmWellPressureFlowPage::createFlowChartPoints() const
{
QVector<QPointF> vecChartPoints;
double dTotalTime = 0.0;
for (int nIndex = 0;
nIndex < m_vecRawFlowPoints.size();
++nIndex)
{
const QPointF& oSegment = m_vecRawFlowPoints[nIndex];
if (nIndex == 0)
{
vecChartPoints.append(QPointF(0.0, oSegment.y()));
}
dTotalTime += oSegment.x();
vecChartPoints.append(QPointF(dTotalTime, oSegment.y()));
if (nIndex < m_vecRawFlowPoints.size() - 1 &&
qAbs(m_vecRawFlowPoints[nIndex + 1].y() -
oSegment.y()) > 1e-6)
{
vecChartPoints.append(QPointF(
dTotalTime,
m_vecRawFlowPoints[nIndex + 1].y()));
}
}
return vecChartPoints;
}
void nmWellPressureFlowPage::updateFlowTable()
{
bool bSignalsWereBlocked = m_pFlowTable->blockSignals(true);
m_pFlowTable->clearContents();
m_pFlowTable->setRowCount(m_vecTableFlowPoints.size());
// 纵向表头显示Qt默认的1N序号内部仍使用零基流量点索引。
for (int nIndex = 0;
nIndex < m_vecTableFlowPoints.size();
++nIndex)
{
QTableWidgetItem* pTimeItem = new QTableWidgetItem(
displayValueText(m_vecTableFlowPoints[nIndex].x(),
m_nTimeDigit));
pTimeItem->setTextAlignment(Qt::AlignCenter);
m_pFlowTable->setItem(nIndex, 0, pTimeItem);
QTableWidgetItem* pFlowItem = new QTableWidgetItem(
displayValueText(m_vecTableFlowPoints[nIndex].y(),
m_nFlowDigit));
pFlowItem->setTextAlignment(Qt::AlignCenter);
m_pFlowTable->setItem(nIndex, 1, pFlowItem);
}
if (m_nSelectedFlowSegment >= 0 &&
m_nSelectedFlowSegment < m_pFlowTable->rowCount())
{
m_pFlowTable->setCurrentCell(m_nSelectedFlowSegment, 0);
m_pFlowTable->selectRow(m_nSelectedFlowSegment);
}
else
{
m_nSelectedFlowSegment = -1;
m_pFlowTable->setCurrentCell(-1, -1);
m_pFlowTable->clearSelection();
}
m_pFlowTable->blockSignals(bSignalsWereBlocked);
}
void nmWellPressureFlowPage::updateXAxisRange(
const QVector<QPointF>& vecFlowChartPoints)
{
bool bHasRange = false;
double dMinimum = 0.0;
double dMaximum = 0.0;
for (int nIndex = 0;
nIndex < m_vecRawPressurePoints.size();
++nIndex)
{
double dTime = m_vecRawPressurePoints[nIndex].x();
dMinimum = bHasRange ? qMin(dMinimum, dTime) : dTime;
dMaximum = bHasRange ? qMax(dMaximum, dTime) : dTime;
bHasRange = true;
}
for (int nIndex = 0; nIndex < vecFlowChartPoints.size(); ++nIndex)
{
double dTime = vecFlowChartPoints[nIndex].x();
dMinimum = bHasRange ? qMin(dMinimum, dTime) : dTime;
dMaximum = bHasRange ? qMax(dMaximum, dTime) : dTime;
bHasRange = true;
}
if (bHasRange)
{
m_pPressureChart->setXAxisRange(dMinimum, dMaximum);
m_pFlowChart->setXAxisRange(dMinimum, dMaximum);
}
else
{
m_pPressureChart->clearXAxisRange();
m_pFlowChart->clearXAxisRange();
}
}
void nmWellPressureFlowPage::updateSelectedFlowSegment()
{
if (m_nSelectedFlowSegment < 0 ||
m_nSelectedFlowSegment >= m_vecRawFlowPoints.size())
{
m_pPressureChart->clearHighlightedTimeRange();
m_pFlowChart->clearHighlightedTimeRange();
return;
}
// 流量点X值是持续时间段区间必须从第0段开始逐段累加。
double dStartTime = 0.0;
for (int nIndex = 0;
nIndex < m_nSelectedFlowSegment;
++nIndex)
{
dStartTime += m_vecRawFlowPoints[nIndex].x();
}
double dEndTime = dStartTime +
m_vecRawFlowPoints[m_nSelectedFlowSegment].x();
m_pPressureChart->setHighlightedTimeRange(
dStartTime, dEndTime);
m_pFlowChart->setHighlightedTimeRange(dStartTime, dEndTime);
}
void nmWellPressureFlowPage::applyFlowSegmentSelection(int nSegmentIndex)
{
if (nSegmentIndex < 0 ||
nSegmentIndex >= m_vecRawFlowPoints.size() ||
nSegmentIndex >= m_pFlowTable->rowCount())
{
nSegmentIndex = -1;
}
m_nSelectedFlowSegment = nSegmentIndex;
bool bSignalsWereBlocked = m_pFlowTable->blockSignals(true);
if (m_nSelectedFlowSegment >= 0)
{
m_pFlowTable->setCurrentCell(m_nSelectedFlowSegment, 0);
m_pFlowTable->selectRow(m_nSelectedFlowSegment);
QTableWidgetItem* pItem = m_pFlowTable->item(
m_nSelectedFlowSegment, 0);
if (pItem != nullptr)
{
m_pFlowTable->scrollToItem(
pItem, QAbstractItemView::EnsureVisible);
}
}
else
{
m_pFlowTable->setCurrentCell(-1, -1);
m_pFlowTable->clearSelection();
}
m_pFlowTable->blockSignals(bSignalsWereBlocked);
updateSelectedFlowSegment();
}
int nmWellPressureFlowPage::findFlowSegmentByTableTime(
double dTimePoint) const
{
double dTotalTime = 0.0;
for (int nIndex = 0;
nIndex < m_vecTableFlowPoints.size();
++nIndex)
{
dTotalTime += qMax(0.0, m_vecTableFlowPoints[nIndex].x());
}
const double dTolerance = qMax(1.0, qAbs(dTotalTime)) * 1e-9;
if (dTimePoint < 0.0 ||
dTimePoint > dTotalTime + dTolerance)
{
return -1;
}
// 零时长点不占区间;分界点继续查找后一有效段。
double dStartTime = 0.0;
int nLastValidSegment = -1;
for (int nIndex = 0;
nIndex < m_vecTableFlowPoints.size();
++nIndex)
{
double dDuration = m_vecTableFlowPoints[nIndex].x();
if (dDuration <= dTolerance)
{
continue;
}
nLastValidSegment = nIndex;
double dEndTime = dStartTime + dDuration;
if (dTimePoint < dEndTime - dTolerance)
{
return nIndex;
}
dStartTime = dEndTime;
}
// 最终结束点没有后一段,归入最后一个有效流量段。
if (nLastValidSegment >= 0 &&
qAbs(dTimePoint - dTotalTime) <= dTolerance)
{
return nLastValidSegment;
}
return -1;
}
void nmWellPressureFlowPage::resetDataViews()
{
m_vecRawPressurePoints.clear();
m_vecRawFlowPoints.clear();
m_vecTableFlowPoints.clear();
m_nSelectedFlowSegment = -1;
// 井绑定切换或清空时先清空并禁用三个记录/相下拉框,等待 refreshData() 重新填充。
if (m_pPressureGaugeCombo != nullptr)
{
bool bSignalsWereBlocked = m_pPressureGaugeCombo->blockSignals(true);
m_pPressureGaugeCombo->clear();
m_pPressureGaugeCombo->setEnabled(false);
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
}
if (m_pFlowGaugeCombo != nullptr)
{
bool bSignalsWereBlocked = m_pFlowGaugeCombo->blockSignals(true);
m_pFlowGaugeCombo->clear();
m_pFlowGaugeCombo->setEnabled(false);
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
}
m_vecFlowGaugeRecords.clear();
if (m_pDisplayPhaseCombo != nullptr)
{
bool bSignalsWereBlocked = m_pDisplayPhaseCombo->blockSignals(true);
m_pDisplayPhaseCombo->clear();
m_pDisplayPhaseCombo->blockSignals(bSignalsWereBlocked);
}
if (m_pDisplayPhaseContainer != nullptr)
{
m_pDisplayPhaseContainer->setVisible(false);
}
m_eDisplayPhase = PHASE_UNKNOWN;
updateViews();
}
QString nmWellPressureFlowPage::buildDiagnosticText() const
{
if (m_pWell == nullptr)
{
return tr("No well is bound to this page.");
}
QStringList listLines;
// 井基础信息:全部即时从井对象重新读取,验证框架解析结果而非页面缓存。
listLines << tr("=== Well ===");
listLines << tr("Well name: %1")
.arg(diagTextOrNone(m_pWell->getWellName()));
listLines << tr("Well code: %1")
.arg(diagTextOrNone(m_pWell->getWellCode()));
listLines << tr("Well category: %1")
.arg(diagWellCategoryText(m_pWell->getWellCategory()));
listLines << tr("Reference flow phase: %1")
.arg(diagPhaseText(m_pWell->getReferenceFlowPhase()));
listLines << QString();
// 压力记录摘要:枚举全部记录,含损坏、空与身份无效记录。
QVector<nmPressureGaugeRecord> vecPressureRecords =
m_pWell->getPressureRecords();
listLines << tr("=== Pressure records ===");
listLines << tr("Total pressure records: %1")
.arg(vecPressureRecords.size());
listLines << tr("Selected pressure gauge code: %1")
.arg(diagTextOrNone(m_pWell->getSelectedPressureGaugeCode()));
for (int nIndex = 0; nIndex < vecPressureRecords.size(); ++nIndex)
{
const nmPressureGaugeRecord& oRecord = vecPressureRecords[nIndex];
listLines << QString();
listLines << tr("[%1] Name: %2")
.arg(nIndex + 1)
.arg(diagTextOrNone(oRecord.sGaugeName));
listLines << QLatin1String(" ") +
tr("Code: %1").arg(diagTextOrNone(oRecord.sGaugeCode));
listLines << QLatin1String(" ") +
tr("Time: %1").arg(diagTextOrNone(oRecord.sGaugeTime));
listLines << QLatin1String(" ") +
tr("Status: %1").arg(diagStatusText(oRecord.eStatus));
listLines << QLatin1String(" ") +
tr("Point count: %1").arg(oRecord.vecPressurePoints.size());
if (oRecord.vecPressurePoints.isEmpty())
{
listLines << QLatin1String(" ") + tr("No pressure points.");
}
else
{
listLines << QLatin1String(" ") +
tr("First point = %1, last point = %2")
.arg(diagPointText(oRecord.vecPressurePoints.first()))
.arg(diagPointText(oRecord.vecPressurePoints.last()));
}
}
listLines << QString();
// 流量记录摘要:油、气、水三相分别统计,多相记录额外检查相间时间一致性。
QVector<nmFlowGaugeRecord> vecFlowRecords = m_pWell->getFlowRecords();
listLines << tr("=== Flow records ===");
listLines << tr("Total flow records: %1").arg(vecFlowRecords.size());
listLines << tr("Selected flow gauge code: %1")
.arg(diagTextOrNone(m_pWell->getSelectedFlowGaugeCode()));
listLines << tr("Current display phase: %1")
.arg(diagPhaseText(m_eDisplayPhase));
for (int nIndex = 0; nIndex < vecFlowRecords.size(); ++nIndex)
{
const nmFlowGaugeRecord& oRecord = vecFlowRecords[nIndex];
listLines << QString();
listLines << tr("[%1] Name: %2")
.arg(nIndex + 1)
.arg(diagTextOrNone(oRecord.sGaugeName));
listLines << QLatin1String(" ") +
tr("Code: %1").arg(diagTextOrNone(oRecord.sGaugeCode));
listLines << QLatin1String(" ") +
tr("Time: %1").arg(diagTextOrNone(oRecord.sGaugeTime));
listLines << QLatin1String(" ") +
tr("Status: %1").arg(diagStatusText(oRecord.eStatus));
listLines << QLatin1String(" ") +
tr("Multiphase: %1")
.arg(oRecord.bMultiPhase ? tr("Yes") : tr("No"));
listLines << QLatin1String(" ") +
tr("Own flow segment index (1-based): %1")
.arg(oRecord.nIndexF);
appendPhaseDiagLines(listLines, tr("Oil"), oRecord.vecOilPoints);
appendPhaseDiagLines(listLines, tr("Gas"), oRecord.vecGasPoints);
appendPhaseDiagLines(listLines, tr("Water"), oRecord.vecWaterPoints);
if (oRecord.bMultiPhase)
{
listLines << QLatin1String(" ") +
tr("Phase time consistency: %1")
.arg(diagPhaseConsistencyText(oRecord));
}
}
// 数值记录显示 Corrupt 时继续向下追溯对应框架井的原始存储,尤其检查
// 另一口井自己的 GaugeDataEx2 是否确实为 N 行 x 4 列。
appendFrameworkFlowStorageDiagnostic(
listLines,
m_pWell->getWellCode(),
m_pWell->getSelectedFlowGaugeCode());
// 单独读取当前流动段分析快照,验证“多相数据”弹框保存的矩阵是否位于
// ZxSegmentInfo::m_vvecMpData。此处只展示不覆盖工程井记录或求解输入。
listLines << QString();
listLines << tr("=== Current flow-segment multiphase data ===");
nmDataAnalyzeContextProvider* pContextProvider =
nmDataAnalyzeContext::provider();
iSubWndFitting* pSubWndFitting =
nmDataAnalyzeManager::getCurrentFitting();
nmDataAnalyzeManager* pCurrentManager =
nmDataAnalyzeManager::getCurrentInstance();
const QString sCurrentPrimaryWellCode = pCurrentManager != nullptr
? pCurrentManager->getPrimaryWellCode() : QString();
const bool bSnapshotBelongsToEditedWell =
!sCurrentPrimaryWellCode.isEmpty() &&
sCurrentPrimaryWellCode == m_pWell->getWellCode();
listLines << tr("Current fitting primary well code: %1")
.arg(diagTextOrNone(sCurrentPrimaryWellCode));
listLines << tr("Snapshot belongs to edited well: %1")
.arg(bSnapshotBelongsToEditedWell ? tr("Yes") : tr("No"));
if (!bSnapshotBelongsToEditedWell)
{
listLines << tr("This snapshot belongs to the current fitting primary well and does not override the edited other well.");
}
bool bSegmentMultiPhase = false;
QString sFlowCurveName;
VVecDouble vvecMultiPhaseData;
const bool bSnapshotAvailable = pContextProvider != nullptr &&
pSubWndFitting != nullptr &&
pContextProvider->getCurrentSegmentMultiPhaseData(
pSubWndFitting,
bSegmentMultiPhase,
sFlowCurveName,
vvecMultiPhaseData);
listLines << tr("Snapshot available: %1")
.arg(bSnapshotAvailable ? tr("Yes") : tr("No"));
if (bSnapshotAvailable)
{
PvtFluidType eFluidType = WFT_Null;
const bool bFluidTypeAvailable =
pContextProvider->getBasicPft(pSubWndFitting, eFluidType);
listLines << tr("PVT phase type: %1")
.arg(bFluidTypeAvailable
? diagFluidTypeText(eFluidType) : tr("Unknown"));
listLines << tr("Segment multiphase flag: %1")
.arg(bSegmentMultiPhase ? tr("Yes") : tr("No"));
listLines << tr("Flow curve name: %1")
.arg(diagTextOrNone(sFlowCurveName));
listLines << tr("Matrix vector count: %1")
.arg(vvecMultiPhaseData.size());
QStringList listVectorSizes;
for (int nVectorIndex = 0;
nVectorIndex < vvecMultiPhaseData.size();
++nVectorIndex)
{
listVectorSizes << QString("[%1] = %2")
.arg(nVectorIndex)
.arg(vvecMultiPhaseData[nVectorIndex].size());
}
listLines << tr("Vector sizes: %1")
.arg(listVectorSizes.isEmpty()
? tr("None")
: listVectorSizes.join(QLatin1String(", ")));
bool bMatrixShapeValid = vvecMultiPhaseData.size() == 4;
int nPointCount = bMatrixShapeValid
? vvecMultiPhaseData[0].size() : 0;
for (int nVectorIndex = 1;
bMatrixShapeValid && nVectorIndex < 4;
++nVectorIndex)
{
if (vvecMultiPhaseData[nVectorIndex].size() != nPointCount)
{
bMatrixShapeValid = false;
}
}
listLines << tr("Four-vector shape valid: %1")
.arg(bMatrixShapeValid ? tr("Yes") : tr("No"));
if (bMatrixShapeValid)
{
listLines << tr("Data rows (time, oil, gas, water):");
const int nMaxListedRows = 64;
const int nListedRows = qMin(nPointCount, nMaxListedRows);
for (int nPointIndex = 0;
nPointIndex < nListedRows;
++nPointIndex)
{
listLines << QString(" [%1] t=%2, oil=%3, gas=%4, water=%5")
.arg(nPointIndex + 1)
.arg(diagNumberText(
vvecMultiPhaseData[0][nPointIndex]))
.arg(diagNumberText(
vvecMultiPhaseData[1][nPointIndex]))
.arg(diagNumberText(
vvecMultiPhaseData[2][nPointIndex]))
.arg(diagNumberText(
vvecMultiPhaseData[3][nPointIndex]));
}
if (nPointCount > nListedRows)
{
listLines << tr(" ... %1 additional rows omitted")
.arg(nPointCount - nListedRows);
}
}
}
return listLines.join(QLatin1String("\n"));
}