|
|
#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;
|
|
|
}
|
|
|
|
|
|
QString gaugeMetadataText(const QString& sText)
|
|
|
{
|
|
|
return sText.trimmed().isEmpty()
|
|
|
? nmWellPressureFlowPage::tr("(none)") : sText;
|
|
|
}
|
|
|
|
|
|
QString gaugeNameText(const QString& sGaugeName)
|
|
|
{
|
|
|
return sGaugeName.trimmed().isEmpty()
|
|
|
? nmWellPressureFlowPage::tr("(unnamed)") : sGaugeName;
|
|
|
}
|
|
|
|
|
|
QString gaugeRecordToolTip(const QString& sGaugeName,
|
|
|
const QString& sGaugeCode,
|
|
|
const QString& sGaugeTime)
|
|
|
{
|
|
|
QStringList listLines;
|
|
|
listLines << nmWellPressureFlowPage::tr("Curve name: %1")
|
|
|
.arg(gaugeNameText(sGaugeName));
|
|
|
listLines << nmWellPressureFlowPage::tr("Gauge Code: %1")
|
|
|
.arg(gaugeMetadataText(sGaugeCode));
|
|
|
listLines << nmWellPressureFlowPage::tr("Measurement time: %1")
|
|
|
.arg(gaugeMetadataText(sGaugeTime));
|
|
|
return listLines.join(QLatin1String("\n"));
|
|
|
}
|
|
|
|
|
|
void disableGaugeComboItem(QComboBox* pCombo, int nItemIndex)
|
|
|
{
|
|
|
QStandardItemModel* pModel =
|
|
|
qobject_cast<QStandardItemModel*>(pCombo->model());
|
|
|
if (pModel == nullptr)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
QStandardItem* pItem = pModel->item(nItemIndex);
|
|
|
if (pItem != nullptr)
|
|
|
{
|
|
|
pItem->setFlags(pItem->flags() & ~Qt::ItemIsEnabled);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void updatePreviewComboToolTip(QComboBox* pCombo)
|
|
|
{
|
|
|
if (pCombo == nullptr || pCombo->currentIndex() < 0 ||
|
|
|
pCombo->itemData(pCombo->currentIndex()).toString().isEmpty())
|
|
|
{
|
|
|
if (pCombo != nullptr)
|
|
|
{
|
|
|
pCombo->setToolTip(QString());
|
|
|
}
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
QString sToolTip = pCombo->itemData(
|
|
|
pCombo->currentIndex(), Qt::ToolTipRole).toString();
|
|
|
if (!sToolTip.isEmpty())
|
|
|
{
|
|
|
sToolTip += QLatin1Char('\n');
|
|
|
}
|
|
|
sToolTip += nmWellPressureFlowPage::tr(
|
|
|
"Preview only; calculation settings are unchanged.");
|
|
|
pCombo->setToolTip(sToolTip);
|
|
|
}
|
|
|
|
|
|
// 统一向压力/流量记录下拉框追加一项;异常记录保留状态提示但不可选。
|
|
|
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);
|
|
|
const int nItemIndex = pCombo->count() - 1;
|
|
|
pCombo->setItemData(nItemIndex,
|
|
|
gaugeRecordToolTip(sGaugeName, sGaugeCode, sGaugeTime),
|
|
|
Qt::ToolTipRole);
|
|
|
if (eStatus != NM_GaugeRecord_Usable)
|
|
|
{
|
|
|
disableGaugeComboItem(pCombo, nItemIndex);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 诊断文本统一使用约 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;
|
|
|
m_sPreviewPressureGaugeCode = pWell == nullptr
|
|
|
? QString() : pWell->getSelectedPressureGaugeCode();
|
|
|
m_sPreviewFlowGaugeCode = pWell == nullptr
|
|
|
? QString() : pWell->getSelectedFlowGaugeCode();
|
|
|
resetDataViews();
|
|
|
}
|
|
|
|
|
|
void nmWellPressureFlowPage::refreshData()
|
|
|
{
|
|
|
if (m_pWell == nullptr)
|
|
|
{
|
|
|
resetDataViews();
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
// 第一步:恢复页面自己的预览记录,不修改井对象保存的计算记录选择。
|
|
|
populatePressureGaugeCombo();
|
|
|
populateFlowGaugeCombo();
|
|
|
populateDisplayPhaseCombo(currentFlowRecord());
|
|
|
|
|
|
// 第二步:直接从预览记录复制数据,后续记录、相别和单位切换都不回写井对象。
|
|
|
const nmPressureGaugeRecord* pPressureRecord = currentPressureRecord();
|
|
|
const nmFlowGaugeRecord* pFlowRecord = currentFlowRecord();
|
|
|
QVector<QPointF> vecRawPressurePoints = pPressureRecord == nullptr
|
|
|
? QVector<QPointF>() : pPressureRecord->vecPressurePoints;
|
|
|
QVector<QPointF> vecRawFlowPoints;
|
|
|
if (pFlowRecord != nullptr)
|
|
|
{
|
|
|
switch (m_eDisplayPhase)
|
|
|
{
|
|
|
case PHASE_Oil:
|
|
|
vecRawFlowPoints = pFlowRecord->vecOilPoints;
|
|
|
break;
|
|
|
case PHASE_Gas:
|
|
|
vecRawFlowPoints = pFlowRecord->vecGasPoints;
|
|
|
break;
|
|
|
case PHASE_Water:
|
|
|
vecRawFlowPoints = pFlowRecord->vecWaterPoints;
|
|
|
break;
|
|
|
default:
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 第三步:单位服务可能晚于页面构造完成,再刷新一次可用单位组。
|
|
|
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;
|
|
|
m_sPreviewPressureGaugeCode.clear();
|
|
|
m_sPreviewFlowGaugeCode.clear();
|
|
|
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::slotPressureGaugePreviewChanged(int nIndex)
|
|
|
{
|
|
|
if (m_pWell == nullptr || nIndex < 0)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const QString sGaugeCode =
|
|
|
m_pPressureGaugeCombo->itemData(nIndex).toString();
|
|
|
if (sGaugeCode.isEmpty() ||
|
|
|
sGaugeCode == m_sPreviewPressureGaugeCode)
|
|
|
{
|
|
|
updatePreviewComboToolTip(m_pPressureGaugeCombo);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
m_sPreviewPressureGaugeCode = sGaugeCode;
|
|
|
refreshData();
|
|
|
}
|
|
|
|
|
|
void nmWellPressureFlowPage::slotFlowGaugePreviewChanged(int nIndex)
|
|
|
{
|
|
|
if (m_pWell == nullptr || nIndex < 0)
|
|
|
{
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
const QString sGaugeCode =
|
|
|
m_pFlowGaugeCombo->itemData(nIndex).toString();
|
|
|
if (sGaugeCode.isEmpty() || sGaugeCode == m_sPreviewFlowGaugeCode)
|
|
|
{
|
|
|
updatePreviewComboToolTip(m_pFlowGaugeCombo);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
m_sPreviewFlowGaugeCode = sGaugeCode;
|
|
|
m_eDisplayPhase = defaultDisplayPhase(currentFlowRecord());
|
|
|
m_nSelectedFlowSegment = -1;
|
|
|
refreshData();
|
|
|
}
|
|
|
|
|
|
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->setMaxVisibleItems(16);
|
|
|
|
|
|
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->setMaxVisibleItems(16);
|
|
|
|
|
|
// 显示相是纯展示状态,整体容器随记录是否为多相含数据而隐藏/显示。
|
|
|
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_pPressureGaugeCombo,
|
|
|
SIGNAL(currentIndexChanged(int)),
|
|
|
this,
|
|
|
SLOT(slotPressureGaugePreviewChanged(int)));
|
|
|
connect(m_pFlowGaugeCombo,
|
|
|
SIGNAL(currentIndexChanged(int)),
|
|
|
this,
|
|
|
SLOT(slotFlowGaugePreviewChanged(int)));
|
|
|
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;
|
|
|
}
|
|
|
|
|
|
m_vecPressureGaugeRecords = m_pWell->getPressureRecords();
|
|
|
|
|
|
int nPreviewIndex = -1;
|
|
|
int nCalculationIndex = -1;
|
|
|
int nFirstUsableIndex = -1;
|
|
|
const QString sCalculationCode =
|
|
|
m_pWell->getSelectedPressureGaugeCode();
|
|
|
for (int nIndex = 0; nIndex < m_vecPressureGaugeRecords.size(); ++nIndex)
|
|
|
{
|
|
|
const nmPressureGaugeRecord& oRecord =
|
|
|
m_vecPressureGaugeRecords[nIndex];
|
|
|
appendGaugeComboItem(m_pPressureGaugeCombo,
|
|
|
oRecord.sGaugeName, oRecord.sGaugeCode,
|
|
|
oRecord.sGaugeTime, oRecord.eStatus);
|
|
|
const int nItemIndex = m_pPressureGaugeCombo->count() - 1;
|
|
|
if (oRecord.eStatus != NM_GaugeRecord_Usable)
|
|
|
{
|
|
|
continue;
|
|
|
}
|
|
|
if (nFirstUsableIndex < 0)
|
|
|
{
|
|
|
nFirstUsableIndex = nItemIndex;
|
|
|
}
|
|
|
if (oRecord.sGaugeCode == m_sPreviewPressureGaugeCode)
|
|
|
{
|
|
|
nPreviewIndex = nItemIndex;
|
|
|
}
|
|
|
if (oRecord.sGaugeCode == sCalculationCode)
|
|
|
{
|
|
|
nCalculationIndex = nItemIndex;
|
|
|
}
|
|
|
}
|
|
|
const int nCurrentIndex = nPreviewIndex >= 0
|
|
|
? nPreviewIndex : (nCalculationIndex >= 0
|
|
|
? nCalculationIndex : (nFirstUsableIndex >= 0
|
|
|
? nFirstUsableIndex : -1));
|
|
|
m_pPressureGaugeCombo->setCurrentIndex(nCurrentIndex);
|
|
|
m_pPressureGaugeCombo->setEnabled(nFirstUsableIndex >= 0);
|
|
|
m_sPreviewPressureGaugeCode = nCurrentIndex >= 0
|
|
|
? m_pPressureGaugeCombo->itemData(nCurrentIndex).toString()
|
|
|
: QString();
|
|
|
updatePreviewComboToolTip(m_pPressureGaugeCombo);
|
|
|
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();
|
|
|
|
|
|
int nPreviewIndex = -1;
|
|
|
int nCalculationIndex = -1;
|
|
|
int nFirstUsableIndex = -1;
|
|
|
const QString sCalculationCode = m_pWell->getSelectedFlowGaugeCode();
|
|
|
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);
|
|
|
const int nItemIndex = m_pFlowGaugeCombo->count() - 1;
|
|
|
if (oRecord.eStatus != NM_GaugeRecord_Usable)
|
|
|
{
|
|
|
continue;
|
|
|
}
|
|
|
if (nFirstUsableIndex < 0)
|
|
|
{
|
|
|
nFirstUsableIndex = nItemIndex;
|
|
|
}
|
|
|
if (oRecord.sGaugeCode == m_sPreviewFlowGaugeCode)
|
|
|
{
|
|
|
nPreviewIndex = nItemIndex;
|
|
|
}
|
|
|
if (oRecord.sGaugeCode == sCalculationCode)
|
|
|
{
|
|
|
nCalculationIndex = nItemIndex;
|
|
|
}
|
|
|
}
|
|
|
const int nCurrentIndex = nPreviewIndex >= 0
|
|
|
? nPreviewIndex : (nCalculationIndex >= 0
|
|
|
? nCalculationIndex : (nFirstUsableIndex >= 0
|
|
|
? nFirstUsableIndex : -1));
|
|
|
m_pFlowGaugeCombo->setCurrentIndex(nCurrentIndex);
|
|
|
m_pFlowGaugeCombo->setEnabled(nFirstUsableIndex >= 0);
|
|
|
m_sPreviewFlowGaugeCode = nCurrentIndex >= 0
|
|
|
? m_pFlowGaugeCombo->itemData(nCurrentIndex).toString()
|
|
|
: QString();
|
|
|
updatePreviewComboToolTip(m_pFlowGaugeCombo);
|
|
|
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 nmPressureGaugeRecord* nmWellPressureFlowPage::currentPressureRecord() const
|
|
|
{
|
|
|
for (int nIndex = 0; nIndex < m_vecPressureGaugeRecords.size(); ++nIndex)
|
|
|
{
|
|
|
if (m_vecPressureGaugeRecords[nIndex].sGaugeCode ==
|
|
|
m_sPreviewPressureGaugeCode)
|
|
|
{
|
|
|
return &m_vecPressureGaugeRecords[nIndex];
|
|
|
}
|
|
|
}
|
|
|
return nullptr;
|
|
|
}
|
|
|
|
|
|
const nmFlowGaugeRecord* nmWellPressureFlowPage::currentFlowRecord() const
|
|
|
{
|
|
|
for (int nIndex = 0; nIndex < m_vecFlowGaugeRecords.size(); ++nIndex)
|
|
|
{
|
|
|
if (m_vecFlowGaugeRecords[nIndex].sGaugeCode ==
|
|
|
m_sPreviewFlowGaugeCode)
|
|
|
{
|
|
|
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默认的1~N序号,内部仍使用零基流量点索引。
|
|
|
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;
|
|
|
if (m_pWell == nullptr)
|
|
|
{
|
|
|
m_sPreviewPressureGaugeCode.clear();
|
|
|
m_sPreviewFlowGaugeCode.clear();
|
|
|
}
|
|
|
|
|
|
// 井绑定切换或清空时先清空并禁用三个记录/相下拉框,等待 refreshData() 重新填充。
|
|
|
if (m_pPressureGaugeCombo != nullptr)
|
|
|
{
|
|
|
bool bSignalsWereBlocked = m_pPressureGaugeCombo->blockSignals(true);
|
|
|
m_pPressureGaugeCombo->clear();
|
|
|
m_pPressureGaugeCombo->setEnabled(false);
|
|
|
m_pPressureGaugeCombo->setToolTip(QString());
|
|
|
m_pPressureGaugeCombo->blockSignals(bSignalsWereBlocked);
|
|
|
}
|
|
|
if (m_pFlowGaugeCombo != nullptr)
|
|
|
{
|
|
|
bool bSignalsWereBlocked = m_pFlowGaugeCombo->blockSignals(true);
|
|
|
m_pFlowGaugeCombo->clear();
|
|
|
m_pFlowGaugeCombo->setEnabled(false);
|
|
|
m_pFlowGaugeCombo->setToolTip(QString());
|
|
|
m_pFlowGaugeCombo->blockSignals(bSignalsWereBlocked);
|
|
|
}
|
|
|
m_vecPressureGaugeRecords.clear();
|
|
|
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"));
|
|
|
}
|