mirror of
https://github.com/GoldenCheetah/GoldenCheetah.git
synced 2026-02-13 08:08:42 +00:00
Updating power values of planned activities with linked workouts (#4799)
* Updating power values of workout based planned activities when they fall into a timerange with different CP * Triggers: * CP configuration changes (future activities only) * planned activities are moved (calendar) * schedules are repeated (calendar) * rest days are inserted or removed (calendar) * Additional: Typo in Calendar (show in train _n_ode -> mode)
This commit is contained in:
committed by
GitHub
parent
1c69798463
commit
3e8c6fe047
@@ -31,6 +31,8 @@
|
||||
#include "HrZones.h"
|
||||
#include "PaceZones.h"
|
||||
|
||||
#include "ErgFile.h"
|
||||
|
||||
#include "JsonRideFile.h" // for DATETIME_FORMAT
|
||||
|
||||
#ifdef SLOW_REFRESH
|
||||
@@ -1225,6 +1227,10 @@ RideCache::moveActivity
|
||||
result.affectedCount = 1;
|
||||
}
|
||||
|
||||
if (item->planned) {
|
||||
updateFromWorkout(item, false);
|
||||
}
|
||||
|
||||
item->refresh();
|
||||
context->notifyRideChanged(item);
|
||||
if (context->ride == item) {
|
||||
@@ -1540,6 +1546,7 @@ RideCache::shiftPlannedActivities
|
||||
continue;
|
||||
}
|
||||
item->setFileName(plannedDirectory.canonicalPath(), newFileName);
|
||||
updateFromWorkout(item, true);
|
||||
item->isstale = true;
|
||||
|
||||
RideItem *linkedItem = getLinkedActivity(item);
|
||||
@@ -1683,6 +1690,99 @@ RideCache::findSuggestion
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
RideCache::updateFromWorkout
|
||||
(RideItem *item, bool autoSave)
|
||||
{
|
||||
if (item == nullptr || ! item->planned) {
|
||||
return false;
|
||||
}
|
||||
QString workoutFilename = item->getText("WorkoutFilename", item->ride()->getTag("WorkoutFilename", "")).trimmed();
|
||||
if (workoutFilename.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
ErgFile ergFile(workoutFilename, ErgFileFormat::unknown, context, item->dateTime.date());
|
||||
if (! ergFile.hasRelativeWatts()) {
|
||||
return false;
|
||||
}
|
||||
bool changed = false;
|
||||
for (const QString &name : item->overrides_) {
|
||||
int value = static_cast<int>(item->getForSymbol(name));
|
||||
// Operate only on the values overridden by ManualActivityWizard
|
||||
if (name == "average_power") {
|
||||
if (value != std::round(ergFile.AP())) {
|
||||
QMap<QString, QString> values;
|
||||
values.insert("value", QString::number(std::round(ergFile.AP())));
|
||||
item->ride()->metricOverrides.insert(name, values);
|
||||
changed = true;
|
||||
}
|
||||
} else if (name == "coggan_np") {
|
||||
if (value != std::round(ergFile.IsoPower())) {
|
||||
QMap<QString, QString> values;
|
||||
values.insert("value", QString::number(std::round(ergFile.IsoPower())));
|
||||
item->ride()->metricOverrides.insert(name, values);
|
||||
changed = true;
|
||||
}
|
||||
} else if (name == "coggan_tss") {
|
||||
if (value != std::round(ergFile.bikeStress())) {
|
||||
QMap<QString, QString> values;
|
||||
values.insert("value", QString::number(std::round(ergFile.bikeStress())));
|
||||
item->ride()->metricOverrides.insert(name, values);
|
||||
changed = true;
|
||||
}
|
||||
} else if (name == "skiba_bike_score") {
|
||||
if (value != std::round(ergFile.BS())) {
|
||||
QMap<QString, QString> values;
|
||||
values.insert("value", QString::number(std::round(ergFile.BS())));
|
||||
item->ride()->metricOverrides.insert(name, values);
|
||||
changed = true;
|
||||
}
|
||||
} else if (name == "skiba_xpower") {
|
||||
if (value != std::round(ergFile.XP())) {
|
||||
QMap<QString, QString> values;
|
||||
values.insert("value", QString::number(std::round(ergFile.XP())));
|
||||
item->ride()->metricOverrides.insert(name, values);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changed) {
|
||||
item->setDirty(true);
|
||||
item->isstale = true;
|
||||
if (autoSave) {
|
||||
QString error;
|
||||
saveActivity(item, error);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
RideCache::updateFromWorkoutAfter
|
||||
(const QDate &when, bool autoSave)
|
||||
{
|
||||
QList<RideItem*> changedItems;
|
||||
for (RideItem *item : context->athlete->rideCache->rides()) {
|
||||
if (item->planned && item->dateTime.date() >= when) {
|
||||
if (context->athlete->rideCache->updateFromWorkout(item, false)) {
|
||||
changedItems << item;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (changedItems.count() > 0) {
|
||||
if (autoSave) {
|
||||
QString error;
|
||||
saveActivities(changedItems, error);
|
||||
}
|
||||
cancel();
|
||||
refresh();
|
||||
estimator->refresh();
|
||||
}
|
||||
return changedItems.count() > 0;
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
RideCache::isValidLink
|
||||
(RideItem *item1, RideItem *item2, QString &error)
|
||||
@@ -1747,6 +1847,7 @@ RideCache::copyPlannedRideFile
|
||||
delete newRide;
|
||||
|
||||
RideItem *newItem = new RideItem(plannedDirectory.canonicalPath(), newFileName, newDateTime, context, true);
|
||||
updateFromWorkout(newItem, true);
|
||||
newItem->isstale = true;
|
||||
|
||||
return newItem;
|
||||
|
||||
@@ -150,6 +150,9 @@ class RideCache : public QObject
|
||||
RideItem *getLinkedActivity(RideItem *item);
|
||||
RideItem *findSuggestion(RideItem *rideItem);
|
||||
|
||||
bool updateFromWorkout(RideItem *item, bool autoSave = false);
|
||||
bool updateFromWorkoutAfter(const QDate &when, bool autoSave = false);
|
||||
|
||||
public slots:
|
||||
|
||||
// restore / dump cache to disk (json)
|
||||
|
||||
@@ -205,7 +205,7 @@ CalendarBaseTable::buildContextMenu
|
||||
}
|
||||
if (entry.hasTrainMode) {
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(tr("Show in train node..."), this, [this, entry]() { emit showInTrainMode(entry); });
|
||||
contextMenu->addAction(tr("Show in train mode..."), this, [this, entry]() { emit showInTrainMode(entry); });
|
||||
}
|
||||
contextMenu->addSeparator();
|
||||
contextMenu->addAction(tr("Delete planned activity"), this, [this, entry]() { emit delActivity(entry); });
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "Colors.h"
|
||||
#include "Units.h"
|
||||
#include "HelpWhatsThis.h"
|
||||
#include "ErgFile.h"
|
||||
|
||||
#include "GcWindowRegistry.h" // for GcWinID types
|
||||
#include "Perspective.h" // for GcWindowDialog
|
||||
@@ -329,7 +330,7 @@ LTMSidebar::presetSelectionChanged()
|
||||
}
|
||||
|
||||
void
|
||||
LTMSidebar::configChanged(qint32)
|
||||
LTMSidebar::configChanged(qint32 why)
|
||||
{
|
||||
seasonsWidget->setStyleSheet(GCColor::stylesheet());
|
||||
eventsWidget->setStyleSheet(GCColor::stylesheet());
|
||||
@@ -343,6 +344,9 @@ LTMSidebar::configChanged(qint32)
|
||||
// let everyone know what date range we are starting with
|
||||
dateRangeTreeWidgetSelectionChanged();
|
||||
|
||||
if (why & CONFIG_ZONES) {
|
||||
context->athlete->rideCache->updateFromWorkoutAfter(QDate::currentDate(), true);
|
||||
}
|
||||
}
|
||||
|
||||
/*----------------------------------------------------------------------
|
||||
|
||||
@@ -676,12 +676,33 @@ ManualActivityPageWorkout::selectionChanged
|
||||
QString title = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::displayname), Qt::DisplayRole).toString();
|
||||
QString type = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::type), Qt::DisplayRole).toString();
|
||||
QString description = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::description), Qt::DisplayRole).toString();
|
||||
int avgPower = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::avgPower), Qt::DisplayRole).toInt();
|
||||
int bikeStress = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::bikestress), Qt::DisplayRole).toInt();
|
||||
int bikeScore = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::bs), Qt::DisplayRole).toInt();
|
||||
if (ergFile != nullptr) {
|
||||
delete ergFile;
|
||||
ergFile = nullptr;
|
||||
}
|
||||
if (type != "code") {
|
||||
QDate when = field("activityDate").toDate();
|
||||
ergFile = new ErgFile(filename, ErgFileFormat::unknown, context, when);
|
||||
if (! ergFile->isValid()) {
|
||||
delete ergFile;
|
||||
ergFile = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int avgPower = 0;
|
||||
int bikeStress = 0;
|
||||
int bikeScore = 0;
|
||||
int isoPower = 0;
|
||||
int xPower = 0;
|
||||
if (ergFile != nullptr && type == "erg") {
|
||||
avgPower = static_cast<int>(ergFile->AP());
|
||||
bikeStress = static_cast<int>(ergFile->bikeStress());
|
||||
bikeScore = static_cast<int>(ergFile->BS());
|
||||
isoPower = static_cast<int>(ergFile->IsoPower());
|
||||
xPower = static_cast<int>(ergFile->XP());
|
||||
}
|
||||
|
||||
int elevationGain = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::elevation), Qt::DisplayRole).toInt();
|
||||
int isoPower = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::isoPower), Qt::DisplayRole).toInt();
|
||||
int xPower = workoutModel->data(workoutModel->index(target.row(), TdbWorkoutModelIdx::xp), Qt::DisplayRole).toInt();
|
||||
setField("woFilename", filename);
|
||||
setField("woTitle", title);
|
||||
setField("woFileType", type);
|
||||
@@ -703,18 +724,6 @@ ManualActivityPageWorkout::selectionChanged
|
||||
setField("distance", distanceKM * (useMetricUnits ? 1.0 : MILES_PER_KM));
|
||||
}
|
||||
|
||||
if (ergFile != nullptr) {
|
||||
delete ergFile;
|
||||
ergFile = nullptr;
|
||||
}
|
||||
|
||||
if (type != "code") {
|
||||
ergFile = new ErgFile(filename, ErgFileFormat::unknown, context);
|
||||
if (! ergFile->isValid()) {
|
||||
delete ergFile;
|
||||
ergFile = nullptr;
|
||||
}
|
||||
}
|
||||
contentStack->setCurrentIndex(ergFile != nullptr ? 1 : 2);
|
||||
context->workout = ergFile;
|
||||
ergFilePlot->setData(ergFile);
|
||||
|
||||
@@ -62,28 +62,38 @@ bool ErgFile::isWorkout(QString name)
|
||||
return false;
|
||||
}
|
||||
|
||||
ErgFile::ErgFile(QString filename, ErgFileFormat mode, Context *context)
|
||||
ErgFile::ErgFile(QString filename, ErgFileFormat mode, Context *context, QDate when)
|
||||
: context(context)
|
||||
{
|
||||
if (when.isValid()) {
|
||||
this->when = when;
|
||||
} else {
|
||||
this->when = QDate::currentDate();
|
||||
}
|
||||
this->filename(filename);
|
||||
this->mode(mode);
|
||||
strictGradient(true);
|
||||
fHasGPS(false);
|
||||
if (context->athlete->zones("Bike")) {
|
||||
int zonerange = context->athlete->zones("Bike")->whichRange(QDateTime::currentDateTime().date());
|
||||
int zonerange = context->athlete->zones("Bike")->whichRange(this->when);
|
||||
if (zonerange >= 0) CP(context->athlete->zones("Bike")->getCP(zonerange));
|
||||
}
|
||||
reload();
|
||||
}
|
||||
|
||||
ErgFile::ErgFile(Context *context)
|
||||
ErgFile::ErgFile(Context *context, QDate when)
|
||||
: context(context)
|
||||
{
|
||||
if (when.isValid()) {
|
||||
this->when = when;
|
||||
} else {
|
||||
this->when = QDate::currentDate();
|
||||
}
|
||||
mode(ErgFileFormat::unknown);
|
||||
strictGradient(true);
|
||||
fHasGPS(false);
|
||||
if (context->athlete->zones("Bike")) {
|
||||
int zonerange = context->athlete->zones("Bike")->whichRange(QDateTime::currentDateTime().date());
|
||||
int zonerange = context->athlete->zones("Bike")->whichRange(this->when);
|
||||
if (zonerange >= 0) CP(context->athlete->zones("Bike")->getCP(zonerange));
|
||||
} else {
|
||||
CP(300);
|
||||
@@ -107,9 +117,9 @@ ErgFile::setFrom(ErgFile *f)
|
||||
}
|
||||
|
||||
ErgFile *
|
||||
ErgFile::fromContent(QString contents, Context *context)
|
||||
ErgFile::fromContent(QString contents, Context *context, QDate when)
|
||||
{
|
||||
ErgFile *p = new ErgFile(context);
|
||||
ErgFile *p = new ErgFile(context, when);
|
||||
|
||||
p->parseComputrainer(contents);
|
||||
p->finalize();
|
||||
@@ -118,9 +128,9 @@ ErgFile::fromContent(QString contents, Context *context)
|
||||
}
|
||||
|
||||
ErgFile *
|
||||
ErgFile::fromContent2(QString contents, Context *context)
|
||||
ErgFile::fromContent2(QString contents, Context *context, QDate when)
|
||||
{
|
||||
ErgFile *p = new ErgFile(context);
|
||||
ErgFile *p = new ErgFile(context, when);
|
||||
|
||||
p->parseErg2(contents);
|
||||
p->finalize();
|
||||
@@ -1235,7 +1245,7 @@ ErgFile::ZoneSections()
|
||||
QList<ErgFileZoneSection> ret;
|
||||
|
||||
const Zones *zones = context->athlete->zones("Bike");
|
||||
int zoneRange = zones->whichRange(QDate::currentDate());
|
||||
int zoneRange = zones->whichRange(when);
|
||||
QList<QString> zoneNames = zones->getZoneNames(zoneRange);
|
||||
if (hasWatts() && duration() > 0) {
|
||||
for (int i = 0; i < Points.size() - 1; ++i) {
|
||||
@@ -1302,7 +1312,7 @@ ErgFile::save(QStringList &errors)
|
||||
// get CP so we can scale back etc
|
||||
int lCP=0;
|
||||
if (context->athlete->zones("Bike")) {
|
||||
int zonerange = context->athlete->zones("Bike")->whichRange(QDateTime::currentDateTime().date());
|
||||
int zonerange = context->athlete->zones("Bike")->whichRange(when);
|
||||
if (zonerange >= 0) lCP = context->athlete->zones("Bike")->getCP(zonerange);
|
||||
}
|
||||
|
||||
@@ -1934,7 +1944,7 @@ ErgFile::calculateMetrics()
|
||||
bool first = true;
|
||||
|
||||
// CP
|
||||
int zonerange = context->athlete->zones("Bike")->whichRange(QDateTime::currentDateTime().date());
|
||||
int zonerange = context->athlete->zones("Bike")->whichRange(when);
|
||||
if (context->athlete->zones("Bike")) {
|
||||
if (zonerange >= 0) CP(context->athlete->zones("Bike")->getCP(zonerange));
|
||||
}
|
||||
|
||||
@@ -118,8 +118,8 @@ class ErgFileLap
|
||||
class ErgFile : public ErgFileBase
|
||||
{
|
||||
public:
|
||||
ErgFile(QString, ErgFileFormat, Context *context); // constructor uses filename
|
||||
ErgFile(Context *context); // no filename, going to use a string
|
||||
ErgFile(QString, ErgFileFormat, Context *context, QDate when = QDate()); // constructor uses filename
|
||||
ErgFile(Context *context, QDate when = QDate()); // no filename, going to use a string
|
||||
|
||||
~ErgFile(); // delete the contents
|
||||
|
||||
@@ -128,8 +128,8 @@ class ErgFile : public ErgFileBase
|
||||
void setFrom(ErgFile *f); // clone an existing workout
|
||||
bool save(QStringList &errors); // save back, with changes
|
||||
|
||||
static ErgFile *fromContent(QString, Context *); // read from memory *.erg
|
||||
static ErgFile *fromContent2(QString, Context *); // read from memory *.erg2
|
||||
static ErgFile *fromContent(QString, Context *, QDate when = QDate()); // read from memory *.erg
|
||||
static ErgFile *fromContent2(QString, Context *, QDate when = QDate()); // read from memory *.erg2
|
||||
|
||||
static bool isWorkout(QString); // is this a supported workout?
|
||||
|
||||
@@ -150,6 +150,8 @@ private:
|
||||
|
||||
bool coalescedSections = false;
|
||||
|
||||
QDate when;
|
||||
|
||||
public:
|
||||
void coalesceSections();
|
||||
bool hasCoalescedSections() const;
|
||||
|
||||
Reference in New Issue
Block a user