diff --git a/pkg/dash/app_html.py b/pkg/dash/app_html.py index df8f2b6..b3c5dcc 100644 --- a/pkg/dash/app_html.py +++ b/pkg/dash/app_html.py @@ -160,7 +160,15 @@ 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%'} + ), ], style={'marginLeft': '50px'}), + dcc.Interval(id='clock-interval', interval=1000, n_intervals=0) ]) return app diff --git a/pkg/dash/func/info.py b/pkg/dash/func/info.py index 7694004..b07f5d9 100644 --- a/pkg/dash/func/info.py +++ b/pkg/dash/func/info.py @@ -1,5 +1,5 @@ import pytz -from pkg.tool import get_tweets_since_last_friday, format_time_str, get_time_since_last_tweet +from pkg.tool import get_tweets_since_last_friday, format_time_str, get_time_since_last_tweet, get_hourly_weighted_array from pkg.dash.app_init import app from dash.dependencies import Input, Output from datetime import timedelta @@ -9,19 +9,25 @@ from dash import html @app.callback( [Output('info-tooltip', 'children')], - [Input('clock-interval', 'n_intervals')] + [Input('clock-interval', 'n_intervals'), + Input('target-input', 'value')] ) -def update_info(n): - # 获取所有指标 +def update_info(n, target_value): pace = calculate_tweet_pace() decline_rates = calculate_pace_decline_rate() pace_increases = calculate_pace_increase_in_hour() - _, days_to_next_friday = get_pace_params() + tweet_count, days_to_next_friday = get_pace_params() + remain_hours = days_to_next_friday * 24 + now = tweet_count table_style_border = {'textAlign': 'center', 'border': '1px solid white'} table_style_c = {'textAlign': 'center'} table_style_l = {'textAlign': 'left'} - # First table for Pace + target = int(target_value) if target_value is not None else None + avg_tweets_per_day = calculate_avg_tweets_per_day(target, now, remain_hours) if target is not None else "未计算" + days_passed = 7 - days_to_next_friday + avg_tweets = round(tweet_count / days_passed, 2) if days_passed > 0 else 0 + pace_table_rows = [ html.Tr([ html.Th('Pace', style=table_style_border), @@ -66,6 +72,22 @@ def update_info(n): html.Td("1.Check Reply", colSpan=7, style=table_style_l) + ]), + html.Tr([ + html.Td(f"Target: {target if target else '[未设置]'}", + colSpan=4, + style=table_style_c), + html.Td(f"Need's Avg Tweets Per Day: {avg_tweets_per_day}", + colSpan=4, + style=table_style_l) + ]), + html.Tr([ + html.Td(f"Avg Tweets: {avg_tweets}", + colSpan=4, + style=table_style_c), + html.Td("", # 右侧留空,与上一行对齐 + colSpan=4, + style=table_style_l) ]) ] pace_table = html.Table(pace_table_rows, style={ @@ -114,6 +136,42 @@ def calculate_pace_increase_in_hour(): 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 - # Add current pace to the increase value pace_increases[f'increase_{increment}'] = round(current_pace + pace_increase, 2) return pace_increases + + +def calculate_avg_tweets_per_day(target, now, remain): + Xi = get_hourly_weighted_array() + if remain <= 0: + return "剩余时间不足,无法计算" + if target <= now: + return "目标已达成,无需额外发帖" + + fx = max(remain - 12, 0) + + if remain > 12: + fy = sum(Xi[0:12]) * 24 + else: + full_hours = int(remain) + fractional_hour = remain - full_hours + if full_hours >= 24: + print(f"Debug: full_hours={full_hours} exceeds 24, capping at 23") + full_hours = 23 + fractional_hour = 0 # 如果超过24小时,小数部分忽略 + + if full_hours < 0: + print(f"Debug: full_hours={full_hours} is negative, setting to 0") + full_hours = 0 + + if full_hours > 0: + fy = sum(Xi[0:full_hours]) + Xi[full_hours] * fractional_hour + else: + fy = Xi[0] * fractional_hour + fy *= 24 + + if fx + fy == 0: + return "计算无效,请检查剩余时间" + + result = (target - now) / ((fx + fy) / 24) + print(f"Debug: fx={fx}, fy={fy}, result={result}") + return round(result, 2) \ No newline at end of file diff --git a/pkg/dash/func/render.py b/pkg/dash/func/render.py index 5b831e9..2cdc5f8 100644 --- a/pkg/dash/func/render.py +++ b/pkg/dash/func/render.py @@ -112,13 +112,18 @@ def render_tab_content(tab, selected_dates, interval, time_zones, days_to_displa x_labels = [f"{i * interval:02d}" for i in range(intervals_per_hour)] + rate_values = [[z_values[h][i] / tweet_count_total if tweet_count_total > 0 else 0 + for i in range(intervals_per_hour)] + for h in range(24)] + fig = go.Figure(data=go.Heatmap( z=z_values, x=x_labels, y=[f"{h:02d}" for h in hours], colorscale='Viridis', hoverongaps=False, - hovertemplate='%{y}:%{x} EST
Tweets: %{z}' + customdata=rate_values, + hovertemplate='%{y}:%{x} EST
Tweets: %{z}
Rate: %{customdata:.2%}' )) if tab in ['line', 'one_day_heatmap']: diff --git a/pkg/tool.py b/pkg/tool.py index bcc8eb7..a7c92d6 100644 --- a/pkg/tool.py +++ b/pkg/tool.py @@ -94,4 +94,34 @@ def format_time_str(days_to_next_friday): minutes = int((total_seconds % (60 * 60)) // 60) seconds = int(total_seconds % 60) total_hours = round(days_to_next_friday * 24, 2) - return f"{days}d {hours:02d}h {minutes:02d}m {seconds:02d}s ({total_hours}h)" \ No newline at end of file + return f"{days}d {hours:02d}h {minutes:02d}m {seconds:02d}s ({total_hours}h)" + + +def get_hourly_weighted_array(): + est = pytz.timezone('US/Eastern') + now = datetime.now(est).date() + last_7_days = [now - timedelta(days=i) for i in range(7)] + + multi_data_agg = render_data.global_agg_df[ + render_data.global_agg_df['date'].isin(last_7_days)].copy() + + if multi_data_agg.empty: + return [1 / 24] * 24 + + agg_data = aggregate_data(multi_data_agg, 60) + one_day_data = agg_data.groupby('interval_group')['tweet_count'].sum().reset_index() + tweet_count_total = one_day_data['tweet_count'].sum() + + hourly_rates = [0] * 24 + for _, row in one_day_data.iterrows(): + minute = row['interval_group'] + hour = int(minute // 60) + if hour < 24: + hourly_rates[hour] = row['tweet_count'] / tweet_count_total if tweet_count_total > 0 else 0 + + total_rate = sum(hourly_rates) + if total_rate > 0: + hourly_rates = [rate / total_rate for rate in hourly_rates] + else: + hourly_rates = [1 / 24] * 24 + return hourly_rates \ No newline at end of file diff --git a/plan.md b/plan.md index f700518..d01c7ae 100644 --- a/plan.md +++ b/plan.md @@ -1,5 +1,7 @@ #### 后续 1. 考虑通过 tweets 数预测市场曲线的模型(人工智能) 2. 考虑elon jets的效果 +3. 推算出达到某个市场要求,这七天内的发帖情况应该是什么样的。 +4. peak点预期:人工智能预测peak时间段,并列举出peak情况对应市场期望。