From 5a30d6f20f7562762351cd36981f0d16d6dcffcb Mon Sep 17 00:00:00 2001 From: NY Date: Thu, 13 Mar 2025 16:35:22 +0800 Subject: [PATCH] =?UTF-8?q?+=E5=8F=AF=E8=B0=83=E8=8A=82=E7=9A=84prediction?= =?UTF-8?q?=20tweets=E4=BB=A5=E5=8F=8APace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/dash/app_html.py | 60 +++++++++++++++++++++++++++--------- pkg/dash/func/info.py | 72 +++++++++++++++++++++++++++---------------- 2 files changed, 91 insertions(+), 41 deletions(-) diff --git a/pkg/dash/app_html.py b/pkg/dash/app_html.py index b3c5dcc..8bbbab9 100644 --- a/pkg/dash/app_html.py +++ b/pkg/dash/app_html.py @@ -3,9 +3,7 @@ from pkg.config import interval_options, days_options, render_data def layout_config(app): - # Dash app layout (unchanged except default days value) app.layout = html.Div([ - # Left sidebar with clock button and tooltip (unchanged) html.Div( id='clock-container', children=[ @@ -63,7 +61,6 @@ def layout_config(app): 'whiteSpace': 'nowrap' } ), - # 新增按钮和提示框 html.Div( id='info-button', children='ℹ️', @@ -76,11 +73,11 @@ def layout_config(app): ), html.Div( id='info-tooltip', - children='这是一个信息按钮示例', # 默认显示的信息 + children='这是一个信息按钮示例', style={ 'position': 'absolute', 'left': '35px', - 'top': '80px', # 调整位置,避免与其他 tooltip 重叠 + 'top': '80px', 'backgroundColor': 'rgba(0, 0, 0, 0.8)', 'color': 'white', 'padding': '10px', @@ -97,7 +94,7 @@ def layout_config(app): html.Img( src='https://pbs.twimg.com/profile_images/1893803697185910784/Na5lOWi5_400x400.jpg', style={ - 'height': '24px', # Matches fontSize of other buttons + 'height': '24px', 'width': '24px', 'cursor': 'pointer', 'padding': '5px', @@ -139,7 +136,7 @@ def layout_config(app): dcc.Dropdown( id='days-display-picker', options=days_options, - value=30, # Default changed to 30 since 1 is removed + value=30, style={'width': '50%', 'marginTop': '10px'} ) ]), @@ -160,13 +157,48 @@ def layout_config(app): dcc.Tab(label='Heatmap(1-day)', value='one_day_heatmap'), ]), html.Div(id='tabs-content'), - dcc.Input( - id='target-input', - type='number', - placeholder='输入 Target 值', - value=None, - style={'marginTop': '10px', 'width': '50%'} - ), + html.Table([ + html.Tr([ + html.Td("Target:", style={'paddingRight': '10px'}), + html.Td( + dcc.Input( + id='target-input', + type='number', + placeholder='输入 Target 值', + value=None, + style={'width': '100%'} + ) + ) + ]), + html.Tr([ + html.Td("Predict Tweets:", style={'paddingRight': '10px'}), + html.Td( + dcc.Input( + id='predict-tweets-input', + type='number', + placeholder='输入 Predict Tweets 值', + value=None, + style={'width': '100%'} + ) + ) + ]), + html.Tr([ + html.Td("Hour:", style={'paddingRight': '10px'}), + html.Td( + dcc.Input( + id='hour-input', + type='number', + placeholder='输入 Hour 值', + value=None, + style={'width': '100%'} + ) + ) + ]) + ], style={ + 'width': '50%', + 'marginTop': '10px', + 'borderCollapse': 'collapse' + }) ], style={'marginLeft': '50px'}), dcc.Interval(id='clock-interval', interval=1000, n_intervals=0) diff --git a/pkg/dash/func/info.py b/pkg/dash/func/info.py index 80619a9..72418fc 100644 --- a/pkg/dash/func/info.py +++ b/pkg/dash/func/info.py @@ -10,12 +10,14 @@ from dash import html @app.callback( [Output('info-tooltip', 'children')], [Input('clock-interval', 'n_intervals'), - Input('target-input', 'value')] + Input('target-input', 'value'), + Input('predict-tweets-input', 'value'), + Input('hour-input', 'value')] ) -def update_info(n, target_value): +def update_info(n, target_value, increment_value, hour_value): pace = calculate_tweet_pace() decline_rates = calculate_pace_decline_rate() - pace_increases = calculate_pace_increase_in_hour() + pace_increases = calculate_pace_increase_in_hour(increment_value, hour_value) tweet_count, days_to_next_friday = get_pace_params() remain_hours = days_to_next_friday * 24 now = tweet_count @@ -28,6 +30,16 @@ def update_info(n, target_value): days_passed = 7 - days_to_next_friday avg_tweets = round(tweet_count / days_passed, 2) if days_passed > 0 else 0 + custom_increment = pace_increases.get('custom_increment') + if custom_increment is None: + custom_header = "[TBD]" + custom_value = "TBD" + else: + increment = pace_increases['custom_increment_key'] + hours = int(hour_value) + custom_header = f"{increment}({hours}h)" + custom_value = f"{custom_increment:.2f}" + pace_table_rows = [ html.Tr([ html.Th('Pace', style=table_style_border), @@ -37,7 +49,7 @@ def update_info(n, target_value): html.Th('5(1h)', style=table_style_border), html.Th('10(1h)', style=table_style_border), html.Th('20(1h)', style=table_style_border), - html.Th('30(1h)', style=table_style_border) + html.Th(custom_header, style=table_style_border) ]), html.Tr([ html.Td(f"{pace:.2f}", style=table_style_border), @@ -47,23 +59,15 @@ def update_info(n, target_value): html.Td(f"{pace_increases['increase_5']:.2f}", style=table_style_border), html.Td(f"{pace_increases['increase_10']:.2f}", style=table_style_border), html.Td(f"{pace_increases['increase_20']:.2f}", style=table_style_border), - html.Td(f"{pace_increases['increase_30']:.2f}", style=table_style_border) + html.Td(custom_value, style=table_style_border) ]), html.Tr([ - html.Td(f"Remain", - colSpan=4, - style=table_style_c), - html.Td(format_time_str(days_to_next_friday), - colSpan=4, - style=table_style_l) + html.Td(f"Remain", colSpan=4, style=table_style_c), + html.Td(format_time_str(days_to_next_friday), colSpan=4, style=table_style_l) ]), html.Tr([ - html.Td(f"Last Tweet", - colSpan=4, - style=table_style_c), - html.Td(format_time_str(get_time_since_last_tweet()), - colSpan=4, - style=table_style_l) + html.Td(f"Last Tweet", colSpan=4, style=table_style_c), + html.Td(format_time_str(get_time_since_last_tweet()), colSpan=4, style=table_style_l) ]), html.Tr([ html.Th("Target", colSpan=2, style=table_style_border), @@ -83,7 +87,6 @@ def update_info(n, target_value): }) return [pace_table] - def get_pace_params(): est = pytz.timezone('US/Eastern') now = datetime.now(est) @@ -112,20 +115,35 @@ def calculate_pace_decline_rate(): return round(decline_per_hour, 2) -def calculate_pace_increase_in_hour(): +def calculate_pace_for_increment(increment, hours, tweet_count, days_to_next_friday, current_pace): + future_days = days_to_next_friday - (hours / 24) + new_tweet_count = tweet_count + increment + if future_days <= 0: + return round(new_tweet_count, 2) + new_pace = (new_tweet_count / (7 - future_days)) * future_days + new_tweet_count + return round(max(new_pace, new_tweet_count), 2) + + +def calculate_pace_increase_in_hour(increment_value, hour_value): tweet_count, days_to_next_friday = get_pace_params() current_pace = (tweet_count / (7 - days_to_next_friday)) * days_to_next_friday + tweet_count - future_days = days_to_next_friday - (1 / 24) - increments = [0, 1, 5, 10, 20, 30] + increments = [0, 1, 5, 10, 20] pace_increases = {} - for increment in increments: - new_tweet_count = tweet_count + increment - new_pace = (new_tweet_count / (7 - future_days)) * future_days + new_tweet_count - pace_increase = new_pace - current_pace - pace_increases[f'increase_{increment}'] = round(current_pace + pace_increase, 2) + for inc in increments: + pace_increases[f'increase_{inc}'] = calculate_pace_for_increment( + inc, 1, tweet_count, days_to_next_friday, current_pace + ) + if increment_value is None or hour_value is None: + pace_increases['custom_increment'] = None + else: + increment = int(increment_value) + hours = int(hour_value) + pace_increases['custom_increment'] = calculate_pace_for_increment( + increment, hours, tweet_count, days_to_next_friday, current_pace + ) + pace_increases['custom_increment_key'] = increment return pace_increases - def calculate_avg_tweets_per_day(target, now, remain): Xi = get_hourly_weighted_array() if remain <= 0: