Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ python -m unittest discover -s tests # 必须全绿
**WGMM 纯算法层(`wgmm_monitor/wgmm/`)**
- `scheduler.decide_next_frequency()`: WGMM 调频决策主函数
- `scheduler.scan_future_peak()`: 未来 15 天峰值扫描
- `scoring.calculate_point_score()` / `batch_calculate_scores()`: 单点 / 批量发布概率得分
- `scoring.calculate_point_score()` / `batch_calculate_scores()`: 单点 / 批量发布概率得分(周期得分 × 条件间隔得分几何均值 − 负向惩罚)
- `features.vectorized_time_features_numpy()`: 周期性时间特征提取
- `features.get_raw_time_components()`: 离散时间维度(用于权重学习)
- `learning.aggregate_publish_events()`: 把视频粒度时间戳折成 UP 主行为粒度(正向数据用,默认 600 秒阈值)
Expand All @@ -386,7 +386,7 @@ python -m unittest discover -s tests # 必须全绿

**业务服务层(`wgmm_monitor/services/`)**
- `MonitorService.run_monitor()`: 主监控循环(三层检测 + 通知 + 调频)
- `BilibiliService.check_potential_new_parts()`: 第一层 - 分片预检查
- `BilibiliService.check_potential_new_parts(memory_urls, known_urls)`: 第一层 - 分片预检查;合并 Gist 状态与本地状态确定最高已知分片,避免 Gist 滞后导致重复探测
- `BilibiliService.quick_precheck()`: 第二层 - 快速ID检查
- `BilibiliService.fetch_video_list()` + `get_all_videos_parallel()`: 第三层 - 完整深度检查
- `FrequencyService.adjust_check_frequency()`: 加载历史 → 过滤异常 → 剪枝 → 调用 scheduler
Expand All @@ -409,7 +409,7 @@ python -m unittest discover -s tests # 必须全绿
```
MonitorService.run_monitor()
├── sync_urls_from_gist() # 从 GitHub Gist 同步已备份 URL
├── BilibiliService.check_potential_new_parts() # 第一层:分片预检查
├── BilibiliService.check_potential_new_parts(memory_urls, known_urls) # 第一层:分片预检查(合并本地与Gist状态)
├── BilibiliService.quick_precheck() # 第二层:快速 ID 检查
│ └── 任一层有变化 → 触发完整检查
├── BilibiliService.fetch_video_list() # 第三层:完整深度扫描
Expand All @@ -434,7 +434,7 @@ FrequencyService.adjust_check_frequency()
├── learning.sync_discovered_periods() # 稳定 custom_N 索引
├── learning.learn_dimension_weights() # 学习维度权重(含 custom_N)
├── learning.learn_adaptive_sigmas() # 学习时间容忍度(含 custom_N)
├── scoring.calculate_point_score() # 当前时刻发布概率
├── scoring.calculate_point_score() # 当前时刻发布概率(周期得分与条件间隔得分几何均值)
├── scheduler.scan_future_peak() # 扫描未来 15 天找峰值
└── 映射得分 → 检查间隔 → FrequencyDecision
```
Expand Down
10 changes: 5 additions & 5 deletions docs/code-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ python monitor.py --wgmm-core-only

`BilibiliService` 封装 B站相关业务:

- `check_potential_new_parts()`:第一层,多分片预检查。
- `check_potential_new_parts(memory_urls, known_urls)`:第一层,多分片预检查。合并 `memory_urls`(Gist 状态)与 `known_urls`(本地状态)确定当前最高已知分片编号,再探测下一分片,避免 Gist 滞后时重复探测本地已知分片
- `quick_precheck()`:第二层,获取最新视频 ID,并同时检查 `memory_urls` 与 `known_urls`。
- `fetch_video_list()`:第三层完整扫描入口。
- `get_video_parts()`:获取单个视频的所有分片 URL。
Expand Down Expand Up @@ -253,10 +253,10 @@ python monitor.py --wgmm-core-only

### `wgmm_monitor/wgmm/scoring.py`

- `calculate_point_score()`:计算单个时间点得分。
- `batch_calculate_scores()`:批量计算扫描窗口得分。

得分由正向事件贡献减去负向事件惩罚,并裁剪到 `[0, 1]`
- `calculate_point_score()`:计算单个时间点得分;先求周期得分与条件间隔得分的几何均值,再减去负向惩罚,裁剪到 `[0, 1]`
- `batch_calculate_scores()`:批量计算扫描窗口得分,逻辑与单点版本相同,用 NumPy 广播实现
- `_conditional_interval_scores()`(私有):以加权核密度估计(KDE)对 log 间隔建模,权重 = 历史起始状态与最近发布的周期相似度 × 时间衰减;数据不足时返回全 1。
- `_cyclic_similarity_to_reference()`(私有):计算历史事件与参考时间点的周期维度相似度,供条件间隔得分加权使用

### `wgmm_monitor/wgmm/scheduler.py`

Expand Down
24 changes: 20 additions & 4 deletions docs/wgmm-algorithm.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,9 @@ sigma 越小,匹配越严格;sigma 越大,匹配越宽松。

## 得分计算

`calculate_point_score()` 计算一个时间点的得分。
`calculate_point_score()` 计算一个时间点的得分,最终得分由**周期得分**与**条件间隔得分**共同决定。

### 周期得分(pos_score / neg_score)

对每个事件:

Expand All @@ -181,13 +183,27 @@ dimension_similarity = exp(-distance_sq / (2 * sigma^2))
combined = sum(dimension_weight * dimension_similarity)
```

正向得分提高检查概率,负向得分抑制检查概率:
### 条件间隔得分(interval_score)

`_conditional_interval_scores()` 预测「距上次发布已过去多久」的概率。

1. 计算历史相邻发布事件的间隔序列。
2. 用 `_cyclic_similarity_to_reference()` 计算每段间隔的起始状态与最近一次发布的周期相似度。
3. 权重 = 状态相似度 × 时间衰减(exp(-lambda × 年龄(小时)))。
4. 以 Silverman 规则自适应带宽,对 log 间隔做加权核密度估计(KDE)。
5. 将目标时间点距上次发布的 elapsed 代入 KDE,归一化到 `[0, 1]`。

### 最终得分合成

```text
score = clip(pos_score - resistance_coefficient * neg_score, 0, 1)
interval_score = conditional_interval_score(target, pos_events, ...)
combined_pos = sqrt(pos_score * interval_score) # 几何均值,两者均高才得高分
score = clip(combined_pos - resistance_coefficient * neg_score, 0, 1)
```

`batch_calculate_scores()` 用 NumPy 广播一次性计算未来扫描窗口的多个时间点。
几何均值确保「发布时段匹配」和「间隔时机匹配」两个条件必须同时满足,任一为 0 则最终为 0。历史数据不足(< 4 条正向事件)时 `interval_score` 回退为 1.0,退化为纯周期得分。

`batch_calculate_scores()` 用 NumPy 广播一次性计算未来扫描窗口的多个时间点,逻辑与单点版本相同。

## 未来峰值扫描

Expand Down
Loading