Use CET for time buckets

This commit is contained in:
danny 2025-12-24 10:56:56 +00:00
parent 36b5dc2623
commit 9286a313f0
2 changed files with 64 additions and 44 deletions

View file

@ -49,7 +49,7 @@ This inbox has a few dominant patterns. They tell you what an auto-reply system
| Most activity happened in **2025-12** | 47,315 messages in one month (87.5% of all messages in this export) |
| The #1 question/topic is **Just one word: book** | 1,857 times (68.4% of all questions/requests) |
| Questions/requests cluster on **Thursday, Friday** | Those two days contain most of the asking in this export |
| Most messages arrive in **18:00-23:59, 12:00-17:59 (UTC)** | If you are present in those blocks, reply rates usually improve |
| Most messages arrive in **18:00-23:59, 12:00-17:59 (CET)** | If you are present in those blocks, reply rates usually improve |
| Repeat messages make up **67.6%** of your text messages | Fast replies are often repeats; custom replies are where delays happen |
| Language used by people (approx) | Spanish 25.7%, English 18.1%, Unknown 56.2% |
| Language used in your messages (approx) | Spanish 63.8%, English 29.2%, Unknown 7.0% |
@ -65,8 +65,8 @@ To avoid guesswork, we start with 3-month blocks (a simple way to smooth noise),
|---|---:|---:|---:|
| 2024 Oct-Dec | 14 | 0 | 0 |
| 2025 Jan-Mar | 21 | 0 | 0 |
| 2025 Apr-Jun | 93 | 100 | 16 |
| 2025 Jul-Sep | 622 | 879 | 88 |
| 2025 Apr-Jun | 92 | 97 | 15 |
| 2025 Jul-Sep | 623 | 882 | 89 |
| 2025 Oct-Dec | 9,712 | 42,628 | 2,609 |
Same data as charts:
@ -75,8 +75,8 @@ Same data as charts:
pie title Messages From People by 3-Month Block
"2024 Oct-Dec" : 14
"2025 Jan-Mar" : 21
"2025 Apr-Jun" : 93
"2025 Jul-Sep" : 622
"2025 Apr-Jun" : 92
"2025 Jul-Sep" : 623
"2025 Oct-Dec" : 9712
```
@ -84,8 +84,8 @@ This shows when people replied most. A spike here usually means you posted somet
```mermaid
pie title Messages You Sent by 3-Month Block
"2025 Apr-Jun" : 100
"2025 Jul-Sep" : 879
"2025 Apr-Jun" : 97
"2025 Jul-Sep" : 882
"2025 Oct-Dec" : 42628
```
@ -107,8 +107,8 @@ This month-by-month table is the clearest view of how the inbox changed over tim
| 2025-03 | 5 | 0 | 0 | n/a |
| 2025-04 | 8 | 5 | 0 | n/a |
| 2025-05 | 48 | 28 | 8 | 12.5% |
| 2025-06 | 37 | 67 | 8 | 87.5% |
| 2025-07 | 145 | 319 | 36 | 63.9% |
| 2025-06 | 36 | 64 | 7 | 85.7% |
| 2025-07 | 146 | 322 | 37 | 64.9% |
| 2025-08 | 193 | 230 | 28 | 50.0% |
| 2025-09 | 284 | 330 | 24 | 20.8% |
| 2025-10 | 787 | 1,190 | 64 | 17.2% |
@ -125,24 +125,24 @@ Use this to time follow-ups and first messages. Do not spread effort evenly acro
| Day of week | Messages from people | Messages you sent | Questions/requests |
|---|---:|---:|---:|
| Monday | 1,627 | 8,547 | 140 |
| Tuesday | 1,952 | 9,622 | 189 |
| Wednesday | 1,242 | 5,396 | 155 |
| Thursday | 2,349 | 7,126 | 1,340 |
| Friday | 1,610 | 5,494 | 728 |
| Saturday | 840 | 3,579 | 88 |
| Sunday | 842 | 3,843 | 73 |
| Monday | 1,600 | 8,359 | 131 |
| Tuesday | 1,939 | 9,654 | 192 |
| Wednesday | 1,282 | 5,554 | 159 |
| Thursday | 2,261 | 6,908 | 1,268 |
| Friday | 1,705 | 5,733 | 803 |
| Saturday | 833 | 3,602 | 87 |
| Sunday | 842 | 3,797 | 73 |
Same data as a chart:
```mermaid
pie title Messages From People by Day of Week
"Monday" : 1627
"Tuesday" : 1952
"Wednesday" : 1242
"Thursday" : 2349
"Friday" : 1610
"Saturday" : 840
"Monday" : 1600
"Tuesday" : 1939
"Wednesday" : 1282
"Thursday" : 2261
"Friday" : 1705
"Saturday" : 833
"Sunday" : 842
```
@ -150,23 +150,23 @@ pie title Messages From People by Day of Week
> Most replies happen in a few time blocks.
Time zone here is UTC (a standard clock). If you work in another time zone, shift the blocks before you schedule.
Time zone here is CET (Central Europe). If you work in another time zone, shift the blocks before you schedule.
| Time of day (UTC) | Messages from people | Messages you sent |
| Time of day (CET) | Messages from people | Messages you sent |
|---|---:|---:|
| 00:00-05:59 | 1,885 | 8,304 |
| 06:00-11:59 | 1,374 | 6,889 |
| 12:00-17:59 | 3,092 | 12,937 |
| 18:00-23:59 | 4,111 | 15,477 |
| 00:00-05:59 | 2,113 | 8,907 |
| 06:00-11:59 | 1,274 | 6,637 |
| 12:00-17:59 | 2,333 | 10,883 |
| 18:00-23:59 | 4,742 | 17,180 |
Same data as a chart:
```mermaid
pie title Messages From People by Time of Day (UTC)
"00:00-05:59" : 1885
"06:00-11:59" : 1374
"12:00-17:59" : 3092
"18:00-23:59" : 4111
pie title Messages From People by Time of Day (CET)
"00:00-05:59" : 2113
"06:00-11:59" : 1274
"12:00-17:59" : 2333
"18:00-23:59" : 4742
```
### Reply Speed (Why It Matters)

View file

@ -13,6 +13,8 @@ from typing import Any, Iterable, Literal
from .analyze_instagram_export import canonicalize_text
DEFAULT_LOCAL_TZ_NAME = "Europe/Brussels"
def _safe_chmod_600(path: Path) -> None:
try:
@ -25,6 +27,17 @@ def _dt_from_ts_ms(ts_ms: int) -> datetime:
return datetime.fromtimestamp(ts_ms / 1000.0, tz=timezone.utc)
def _load_tz(name: str | None) -> timezone:
tz_name = (name or "").strip() or DEFAULT_LOCAL_TZ_NAME
try:
from zoneinfo import ZoneInfo # type: ignore
return ZoneInfo(tz_name) # type: ignore[return-value]
except Exception:
# Fallback: fixed CET (UTC+1). DST is not represented in this mode.
return timezone(timedelta(hours=1))
def _iso(ts_ms: int | None) -> str | None:
if not ts_ms:
return None
@ -488,8 +501,11 @@ def generate_report(
owner_name: str | None,
reply_window_hours: float,
scripted_min_count: int,
local_tz_name: str | None,
) -> Path:
export_root = _resolve_export_root(export_input)
local_tz = _load_tz(local_tz_name)
time_zone_label = "CET"
summary: dict[str, Any] | None = None
if analysis_dir:
@ -572,11 +588,13 @@ def generate_report(
min_ts = e.ts_ms if min_ts is None or e.ts_ms < min_ts else min_ts
max_ts = e.ts_ms if max_ts is None or e.ts_ms > max_ts else max_ts
month = _month_key(e.ts_ms)
quarter = _quarter_key(e.ts_ms)
weekday = _weekday_name(e.ts_ms)
hb = _hour_bucket(_hour(e.ts_ms))
day = _date_key(e.ts_ms)
dt_local = _dt_from_ts_ms(e.ts_ms).astimezone(local_tz)
month = f"{dt_local.year:04d}-{dt_local.month:02d}"
q = (dt_local.month - 1) // 3 + 1
quarter = f"{dt_local.year:04d}-Q{q}"
weekday = dt_local.strftime("%A")
hb = _hour_bucket(int(dt_local.hour))
day = dt_local.date().isoformat()
if e.is_owner:
outbound_msgs += 1
@ -691,8 +709,8 @@ def generate_report(
if min_ts is None or max_ts is None:
raise RuntimeError("No messages found in export.")
window_start = _dt_from_ts_ms(min_ts)
window_end = _dt_from_ts_ms(max_ts)
window_start = _dt_from_ts_ms(min_ts).astimezone(local_tz)
window_end = _dt_from_ts_ms(max_ts).astimezone(local_tz)
window_days = max(1, int((window_end - window_start).total_seconds() / 86400) + 1)
def median_seconds(values: list[int]) -> int | None:
@ -835,7 +853,7 @@ def generate_report(
report.append(f"| Most activity happened in **{m}** | {m_total:,} messages in one month ({_pct(m_total, total_msgs)} of all messages in this export) |")
report.append(f"| The #1 question/topic is **{top_theme}** | {top_theme_cnt:,} times ({_pct(top_theme_cnt, inbound_questions)} of all questions/requests) |")
report.append(f"| Questions/requests cluster on **{top_question_days_s}** | Those two days contain most of the asking in this export |")
report.append(f"| Most messages arrive in **{top_time_blocks_s} (UTC)** | If you are present in those blocks, reply rates usually improve |")
report.append(f"| Most messages arrive in **{top_time_blocks_s} ({time_zone_label})** | If you are present in those blocks, reply rates usually improve |")
report.append(f"| Repeat messages make up **{_pct(scripted_outbound_with_text, outbound_with_text)}** of your text messages | Fast replies are often repeats; custom replies are where delays happen |")
report.append(f"| Language used by people (approx) | {in_lang_summary} |")
report.append(f"| Language used in your messages (approx) | {out_lang_summary} |")
@ -935,11 +953,11 @@ def generate_report(
report.append("> Most replies happen in a few time blocks.")
report.append("")
report.append(
"Time zone here is UTC (a standard clock). If you work in another time zone, shift the blocks before you schedule."
f"Time zone here is {time_zone_label} (Central Europe). If you work in another time zone, shift the blocks before you schedule."
)
report.append("")
hb_order = ["00:00-05:59", "06:00-11:59", "12:00-17:59", "18:00-23:59"]
report.append("| Time of day (UTC) | Messages from people | Messages you sent |")
report.append(f"| Time of day ({time_zone_label}) | Messages from people | Messages you sent |")
report.append("|---|---:|---:|")
for hb in hb_order:
st = by_hour_bucket.get(hb, TimeBucketStats())
@ -948,7 +966,7 @@ def generate_report(
report.append("Same data as a chart:")
report.append("")
report.append("```mermaid")
report.append('pie title Messages From People by Time of Day (UTC)')
report.append(f'pie title Messages From People by Time of Day ({time_zone_label})')
for hb in hb_order:
v = by_hour_bucket.get(hb, TimeBucketStats()).inbound
if v:
@ -1208,6 +1226,7 @@ def main(argv: list[str] | None = None) -> int:
ap.add_argument("--analysis-dir", default=None, help="optional analyze_instagram_export output dir (for summary.json)")
ap.add_argument("--out", default=None, help="output markdown path")
ap.add_argument("--owner-name", default=None, help="owner sender_name (default: infer)")
ap.add_argument("--local-tz", default=DEFAULT_LOCAL_TZ_NAME, help="IANA time zone name (default: Europe/Brussels)")
ap.add_argument("--reply-window-hours", type=float, default=48.0, help="how long counts as a response")
ap.add_argument(
"--scripted-min-count",
@ -1229,6 +1248,7 @@ def main(argv: list[str] | None = None) -> int:
owner_name=(args.owner_name.strip() if args.owner_name else None),
reply_window_hours=float(args.reply_window_hours),
scripted_min_count=int(args.scripted_min_count),
local_tz_name=(args.local_tz.strip() if args.local_tz else None),
)
print(json.dumps({"ok": True, "out": str(p)}, ensure_ascii=False))
return 0