feat(solve): monte_carlo 系列两个函数落地(L0 11/12、L1)+ Wilson 区间;修 z_score^2 异或 bug
用户实现: - solve.monte_carlo(L0 第 11 项,S 档):向量化采样→仿真→汇总,8 个指标; 批量大小由实现传 sampler(rng, size=n);结果含 NaN 判失败;std 用 ddof=0。 - solve.monte_carlo_probability(L1,S 档):event(batch)->bool,Wilson score 区间夹 [0,1], confidence 记进 params,小概率改成 message 警告。 Codex 只碰工程侧(算法未动,唯一例外见下): - 真 bug:z_score^2 是异或不是乘方(np.float64 ^ 2 抛 TypeError,被 except 吞成 success=False, 函数实际不可用)-> 改 z_score**2。新增经验第 21 条。 - docstring:补齐 Parameters / Raises,修掉两处跑不通的 Examples,Wald -> Wilson 同步。 - 入口校验按 §4.7 收严 n(bool / np.integer / TypeError 与 ValueError 分工)—— 决策 49 后此类归用户,已记档。 - ruff 九条(I001 / F541 x3 / SIM300 / W291 x2 / W293 x2 等)、格式与文案。 - 测试:摘掉 2 条已实现却仍挂着的 skip(易错点 9),新增 14 条(含 vectorization、 Wilson 对 statsmodels 的对照、NaN / 布尔 / 长度等边界),solve 段共 25 条。 校验:193 收集 / 161 passed / 32 skipped / 0 xfail;ruff 0;pyright 0。 文档:STATUS.md 决策 47-50;docs/dev/personal-pitfalls.md 第 18-21 条;CHANGELOG.md。
This commit is contained in:
1 parent
58f2236703
commit
b0c4b4a413
5 files changed
+619
-61
No files matched your search
@@ -18,10 +18,11 @@
|
||||
- A 档**薄封装**(`rmse`/`mae`/`fit_linear`/`train_test_split`…)算工程,Codex 可直接实现。
|
||||
- 分地盘按**文件**划,不按函数划(同一文件两人各写一半曾导致用户编辑器保存失败)。
|
||||
|
||||
当前 **L0 10/12**:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、
|
||||
`detect_outliers`、`normalize`/`standardize`、`describe`、`correlation_matrix`、`bootstrap_ci`;
|
||||
另**提前落地**了 L1 的 `confidence_interval` / `t_test`。剩余 2 项:`monte_carlo`(用户写)、
|
||||
`save_figure`(Codex 写)。
|
||||
当前 **L0 11/12**:`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、
|
||||
`detect_outliers`、`normalize`/`standardize`、`describe`、`correlation_matrix`、`bootstrap_ci`、
|
||||
`monte_carlo`(**用户写**,2026-10-04);另**提前落地**了 L1 的 `confidence_interval` / `t_test` /
|
||||
`monte_carlo_probability`(**用户写**,2026-10-05)。剩余 1 项:`save_figure`(Codex 写)。
|
||||
`monte_carlo` 的口径已拍板(决策 48),`ci_low`/`ci_high` 的语义与 `monte_carlo_probability` 的 6 条待办见"下一步"。
|
||||
|
||||
## 任务清单
|
||||
|
||||
@@ -234,13 +235,52 @@
|
||||
- [x] **工程收尾(本轮)**:`tests/test_solve.py` 的模块级 `pytestmark = pytest.mark.skip(...)`
|
||||
拆成 **9 条逐条** `@pytest.mark.skip(reason=_NOT_IMPLEMENTED)` —— 模块级 skip 会把整个文件吞掉,
|
||||
正是易错点 9 的根源(决策 26)。收集数不变(177)、跳过数不变(34),只是粒度变细。
|
||||
- [ ] 剩余 2 项:`monte_carlo`(**用户写**)、`save_figure`(Codex 写)
|
||||
- [x] `solve/simulate.py` 的 `monte_carlo`(**L0 第 11 项,S 档,算法由用户本人写**,2026-10-04)
|
||||
- 核心逻辑:向量化采样 → 一次仿真 → 8 个指标;`get_rng` 无条件调用(决策 46 已内化)
|
||||
- 用户第二轮改了三处(见决策 48):批量大小改由实现传 `sampler(rng, size=n)`;
|
||||
结果含 NaN 判失败;`np.std(data, ddof=0)` 写明
|
||||
- **Codex 只碰工程侧,算法一行未动**:
|
||||
① ruff 三条(I001 合并同源 import、F541 空 f-string ×2)
|
||||
② docstring:补 `Parameters`(原文漏 `random_state` / `**kwargs`)与 `Raises`;按新契约改写
|
||||
`sampler` 说明(必须能接住 `size`,且 `**kwargs` 里别再传 `size`);`Notes` 记 NaN 口径;
|
||||
**修掉跑不通的 Examples**(原版 `lambda rng, **k: rng.normal(0, 1)` 返回标量,实测 `success=False`)
|
||||
③ 按 §4.7 收严 `n` 校验:`np.int64` 不再被拒、`n=True` 改抛 `TypeError`、浮点 `n` 归 `TypeError`、
|
||||
`n<=0` 归 `ValueError`
|
||||
④ 格式与文案:`success= True` → `success=True`;失败文案"重采样" → "模拟"(从 `bootstrap_ci` 抄来没改)
|
||||
⑤ **入口统一 `dtype=float`** —— 修掉一个真 bug:布尔结果("事件是否发生",MC 最常见的用法)
|
||||
会让 `np.std` 在 numpy 2 里抛 `boolean subtract ... is not supported`,整个函数判失败;
|
||||
字符串结果也从"ufunc isnan 不支持"变成可读的 `could not convert string to float`
|
||||
- **测试:10 条**(`tests/test_solve.py`)—— 返回长度与 8 个 key、`simulator` 只被调用一次(向量化的可执行版本)、
|
||||
同种子可复现、省略种子时跟随 `set_seed`、E[X²]=1(n=2e5)、docstring Example 真能跑、`np.int64` 的 n、
|
||||
布尔结果按 0/1 统计、NaN 结果判失败、5 个非法入参
|
||||
- 行为探针实测:`simulator` 调用次数 = **1**(真向量化);20/20 次区间覆盖 0
|
||||
- [x] `solve/simulate.py` 的 `monte_carlo_probability`(**L1,S 档,算法由用户本人写**,2026-10-05)
|
||||
- 核心:向量化 `event(batch) -> bool 数组` → `p = mean(data)` → **Wilson score 区间**并夹到 `[0, 1]`
|
||||
(比例量纲,沿用决策 42 的口径——同类问题没重犯)
|
||||
- 用户第二轮(2026-10-05)按 6 条待办改掉 5 条:Wilson 替 Wald、`callable` 入口校验、
|
||||
`confidence` 记进 `params`、`float()` 统一 ci 类型、小概率不再判失败(改成 message 里的警告)——见决策 50
|
||||
- **Codex 第三轮只碰工程侧**:① `z_score^2` → `z_score**2` —— **真 bug**:`^` 是异或,
|
||||
numpy 浮点直接抛 `TypeError`,被 `except` 吞成 `success=False`,6 条测试全红才暴露(易错点 21);
|
||||
② message 无警告时的尾逗号;③ `{"confidence":confidence}` 的空格;④ docstring 的 "Wald" → "Wilson";
|
||||
⑤ 新增 1 条对照测试:区间必须等于 `statsmodels.proportion_confint(method="wilson")`
|
||||
- 行为探针实测:p≈0.5 正确(0.49991 @ n=2e5)、`confidence` 变大区间变宽、
|
||||
同种子可复现、省略种子跟随 `set_seed`、`np.int64` 的 n 可收、`confidence=1.5` 抛 `ValueError`
|
||||
- **Codex 只碰工程侧,算法一行未动**:
|
||||
① ruff 六条(SIM300 Yoda、W291 ×2、W293 ×2、F541)
|
||||
② docstring:补 `Parameters`(原文只有 Returns/Notes/Examples)与 `Raises`,
|
||||
**修掉跑不通的 Examples**(原 `lambda rng, **k: rng.normal(0, 1)` 返回标量,实测 `success=False`),
|
||||
并把"Wald 区间 + 夹到 [0,1]"与"`p <= 1e-3` 判失败"两条**现状**如实写进 Returns / Notes
|
||||
③ 格式:`metrics` 字典缩进、`{"n":n, ...}` 空格、行尾空白
|
||||
④ 文案:NaN 提示里的 "simulator" 改成 "event"(从 `monte_carlo` 抄来没改)
|
||||
- **测试:摘掉 2 条 skip**(`test_monte_carlo_probability_of_standard_normal`、`test_monte_carlo_is_reproducible`
|
||||
—— 实现写完还挂着 skip,正是易错点 9)并新增 5 条,`solve` 段共 24 条
|
||||
- [ ] 剩余 1 项:`save_figure`(A 档 = 工程,**Codex 直接写**)
|
||||
|
||||
### 验证结果
|
||||
|
||||
| 检查 | venv | conda `mcm-toolkit` |
|
||||
|---|---|---|
|
||||
| `pytest` | 见下(单列) | **143 passed, 34 skipped(0 xfail)** |
|
||||
| `pytest` | 见下(单列) | **161 passed, 32 skipped(0 xfail)** |
|
||||
| `import mcm_toolkit` | 0.0.1 / 16 项 | 0.0.1 / 16 项 |
|
||||
| `ruff check` | All checks passed | **All checks passed**(0 error) |
|
||||
| `scripts/doctor.ps1` | 跑通 | — |
|
||||
@@ -250,13 +290,14 @@
|
||||
(修配置之前是 17 errors,全是"无法解析导入 numpy / pandas",见决策 29 与
|
||||
`docs/04-environment.md` 坑 7)
|
||||
- base 亦已复核:numpy 2.4.6 / scipy 1.18.1 / pandas 3.0.6(与两套环境一致)
|
||||
- **177 条用例:143 passed / 34 skipped / 0 xfail**。收集数按文件:`test_data.py` 52、`test_api_surface.py` 20、
|
||||
`test_core.py` 23、`test_solve.py` 9、`test_viz.py` 8、`test_decision.py` 7、`test_stats.py` 53、
|
||||
- **193 条用例:161 passed / 32 skipped / 0 xfail**。收集数按文件:`test_data.py` 52、`test_api_surface.py` 20、
|
||||
`test_core.py` 23、`test_solve.py` 25(+16,含摘掉 2 条 skip)、`test_viz.py` 8、`test_decision.py` 7、`test_stats.py` 53、
|
||||
`test_models.py` 5
|
||||
- **`xfail` 再次清零**:`correlation_matrix` 的契约冲突已按方案 A 解决(决策 41),那条用例转正
|
||||
- 上一轮那 7 条 xfail 已全部转正,决策 30 那套信号(XPASS → 摘 mark)跑完过一个完整闭环
|
||||
- `examples/01_clean_and_describe.py` 实测跑通(exit 0);`00_quickstart.py` 仍等 `stats` / `viz`
|
||||
- 34 条 skip 是等实现的算法(`train_test_split`、`monte_carlo`、`anova`、`fit_distribution` 等);
|
||||
- 32 条 skip 是等实现的算法(`train_test_split`、`monte_carlo_integral`、`anova`、`fit_distribution` 等);
|
||||
`monte_carlo_probability` 已落地,摘掉 2 条 skip(本轮净 +3 条用例);
|
||||
`bootstrap_ci` 已落地摘掉 3 条 skip(本轮新增 6 条用例,净 +3);`test_solve.py` 9 条按决策 26 改为逐条 skip;
|
||||
`TestValidate` 的 4 条仍挂着 —— `check_*` 还是 `NotImplementedError`,但它们实现时应当直接复用 `as_array`
|
||||
- **本轮回归(已修)**:`transform.py` 上一版有 3 处真 bug —— 修前 **7 failed / 75 passed**,
|
||||
@@ -271,7 +312,7 @@
|
||||
只差 Python 补丁号(3.13.9 vs 3.13.15)。详见 `docs/04-environment.md` §5。
|
||||
- [x] ~~Gitea 远程尚未配置~~ → **已配置并推送完成**,见上面 D 节与决策 19/20。
|
||||
note:仓库当前是 **Public**(匿名 `git ls-remote` 可读),要改私有去 Gitea 仓库设置。
|
||||
- [ ] L0 完成 **10/12**(`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、
|
||||
- [ ] L0 完成 **11/12**(`Result`、`get_rng`/`set_seed`、`load_table`/`save_table`、`clean_missing`、
|
||||
`detect_outliers`、`normalize`、`standardize`、`describe`、`correlation_matrix`、`bootstrap_ci`),
|
||||
另 L1 的 `confidence_interval` / `t_test` 已提前落地;
|
||||
**只剩 `monte_carlo`(用户写)与 `save_figure`(Codex 写)**,详情见 E 节与"下一步"
|
||||
@@ -353,37 +394,89 @@ docstring 与 `test_describe_reports_expected_keys` 早就写死了这个方向
|
||||
工程侧(入口/校验、`tests/`、lint、文档、卡片、`.gitignore`)反过来由 Codex 直接落盘,用户只读;
|
||||
分地盘按**文件**划,不按函数划。决策 39 的"语义由用户拍板"继续有效。
|
||||
|
||||
| 48 | `monte_carlo` 的四个口径由**用户拍板**:实现传 `size=n`;结果含 NaN 判失败;`std` 用 `ddof=0`;出错仍统一返回 `success=False` |
|
||||
用户 2026-10-04 直接改在代码里。① 批量大小由实现传(`sampler(rng, size=n, **kwargs)`):调用方少写一次,
|
||||
代价是**每个 sampler 都必须能接住 `size`**,且 `**kwargs` 里不能再传 `size`(会重复传参)—— 已写进 docstring。
|
||||
② 结果里只要出现 NaN 就 `success=False`:MC 的 NaN 多来自除零 / 模型发散,静默统计只会得到一列 nan 指标。
|
||||
③ `np.std` 默认 `ddof=0`,现在显式写明(与 `describe` 那条线的 `ddof=1` 不一致,属已知差异)。
|
||||
④ 计算期异常仍全部包成 `success=False`(保留 `try/except Exception`);代价是自己写错代码时没有 traceback。
|
||||
配套 Codex 代改:入口 `np.asarray(..., dtype=float)` —— 布尔结果此前会让 `np.std` 抛错(真 bug,已修)。
|
||||
**唯一未定**:`ci_low`/`ci_high` 保持"结果的分位数"(现状)还是改成"均值的置信区间"。
|
||||
|
||||
| 49 | **这类改动归用户本人**:入口没收紧、少考虑情况、不符合简单原则 —— 用户 2026-10-04 明确"交给我改,不要帮我改" |
|
||||
用户原话:"以后像这种入口没收紧、少考虑情况、不符合简单原则的情况交给我改,不要帮我改,把这几条经验写入经验文档"。
|
||||
这是决策 47 的**收窄**:47 把"入口 / 校验重构"划给了 Codex,49 把它收回 —— 凡是**带判断、取舍、设计成分**的,
|
||||
一律由用户写,Codex 只报告 + 列选项(与决策 39 同一逻辑)。判据一句话:**这处改动要不要"想一下才对"** ——
|
||||
要,就留给用户;不要(ruff、格式化、docstring 缺节、`tests/`、`STATUS` / `CHANGELOG`、函数卡片)才由 Codex 顺手补。
|
||||
本轮实例(本该留给用户,Codex 已代改、用户可回退重写):`monte_carlo` 的 `n` 按 §4.7 收严;入口 `np.asarray(..., dtype=float)`。
|
||||
经验已写入 `docs/dev/personal-pitfalls.md` **第 18 / 19 / 20 条**,并把第 1 条(类型判断只写 `int`)**复发 +1**。
|
||||
|
||||
| 50 | `monte_carlo_probability` 的区间由 **Wald 改 Wilson score**;小概率不再判失败(改成 message 警告);`confidence` 记进 `params` |
|
||||
用户 2026-10-05 按"6 条待办"自行改定。Wilson 在 p 贴近 0 / 1 时不会退化成零宽:实测 p=1、n=1000 从 `(1.0, 1.0)`
|
||||
变成 `(0.99617, 1.0)`;p=0 从"判失败"变成 `(0.0, 0.00383)`(原 Wald 是 0)。区间口径与
|
||||
`statsmodels.proportion_confint(method="wilson")` 对齐,已加成对照测试。
|
||||
**遗留两条**:`event: Callable[..., bool]` 注解与"吃整批、返回数组"不符;警告文案把 p≈1 也说成"小概率事件"。
|
||||
另:`z_score^2` 那处是 Codex 修的真 bug(`^` 是异或,见易错点 21)——按决策 31 属纯 bug,未改任何语义。
|
||||
|
||||
## 下一步
|
||||
|
||||
### ⭐ 明天从这里开始(第一个要写的文件)
|
||||
### ⭐ 当前进度与待办(2026-10-05)
|
||||
|
||||
**文件:`src/mcm_toolkit/solve/simulate.py` → 写 `monte_carlo`(L0 第 11/12 项,S 档)。**
|
||||
同一文件里 `monte_carlo_probability` / `monte_carlo_integral` / `random_walk` 先别动 —— 一次一个函数。
|
||||
**本轮:`monte_carlo_probability` 已由用户写完并改到第二版**(L1,S 档)。Codex 两轮都只做工程侧,
|
||||
**算法逻辑一行未动**(唯一例外是一个纯 typo:`z_score^2` → `z_score**2`,见易错点 21 / 决策 50)。
|
||||
|
||||
**① 参考契约(docstring 里已定,签名别改)**
|
||||
- `monte_carlo(sampler, simulator, n=10000, random_state=None, **kwargs) -> Result`
|
||||
- `sampler(rng, **kwargs) -> 参数`:一次抽**一整批**参数(可以是标量/数组/元组,随便)
|
||||
- `simulator(params) -> float`:单次仿真结果
|
||||
- 返回 `Result`:`data` = `n` 个结果组成的 `np.ndarray`;
|
||||
`metrics = {"mean", "std", "median", "p05", "p95", "ci_low", "ci_high"}`
|
||||
**接下来三件事**
|
||||
1. `monte_carlo_probability` 还剩 **2 条待办**(下面一节)——都是你的活。
|
||||
2. `ci_low` / `ci_high` 的语义确认一句(也是你的活),确认后 Codex 补 `docs/algorithms/monte_carlo.md` 卡片。
|
||||
3. L0 最后一项 `save_figure`(A 档 = 工程,`viz/export.py`)—— **Codex 直接实现**,你只读。
|
||||
|
||||
**② 必须向量化**:`sampler` 一次抽 `n` 份(如 `rng.normal(0, 1, size=n)`),`simulator` 对整个参数数组算一次,
|
||||
**绝不在 `simulator` 里写 Python 循环**。10^4 次循环还能忍,10^6 次直接放弃 —— 这是"能用"和"不能用"的分界线。
|
||||
**你下一个要写的(S 档,仍在 `solve/simulate.py`)**:`monte_carlo_integral` → `random_walk`。
|
||||
`monte_carlo_integral` 其实就是 `monte_carlo` 的特例:自己造 `n×d` 的均匀样本 → 逐点求值 →
|
||||
`mean(f) × 体积`,正好把那套"造批 → 一次算 → 汇总"的写法复用第二次。
|
||||
|
||||
**③ 随机数无条件走 `core.get_rng`**(决策 46 的教训,别重演 bootstrap 那个坑):
|
||||
`rng = get_rng(random_state)`,不要自己另开 `np.random.default_rng()`,否则 `set_seed` 管不到它、区间不可复现。
|
||||
### ✅ monte_carlo_probability 的 6 条待办(2026-10-05 已办 5 条)
|
||||
|
||||
**④ 先写一条会失败的测试**(roadmap §3.2 的纪律)。`tests/test_solve.py` 现在**没有**直接测 `monte_carlo` 的用例
|
||||
(`test_monte_carlo_is_reproducible` 名字像,实际调的是 `monte_carlo_probability`)。建议三条:
|
||||
- 固定种子两次结果完全相同(可复现)
|
||||
- `sampler` 恒返回常数 → `data` 全等于该常数、`std == 0`(常量边界)
|
||||
- `n` 增大时 `mean` 的波动按 `O(1/√n)` 下降(可留成大样本慢测或标 `slow`)
|
||||
- ~~① `p <= 1e-3` 判 `success=False`~~ → 改成 `warning_message`,仍返回区间(实测 p=0 → `(0.0, 0.00383)`)。
|
||||
- ~~② Wald 在 p 靠近 1 时退化~~ → 换 **Wilson**(p=1、n=1000:`(1.0, 1.0)` → `(0.99617, 1.0)`)。
|
||||
- ~~③ `call_params` 没记 `confidence`~~ → 已加。
|
||||
- ~~④ `event` / `sampler` 不可调用被吞成 `success=False`~~ → 已加 `callable` 校验,现在抛 `TypeError`。
|
||||
- ~~⑤ `metrics` 混 `np.float64`~~ → `float()` 统一(实测 4 个 key 全是 `float`)。
|
||||
- ~~⑥ `n: int`~~ → 已改 `int | np.integer`。
|
||||
|
||||
**⑤ 还欠一张 S 档卡片**:`docs/algorithms/monte_carlo.md`(模板 `docs/dev/function-card-template.md`,
|
||||
照抄 `docs/algorithms/bootstrap_ci.md` 的骨架),写完把 `docs/algorithms/README.md` 的进度表打勾。
|
||||
**还剩 2 条(你的活)**
|
||||
1. `event: Callable[..., bool]` 的注解与行为不符:实际必须吃**整批**参数、返回长度 `n` 的数组。
|
||||
2. 警告文案把 **p ≈ 1** 也说成"小概率事件 P < 1e-3 不建议使用"——那一条建议对 p→1 是错的
|
||||
(临界情形该说的是"改用 1−p 或换方法")。条件是你写的 `p_value <= 1e-3 or p_value >= 1 - 1e-3`,文案只覆盖了前一半。
|
||||
|
||||
**⑥ 等你拍板的一个口径(Codex 不代选,决策 39/47)**:`ci_low` / `ci_high` 用**分位数法**
|
||||
(`np.percentile(data, [2.5, 97.5])`)还是**正态近似**(`mean ± 1.96·std/√n`)?两个都能用,选一个我记进卡片。
|
||||
### ⏳ monte_carlo 只剩 1 个口径要你确认
|
||||
|
||||
**`ci_low` / `ci_high` 是不是就按"结果的分位数"定?**
|
||||
|
||||
现状 `np.percentile(data, [2.5, 97.5])`。实测 n=2e5、E[X²]=1 给出 `[0.001, 5.0455]` ——
|
||||
这是**模拟结果的分散度**(预测区间),与 `p05` / `p95` 同类,只是分位点不同。
|
||||
而"均值估计的置信区间"(MC 的统计误差)是 `mean ± 1.96·std/√n` = `[0.9962, 1.0086]`,
|
||||
两者差两个数量级,论文话术也完全不同:
|
||||
|
||||
- A(现状):写"95% 的模拟结果落在 [a, b]"(描述风险分布)
|
||||
- B:改成均值 CI → 写"估计值 1.0024 ± 0.0043(95% CI)"(证明 n 够大)
|
||||
- C:两者都要 → 指标要改名(如 `p025`/`p975` + `mean_ci_low`/`mean_ci_high`)
|
||||
|
||||
注意:`monte_carlo_probability` 里的 `ci_*` 已经是**估计量的 CI**(Wald 区间),所以这两个函数的 `ci_*`
|
||||
目前**含义不同**——这正是要给它们定口径的原因之一。
|
||||
|
||||
### ✅ 已拍板(决策 48)
|
||||
|
||||
- 批量大小由实现传 `size=n`;`sampler` 必须能接住 `size`,`**kwargs` 里不要再传 `size`。
|
||||
- 结果含 NaN → `success=False`(不落 metrics)。
|
||||
- `std` 用 `ddof=0`。
|
||||
- 出错仍统一返回 `success=False`(`try/except Exception` 保留)。
|
||||
|
||||
### ⏳ 另外两个小口径(你说了算)
|
||||
|
||||
- **`random_state` 的注解**:经验文档第 3 条写的标准是 `int | np.integer | np.random.Generator | None`,
|
||||
但 `stats/bootstrap.py`、`solve/simulate.py`、`stats/inference.py` 现在都写 `int | None`(只有 `core/random.py` 是全的)——
|
||||
要不要全项目对齐?(运行期 `get_rng` 收 `np.int64`,所以是注解与行为不一致,不是功能 bug)
|
||||
- **`std` 的 `ddof`**:`monte_carlo` 用 `ddof=0`,`describe` 那条线用 `ddof=1`。要不要统一?
|
||||
|
||||
### 其余待办
|
||||
|
||||
|
||||
Reference in new issue
Block a user