2025-03-06 14:53:17 +08:00
|
|
|
|
import pytz
|
2025-03-12 17:40:20 +08:00
|
|
|
|
from pkg.tool import get_tweets_since_last_friday, format_time_str, get_time_since_last_tweet, get_hourly_weighted_array
|
2025-03-06 13:59:37 +08:00
|
|
|
|
from pkg.dash.app_init import app
|
|
|
|
|
from dash.dependencies import Input, Output
|
|
|
|
|
from datetime import timedelta
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
from dash import html
|
|
|
|
|
|
2025-03-07 13:52:35 +08:00
|
|
|
|
|
2025-03-06 13:59:37 +08:00
|
|
|
|
@app.callback(
|
|
|
|
|
[Output('info-tooltip', 'children')],
|
2025-03-12 17:40:20 +08:00
|
|
|
|
[Input('clock-interval', 'n_intervals'),
|
|
|
|
|
Input('target-input', 'value')]
|
2025-03-06 13:59:37 +08:00
|
|
|
|
)
|
2025-03-12 17:40:20 +08:00
|
|
|
|
def update_info(n, target_value):
|
2025-03-06 14:53:17 +08:00
|
|
|
|
pace = calculate_tweet_pace()
|
|
|
|
|
decline_rates = calculate_pace_decline_rate()
|
|
|
|
|
pace_increases = calculate_pace_increase_in_hour()
|
2025-03-12 17:40:20 +08:00
|
|
|
|
tweet_count, days_to_next_friday = get_pace_params()
|
|
|
|
|
remain_hours = days_to_next_friday * 24
|
|
|
|
|
now = tweet_count
|
2025-03-07 13:52:35 +08:00
|
|
|
|
table_style_border = {'textAlign': 'center', 'border': '1px solid white'}
|
2025-03-07 14:14:08 +08:00
|
|
|
|
table_style_c = {'textAlign': 'center'}
|
|
|
|
|
table_style_l = {'textAlign': 'left'}
|
2025-03-07 13:52:35 +08:00
|
|
|
|
|
2025-03-12 17:40:20 +08:00
|
|
|
|
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
|
|
|
|
|
|
2025-03-07 13:52:35 +08:00
|
|
|
|
pace_table_rows = [
|
|
|
|
|
html.Tr([
|
|
|
|
|
html.Th('Pace', style=table_style_border),
|
|
|
|
|
html.Th('DCL', style=table_style_border),
|
|
|
|
|
html.Th('0(1h)', style=table_style_border),
|
|
|
|
|
html.Th('1(1h)', style=table_style_border),
|
|
|
|
|
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.Tr([
|
|
|
|
|
html.Td(f"{pace:.2f}", style=table_style_border),
|
|
|
|
|
html.Td(decline_rates, style=table_style_border),
|
|
|
|
|
html.Td(f"{pace_increases['increase_0']:.2f}", style=table_style_border),
|
|
|
|
|
html.Td(f"{pace_increases['increase_1']:.2f}", style=table_style_border),
|
|
|
|
|
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.Tr([
|
2025-03-07 14:14:08 +08:00
|
|
|
|
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)
|
2025-03-11 09:47:44 +08:00
|
|
|
|
]),
|
|
|
|
|
html.Tr([
|
|
|
|
|
html.Td(f"Warning:",
|
|
|
|
|
colSpan=1,
|
|
|
|
|
style=table_style_c),
|
|
|
|
|
html.Td("1.Check Reply",
|
|
|
|
|
colSpan=7,
|
|
|
|
|
style=table_style_l)
|
2025-03-12 17:40:20 +08:00
|
|
|
|
]),
|
|
|
|
|
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)
|
2025-03-07 13:52:35 +08:00
|
|
|
|
])
|
2025-03-06 14:53:17 +08:00
|
|
|
|
]
|
2025-03-07 13:52:35 +08:00
|
|
|
|
pace_table = html.Table(pace_table_rows, style={
|
2025-03-06 13:59:37 +08:00
|
|
|
|
'width': '100%',
|
|
|
|
|
'textAlign': 'left',
|
|
|
|
|
'borderCollapse': 'collapse'
|
|
|
|
|
})
|
2025-03-07 13:52:35 +08:00
|
|
|
|
return [pace_table]
|
|
|
|
|
|
2025-03-06 13:59:37 +08:00
|
|
|
|
|
2025-03-06 14:53:17 +08:00
|
|
|
|
def get_pace_params():
|
2025-03-06 13:59:37 +08:00
|
|
|
|
est = pytz.timezone('US/Eastern')
|
|
|
|
|
now = datetime.now(est)
|
|
|
|
|
today = now.date()
|
|
|
|
|
days_to_next_friday = (4 - today.weekday()) % 7
|
|
|
|
|
next_friday = (now.replace(hour=12, minute=0, second=0, microsecond=0) +
|
|
|
|
|
timedelta(days=days_to_next_friday))
|
|
|
|
|
if now > next_friday:
|
|
|
|
|
next_friday += timedelta(days=7)
|
|
|
|
|
days_to_next_friday = (next_friday - now).total_seconds() / (24 * 60 * 60)
|
2025-03-06 14:53:17 +08:00
|
|
|
|
tweet_count = get_tweets_since_last_friday()
|
|
|
|
|
return tweet_count, days_to_next_friday
|
|
|
|
|
|
2025-03-07 13:52:35 +08:00
|
|
|
|
|
2025-03-06 14:53:17 +08:00
|
|
|
|
def calculate_tweet_pace():
|
|
|
|
|
tweet_count, days_to_next_friday = get_pace_params()
|
2025-03-06 13:59:37 +08:00
|
|
|
|
pace = (tweet_count / (7 - days_to_next_friday)) * days_to_next_friday + tweet_count
|
2025-03-06 14:53:17 +08:00
|
|
|
|
return round(pace, 6) if pace > 0 else float(tweet_count)
|
|
|
|
|
|
2025-03-07 13:52:35 +08:00
|
|
|
|
|
2025-03-06 14:53:17 +08:00
|
|
|
|
def calculate_pace_decline_rate():
|
|
|
|
|
tweet_count, days_to_next_friday = get_pace_params()
|
|
|
|
|
T = 7
|
|
|
|
|
decline_per_day = -(tweet_count * T) / ((T - days_to_next_friday) ** 2)
|
|
|
|
|
decline_per_hour = decline_per_day / 24
|
2025-03-07 13:52:35 +08:00
|
|
|
|
return round(decline_per_hour, 2)
|
|
|
|
|
|
2025-03-06 14:53:17 +08:00
|
|
|
|
|
|
|
|
|
def calculate_pace_increase_in_hour():
|
|
|
|
|
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)
|
2025-03-07 13:52:35 +08:00
|
|
|
|
increments = [0, 1, 5, 10, 20, 30]
|
2025-03-06 14:53:17 +08:00
|
|
|
|
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
|
2025-03-07 13:52:35 +08:00
|
|
|
|
pace_increases[f'increase_{increment}'] = round(current_pace + pace_increase, 2)
|
2025-03-06 14:53:17 +08:00
|
|
|
|
return pace_increases
|
2025-03-12 17:40:20 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)
|