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;