-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New Full Logbook #38
New Full Logbook #38
Changes from 8 commits
7667431
89a0564
9010e4d
37f9605
5831bc4
d7df7e1
bb0266d
a5d4b11
92832bc
8c0f916
d089a01
8a504a5
1dc9d81
f8890db
75f9f98
87f338a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,6 +3,7 @@ | |
import sqlite3 | ||
import bs4 | ||
import requests | ||
import pandas as pd | ||
|
||
import boardlib.util.grades | ||
|
||
|
@@ -391,3 +392,204 @@ def save_climb( | |
) | ||
response.raise_for_status() | ||
return response.json() | ||
|
||
|
||
def get_bids_logbook(board, token, user_id): | ||
sync_results = user_sync(board, token, user_id, tables=["bids"]) | ||
return sync_results["PUT"]["bids"] | ||
|
||
|
||
|
||
|
||
def bids_logbook_entries(board, username, password, db_path=None): | ||
login_info = login(board, username, password) | ||
raw_entries = get_bids_logbook(board, login_info["token"], login_info["user_id"]) | ||
|
||
for raw_entry in raw_entries: | ||
if db_path: | ||
climb_name = get_climb_name_from_db(db_path, raw_entry["climb_uuid"]) | ||
else: | ||
climb_name = get_climb_name(board, raw_entry["climb_uuid"]) | ||
|
||
yield { | ||
"uuid": raw_entry["uuid"], | ||
"user_id": raw_entry["user_id"], | ||
"climb_uuid": raw_entry["climb_uuid"], | ||
"climb_name": climb_name if climb_name else "Unknown Climb", | ||
"angle": raw_entry["angle"], | ||
"is_mirror": raw_entry["is_mirror"], | ||
"bid_count": raw_entry["bid_count"], | ||
"comment": raw_entry["comment"], | ||
"climbed_at": raw_entry["climbed_at"], | ||
"created_at": raw_entry["created_at"], | ||
} | ||
|
||
def get_displayed_grade_from_db(database, climb_uuid, angle, grades_dict): | ||
conn = sqlite3.connect(database) | ||
cursor = conn.cursor() | ||
cursor.execute( | ||
"SELECT display_difficulty FROM climb_stats WHERE climb_uuid = ? AND angle = ?", | ||
(climb_uuid, angle) | ||
) | ||
row = cursor.fetchone() | ||
conn.close() | ||
if row: | ||
difficulty_value = round(row[0]) | ||
grade_info = grades_dict.get(difficulty_value, {}) | ||
return grade_info.get("french_name", "NA") | ||
return "NA" | ||
lemeryfertitta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def get_full_logbook_entries(board, username, password, grade_type="font", db_path=None): | ||
lemeryfertitta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# Get bids and ascents data | ||
login_info = login(board, username, password) | ||
token = login_info["token"] | ||
user_id = login_info["user_id"] | ||
|
||
bids_entries = list(bids_logbook_entries(board, username, password, db_path)) | ||
raw_ascents_entries = get_logbook(board, token, user_id) | ||
|
||
# Convert bids entries to DataFrame | ||
bids_df = pd.DataFrame(bids_entries) | ||
bids_df['climbed_at'] = pd.to_datetime(bids_df['climbed_at']) | ||
|
||
# Process raw ascents entries and convert to DataFrame | ||
ascents_entries = [] | ||
grades = get_grades(board) | ||
|
||
# Convert grades to dictionary for easy lookup | ||
grades_dict = {grade['difficulty']: grade for grade in grades} | ||
|
||
for raw_entry in raw_ascents_entries: | ||
if not raw_entry["is_listed"]: | ||
continue | ||
if db_path: | ||
climb_name = get_climb_name_from_db(db_path, raw_entry["climb_uuid"]) | ||
displayed_grade = get_displayed_grade_from_db(db_path, raw_entry["climb_uuid"], raw_entry["angle"], grades_dict) | ||
else: | ||
climb_name = get_climb_name(board, raw_entry["climb_uuid"]) | ||
displayed_grade = "NA" | ||
lemeryfertitta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
grade_info = grades_dict.get(raw_entry["difficulty"], {}) | ||
logged_grade = grade_info.get("french_name" if grade_type == "font" else "verm_name", "Unknown") | ||
lemeryfertitta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
ascents_entries.append({ | ||
"board": board, | ||
"angle": raw_entry["angle"], | ||
"climb_uuid": raw_entry["climb_uuid"], | ||
"name": climb_name if climb_name else "Unknown Climb", | ||
lemeryfertitta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"date": datetime.datetime.strptime(raw_entry["climbed_at"], "%Y-%m-%d %H:%M:%S"), | ||
"logged_grade": logged_grade, | ||
"displayed_grade": displayed_grade, | ||
"tries": raw_entry["attempt_id"] if raw_entry["attempt_id"] else raw_entry["bid_count"], | ||
"is_mirror": raw_entry["is_mirror"] | ||
}) | ||
|
||
ascents_df = pd.DataFrame(ascents_entries) | ||
|
||
# Summarize the bids table | ||
bids_summary = bids_df.groupby(['climb_uuid', 'climb_name', bids_df['climbed_at'].dt.date, 'is_mirror', 'angle']).agg({ | ||
'bid_count': 'sum' | ||
}).reset_index().rename(columns={'climbed_at': 'date'}) | ||
|
||
# Create a new column for is_ascent | ||
bids_summary['is_ascent'] = False | ||
bids_summary['tries'] = bids_summary['bid_count'] | ||
|
||
# Check for ascents and update the logbook | ||
final_logbook = [] | ||
|
||
for _, ascent_row in ascents_df.iterrows(): | ||
ascent_date = ascent_row['date'].date() | ||
ascent_climb_uuid = ascent_row['climb_uuid'] | ||
ascent_climb_name = ascent_row['name'] | ||
ascent_is_mirror = ascent_row['is_mirror'] | ||
ascent_angle = ascent_row['angle'] | ||
|
||
# Find corresponding bids entries | ||
bid_match = bids_summary[ | ||
(bids_summary['climb_uuid'] == ascent_climb_uuid) & | ||
(bids_summary['date'] == ascent_date) & | ||
(bids_summary['is_mirror'] == ascent_is_mirror) & | ||
(bids_summary['angle'] == ascent_angle) | ||
] | ||
|
||
if not bid_match.empty: | ||
bid_row = bid_match.iloc[0] | ||
total_tries = ascent_row['tries'] + bid_row['tries'] | ||
final_logbook.append({ | ||
'board': ascent_row['board'], | ||
'angle': ascent_row['angle'], | ||
'climb_name': ascent_row['name'], | ||
'date': ascent_row['date'], | ||
'logged_grade': ascent_row['logged_grade'], | ||
'displayed_grade': ascent_row['displayed_grade'], | ||
'tries': total_tries, | ||
'is_mirror': ascent_row['is_mirror'], | ||
'is_ascent': True | ||
}) | ||
bids_summary = bids_summary.drop(bid_match.index) # Remove matched bids | ||
else: | ||
final_logbook.append({ | ||
'board': ascent_row['board'], | ||
'angle': ascent_row['angle'], | ||
'climb_name': ascent_row['name'], | ||
'date': ascent_row['date'], | ||
'logged_grade': ascent_row['logged_grade'], | ||
'displayed_grade': ascent_row['displayed_grade'], | ||
'tries': ascent_row['tries'], | ||
'is_mirror': ascent_row['is_mirror'], | ||
'is_ascent': True | ||
}) | ||
|
||
# Add remaining bids that do not have corresponding ascents | ||
for _, bid_row in bids_summary.iterrows(): | ||
if db_path: | ||
displayed_grade = get_displayed_grade_from_db(db_path, bid_row["climb_uuid"], bid_row["angle"], grades_dict) | ||
else: | ||
displayed_grade = 'NA' | ||
lemeryfertitta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
final_logbook.append({ | ||
'board': board, | ||
'angle': bid_row['angle'], | ||
'climb_name': bid_row['climb_name'], | ||
'date': bid_row['date'], | ||
'logged_grade': 'NA', # We don't have logged_grade information for bids | ||
lemeryfertitta marked this conversation as resolved.
Show resolved
Hide resolved
|
||
'displayed_grade': displayed_grade, | ||
'tries': bid_row['tries'], | ||
'is_mirror': bid_row['is_mirror'], | ||
'is_ascent': False | ||
}) | ||
|
||
# Convert to DataFrame | ||
full_logbook_df = pd.DataFrame(final_logbook, columns=['board', 'angle', 'climb_name', 'date', 'logged_grade', 'displayed_grade', 'tries', 'is_mirror', 'is_ascent']) | ||
|
||
# Ensure all dates are converted to Timestamps | ||
full_logbook_df['date'] = pd.to_datetime(full_logbook_df['date']) | ||
|
||
|
||
# Calculate sessions_count and tries_total | ||
def calculate_sessions_count(group): | ||
group = group.sort_values(by='date') | ||
unique_dates = group['date'].dt.date.drop_duplicates().reset_index(drop=True) | ||
sessions_count = unique_dates.rank(method='dense').astype(int) | ||
sessions_count_map = dict(zip(unique_dates, sessions_count)) | ||
group['sessions_count'] = group['date'].dt.date.map(sessions_count_map) | ||
return group | ||
|
||
full_logbook_df = full_logbook_df.groupby(['climb_name', 'is_mirror', 'angle']).apply(calculate_sessions_count).reset_index(drop=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
def calculate_tries_total(group): | ||
group = group.sort_values(by='date') | ||
group['tries_total'] = group['tries'].cumsum() | ||
return group | ||
|
||
full_logbook_df = full_logbook_df.groupby(['climb_name', 'is_mirror', 'angle']).apply(calculate_tries_total).reset_index(drop=True) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
# Add is_repeat column | ||
full_logbook_df['is_repeat'] = full_logbook_df.duplicated(subset=['climb_name', 'is_mirror', 'angle'], keep='first') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
# Sort the DataFrame by date | ||
full_logbook_df = full_logbook_df.sort_values(by='date') | ||
|
||
return full_logbook_df | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than maintaining two logbook commands, let's just get your features into the original command.
For Moonboard logbooks, we can just return null in the new fields for now, with the idea of eventually implementing them or just leaving them null if they don't apply. I think it's fine to leave fields null if they require a database. We can update the README and/or command help to explain the differences.