From 17bf55c40d4906f07b3fca87ec17894f3e4e72de Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Fri, 21 Aug 2015 19:58:27 +0100 Subject: [PATCH] Formula estimate function .. estimate(model, duration) or to get the model parameters estimate(model, parameter) always for the date of the ride. e.g. estimate(2p, cp) gets the estimated CP for the date of the ride using the classic 2 parameter model Models are one of; 2p - classic monod scherrer 3p - morton 3p ext - gc extended ws - ward-smith velo - veloclinic Parameters are one of; w' - W' cp - critical power ftp - functional threshold power pmax - max power For watts per kilo just add / config(weight) or Athlete_Weight to take into account ride specific weight settings / overrides. --- src/Athlete.cpp | 12 ++++ src/Athlete.h | 3 + src/DataFilter.cpp | 157 +++++++++++++++++++++++++++++++++++++++++++-- src/DataFilter.h | 4 ++ src/DataFilter.y | 1 + src/LTMTool.cpp | 15 ++++- src/LTMTool.h | 1 + 7 files changed, 184 insertions(+), 9 deletions(-) diff --git a/src/Athlete.cpp b/src/Athlete.cpp index b6b357a5b..28b18c22c 100644 --- a/src/Athlete.cpp +++ b/src/Athlete.cpp @@ -560,3 +560,15 @@ Athlete::getPMCFor(Leaf *expr, DataFilter *df, int stsdays, int ltsdays) return returning; } + +PDEstimate +Athlete::getPDEstimateFor(QDate date, QString model, bool wpk) +{ + // whats the estimate for this date + foreach(PDEstimate est, PDEstimates) { + if (est.model == model && est.wpk == wpk && est.from <= date && est.to >= date) + return est; + } + return PDEstimate(); +} + diff --git a/src/Athlete.h b/src/Athlete.h index 733494c6d..6590386a4 100644 --- a/src/Athlete.h +++ b/src/Athlete.h @@ -97,6 +97,9 @@ class Athlete : public QObject RideCache *rideCache; QList withings_; + // Estimates + PDEstimate getPDEstimateFor(QDate, QString model, bool wpk); + // PMC Data PMCData *getPMCFor(QString metricName, int stsDays = -1, int ltsDays = -1); // no Specification used! PMCData *getPMCFor(Leaf *expr, DataFilter *df, int stsDays = -1, int ltsDays = -1); // no Specification used! diff --git a/src/DataFilter.cpp b/src/DataFilter.cpp index d87e1b957..cccc69c5b 100644 --- a/src/DataFilter.cpp +++ b/src/DataFilter.cpp @@ -84,23 +84,49 @@ static struct { { "sb", 1 }, { "rr", 1 }, + // estimate + { "estimate", 2 }, // estimate(model, (cp|ftp|w'|pmax|x)) + // add new ones above this line { "", -1 } }; +static QStringList pdmodels() +{ + QStringList returning; + + returning << "2p"; + returning << "3p"; + returning << "ext"; + returning << "ws"; + returning << "velo"; + + return returning; +} + QStringList DataFilter::functions() { QStringList returning; for(int i=0; DataFilterFunctions[i].parameters != -1; i++) { - QString function = DataFilterFunctions[i].name + "("; - for(int j=0; jfparms) validateFilter(df, p); + if (leaf->function == "estimate") { + + // we only want two parameters and they must be + // a model name and then either ftp, cp, pmax, w' + // or a duration + if (leaf->fparms.count() > 0) { + // check the model name + if (leaf->fparms[0]->type != Leaf::Symbol) { + + leaf->fparms[0]->inerror = true; + DataFiltererrors << QString(QObject::tr("estimate function expects model name as first parameter")); + + } else { + + if (!pdmodels().contains(*(leaf->fparms[0]->lvalue.n))) { + leaf->inerror = leaf->fparms[0]->inerror = true; + DataFiltererrors << QString(QObject::tr("estimate function expects model name as first parameter")); + } + } + + if (leaf->fparms.count() > 1) { + + // check symbol name if it is a symbol + if (leaf->fparms[1]->type == Leaf::Symbol) { + QRegExp estimateValidSymbols("^(cp|ftp|pmax|w')$", Qt::CaseInsensitive); + if (!estimateValidSymbols.exactMatch(*(leaf->fparms[1]->lvalue.n))) { + leaf->inerror = leaf->fparms[1]->inerror = true; + DataFiltererrors << QString(QObject::tr("estimate function expects parameter or duration as second parameter")); + } + } else { + validateFilter(df, leaf->fparms[1]); + } + } + } + + } else { + + // normal parm check ! + foreach(Leaf *p, leaf->fparms) validateFilter(df, p); + } // does it exist? for(int i=0; DataFilterFunctions[i].parameters != -1; i++) { @@ -988,6 +1056,13 @@ void Leaf::validateFilter(DataFilter *df, Leaf *leaf) DataFilter::DataFilter(QObject *parent, Context *context) : QObject(parent), context(context), isdynamic(false), treeRoot(NULL) { + // set up the models we support + models << new CP2Model(context); + models << new CP3Model(context); + models << new MultiModel(context); + models << new ExtendedModel(context); + models << new WSModel(context); + configChanged(CONFIG_FIELDS); connect(context, SIGNAL(configChanged(qint32)), this, SLOT(configChanged(qint32))); connect(context, SIGNAL(rideSelected(RideItem*)), this, SLOT(dynamicParse())); @@ -995,6 +1070,13 @@ DataFilter::DataFilter(QObject *parent, Context *context) : QObject(parent), con DataFilter::DataFilter(QObject *parent, Context *context, QString formula) : QObject(parent), context(context), isdynamic(false), treeRoot(NULL) { + // set up the models we support + models << new CP2Model(context); + models << new CP3Model(context); + models << new MultiModel(context); + models << new ExtendedModel(context); + models << new WSModel(context); + configChanged(CONFIG_FIELDS); // regardless of success or failure set signature @@ -1189,6 +1271,9 @@ void DataFilter::configChanged(qint32) Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m) { + // if error state all bets are off + if (inerror) return Result(0); + switch(leaf->type) { // @@ -1528,6 +1613,64 @@ Result Leaf::eval(Context *context, DataFilter *df, Leaf *leaf, RideItem *m) } break; + case 30 : + { /* ESTIMATE( model, CP | FTP | W' | PMAX | duration ) */ + + // which model ? + QString model = *leaf->fparms[0]->lvalue.n; + if (model == "2p") model = "2 Parm"; + if (model == "3p") model = "3 Parm"; + if (model == "ws") model = "WS"; + if (model == "velo") model = "Velo"; + if (model == "ext") model = "Ext"; + + // what we looking for ? + QString parm = leaf->fparms[1]->type == Leaf::Symbol ? *leaf->fparms[1]->lvalue.n : ""; + bool toDuration = parm == "" ? true : false; + double duration = toDuration ? eval(context, df, leaf->fparms[1], m).number : 0; + + // get the PD Estimate for this date - note we always work with the absolulte + // power estimates in formulas, since the user can just divide by config(weight) + // or Athlete_Weight (which takes into account values stored in ride files. + PDEstimate pde = context->athlete->getPDEstimateFor(m->dateTime.date(), model, false); + + // no model estimate for this date + if (pde.parameters.count() == 0) return Result(0); + + // get a duration + if (toDuration == true) { + + double value = 0; + + // we need to find the model + foreach(PDModel *pdm, df->models) { + + // not the one we want + if (pdm->code() != model) continue; + + // set the parameters previously derived + pdm->loadParameters(pde.parameters); + + // use seconds + pdm->setMinutes(false); + + // get the model estimate for our duration + value = pdm->y(duration); + + // our work here is done + return Result(value); + } + + } else { + if (parm == "cp") return Result(pde.CP); + if (parm == "w'") return Result(pde.WPrime); + if (parm == "ftp") return Result(pde.FTP); + if (parm == "pmax") return Result(pde.PMax); + } + return Result(0); + } + break; + default: return Result(0); } diff --git a/src/DataFilter.h b/src/DataFilter.h index 3a4024a66..bb587bd69 100644 --- a/src/DataFilter.h +++ b/src/DataFilter.h @@ -118,6 +118,9 @@ class DataFilter : public QObject static QStringList functions(); // return list of functions supported + // pd models for estimates + QList models; + public slots: QStringList parseFilter(QString query, QStringList *list=0); QStringList check(QString query); @@ -133,6 +136,7 @@ class DataFilter : public QObject void results(QStringList); + private: void setSignature(QString &query); diff --git a/src/DataFilter.y b/src/DataFilter.y index 9066260f3..d8d79b0f6 100644 --- a/src/DataFilter.y +++ b/src/DataFilter.y @@ -258,6 +258,7 @@ expr : expr SUBTRACT expr { $$ = new Leaf(@1.first_column, @3.last_ /* functions all have zero or more parameters */ | symbol '(' parms ')' { /* need to convert symbol to a function */ + $1->leng = @4.last_column; $1->type = Leaf::Function; $1->series = NULL; // not tiz/best $1->function = *($1->lvalue.n); diff --git a/src/LTMTool.cpp b/src/LTMTool.cpp index f7b833208..e34c3fb22 100644 --- a/src/LTMTool.cpp +++ b/src/LTMTool.cpp @@ -1730,8 +1730,7 @@ EditMetricDetailDialog::EditMetricDetailDialog(Context *context, LTMTool *ltmToo metricDetail->formula = tr("# type in a formula to use\n" "# for e.g. TSS / Duration\n" "# as you type the available metrics\n" - "# will be offered by autocomplete\n" - "# all lines beginning with # are comments.\n"); + "# will be offered by autocomplete\n"); } formulaEdit->setText(metricDetail->formula); formulaType->setCurrentIndex(formulaType->findData(metricDetail->formulaType)); @@ -2399,6 +2398,18 @@ DataFilterEdit::checkErrors() // XXX next commit } +bool +DataFilterEdit::event(QEvent *e) +{ + // intercept all events + if (e->type() == QEvent::ToolTip) { + // XXX error reporting when mouse over error + } + + // call standard event handler + return QTextEdit::event(e); +} + void DataFilterEdit::setCompleter(QCompleter *completer) { if (c) diff --git a/src/LTMTool.h b/src/LTMTool.h index bef3451a9..7e86e682f 100644 --- a/src/LTMTool.h +++ b/src/LTMTool.h @@ -251,6 +251,7 @@ public slots: void setText(const QString&); void insertCompletion(const QString &completion); void checkErrors(); + bool event(QEvent *); private: QString textUnderCursor() const;