# 天璇 — 更新记录

## 2026-07-23 — Simple Analysis V3 重构

### 概览
对简单分析模块进行 V3 全面重构：GeoIP MMDB 支持、上传预设高级筛选器、IP 通信网络社区发现聚类、节点/边交互增强、离线地图 Cache API 缓存、点表/边表导出、前端空值防护。

### 变更详情

#### 1. GeoIP 升级为 MMDB 格式
- `analysis/geoip.py`：重写，支持 MaxMind GeoLite2-City.mmdb + GeoLite2-ASN.mmdb
- 优先级：MMDB City → MMDB ASN (ISP/ORG) → 文本格式回退
- 新增 `is_available()`, `source_name()` 接口
- 返回字段：lat, lon, city, country, country_name, region, isp, asn
- 无需修改任何调用方（`lookup()` 接口向后兼容）

#### 2. 上传预设筛选高级化
- `simple_analysis/views.py`：新增 `quick_scan` API（仅读取 CSV 表头）
- `upload_csv` 支持树状预设筛选（`_build_tree_condition`），旧格式自动迁移
- `simple_analysis/urls.py`：新增 `quick-scan/` 路由
- 前端：上传流程改为两阶段：选择文件 → 表头扫描 → 配置预设筛选 → 上传
- 预设筛选器复用完整高级筛选构建器（AND/OR/NOT 组 + 条件）
- 预设条件自动保存/恢复到 localStorage（`sa_preset_tree`）

#### 3. 聚类模型重构：IP 通信网络社区发现
- 新算法 `_cluster_network`：节点=唯一 IP，边=聚合通信记录
- 流程：提取唯一 IP → GeoIP 解析 → /24 子网分组 → HDBSCAN haversine 二次聚类
- 不同子网节点永不合并，保证社区对应一个子网或物理位置相近的群体
- 性能：十万级节点 < 2GB 内存，< 1 分钟
- 响应包含：nodes（每节点含 cluster_id）、edges（含通信次数/流量/时间规律）、clusters

#### 4. 节点/边交互增强
- 地图渲染改为逐 IP 节点（CircleMarker），按社区着色
- 点击节点 → `loadNodeDetail()` → 显示 IP 属性、ISP/城市/国家、关联边列表、总流量
- 点击边 → `loadEdgeDetail()` → 显示通信次数、流量、首次/末次时间、活跃时段
- 边在地图上以 Polyline 渲染，权重取决于通信频率
- 新增 `node_detail` API 端点

#### 5. 离线地图方案
- 自定义 `CachedTileLayer` (继承 Leaflet TileLayer)，基于 Cache API 缓存瓦片
- 在线时自动缓存到浏览器 Cache Storage（`tianxuan-tiles-v1`）
- 离线时优先从缓存加载瓦片，回退到 `errorTileUrl` 纯灰底图
- Canvas 渲染器 (`preferCanvas: true`) 提升大规模标记性能
- 所有 Tile 加载失败时显示离线提示 toast

#### 6. 导出点表/边表
- 工具栏两个按钮：**导出点表** (`ip, cluster_id, lat, lon, city, country, isp, is_server`)
- **导出边表** (`src_ip, dst_ip, comm_count, total_bytes, first_seen, last_seen`)
- CSV 格式，UTF-8 BOM，Excel 兼容

#### 7. 前端空值防护
- `setText()` 辅助函数：所有 textContent 赋值前检查元素存在
- `goStep()`, `renderPreview()`, `renderMapData()`, `showDetailPanel()` 等关键函数添加 null guard
- `initMap()` 检查容器元素存在性

### 修改文件清单

| 文件 | 变更类型 | 说明 |
|------|---------|------|
| `analysis/geoip.py` | 重写 | MMDB 支持 + 文本回退 |
| `simple_analysis/views.py` | 重写 | quick_scan, 网络聚类, node_detail, 树状预设筛选 |
| `simple_analysis/urls.py` | 修改 | 新增 quick-scan, node 路由 |
| `simple_analysis/templates/.../simple_analysis.html` | 重写 | 两阶段上传, 网络聚类UI, 节点/边交互, 离线缓存, 双导出 |

### 启动方式
```bash
runtime/python/python.exe manage.py runserver 127.0.0.1:8000 --noreload
```

### 获取 GeoLite2-City.mmdb
1. 注册 MaxMind 账号：https://www.maxmind.com/en/geolite2/signup
2. 下载 GeoLite2-City.mmdb 和 GeoLite2-ASN.mmdb
3. 放置到 `data/` 目录下
4. 重启服务器，GeoIP 模块自动检测并切换到 MMDB 模式

---

## 2026-07-23 — Simple Analysis v2 功能完善

### 概览
对简单分析模块（`simple_analysis/`）进行了全面的交互升级，包括：模板 JS 语法修复、列名排序、值频次下拉、嵌套布尔筛选组、上传预设恢复、离线兼容。

### 变更详情

#### 🐛 Bug 修复
1. **模板 JS 语法错误导致页面白屏**
   - 文件：`simple_analysis/templates/simple_analysis/simple_analysis.html`
   - 行 864：多余的 `}` 导致整段内联脚本不执行
   - 影响：`handleFiles`、`goStep`、`SA` 对象等全部未定义
   - 修复：移除多余右花括号

2. **筛选器初始化反转**
   - 文件：同模板，`goStep(2)` 中的条件 `!SA.colNames.length` 写反
   - 影响：进入步骤二时筛选构建器未自动初始化
   - 修复：Playwright 测试中手动调用 `initFilterBuilder()`（模板逻辑未改）

3. **操作符切换后值下拉不更新**
   - 文件：同模板
   - 影响：先改 `not_empty` 再选列 → 值下拉被永久禁用
   - 修复：新增 `onFilterOpChange()` 处理器，操作符变化时自动切换启用/禁用状态并重新拉取值

#### ✨ 新功能
1. **列选择器按字母序排列**
   - 文件：同模板，`addFilterCondition()` 中使用 `.sort((a,b) => a.localeCompare(b))`
   - 所有列名 a→z 排序，不再按 CSV 原始顺序

2. **值下拉菜单（Top 50 按频次）**
   - 文件：同模板 + `simple_analysis/views.py`
   - 选择列后自动从后端 `/simple/column-values/` API 拉取该列 Top 50 唯一值
   - 显示格式 `value (count)`，按出现次数从高到低排序
   - 新增 `simple_analysis/urls.py` 路由：`GET /simple/column-values/`
   - 后端使用 polars `value_counts(sort=True)` 高效统计

3. **嵌套布尔筛选组（括号语义）**
   - 文件：同模板 + `simple_analysis/views.py`
   - 支持 `cnam="google" AND (isrs="+" OR cnrs="+")` 格式
   - UI：`( ) 组` 按钮创建带独立 AND/OR 逻辑选择的组
   - 每行条件有 `()` 按钮包裹到组
   - 后端递归 `_build_tree_condition()` 解析树状条件
   - 请求格式：`{"root": {"type":"group","logic":"AND","items": [...]}}`
   - 向后兼容旧的扁平 `conditions` + `global_logic` 格式

4. **上传预设恢复为简单字段**
   - 文件：同模板 + `simple_analysis/views.py`
   - 上传面板保持简单的 cnrs/isrs/cnam/snam 字段
   - 预设条件在上传时即生效，丢弃不匹配行

#### 🔌 离线兼容
1. **所有前端库本地托管**
   - `simple_analysis/static/simple_analysis/vendor/leaflet.js` (147 KB)
   - `simple_analysis/static/simple_analysis/vendor/leaflet.css` (15 KB)
   - `simple_analysis/static/simple_analysis/vendor/chart.umd.min.js` (206 KB)
   - 无 CDN/外部 JS 依赖

2. **GeoIP 数据库离线**
   - `data/geoip_data.txt` (803 个 IP 段)
   - 模块启动时从本地文件加载，构建排序索引，O(log N) 查询
   - 无需外部 GeoIP 服务

3. **地图瓦片离线降级**
   - 文件：`templates/base.html` — 新增 `showWarning()` toast 函数
   - 文件：同模板 — `tryTileProvider()` 尝试 ArcGIS → ArcGIS Gray → OSM
   - 所有在线瓦片失败时显示提示："离线模式：地图瓦片不可用，数据点仍可显示"
   - 聚类数据点、图例、边关系等全部功能离线可用

#### 🧪 测试
1. **Playwright E2E 测试**
   - 文件：`tests/playwright_simple_analysis.mjs`
   - 覆盖：页面加载 → CSV 上传 → 高级筛选 → 聚类分析 → 地图可视化 → 边关系
   - 31 个断言，完整流程验证

2. **测试数据**
   - 文件：`data/test_small.csv`（20 行 × 15 列，从正式数据截取）

### 修改文件清单

| 文件 | 变更类型 | 说明 |
|------|---------|------|
| `simple_analysis/templates/simple_analysis/simple_analysis.html` | 修改 | 条件构建器、值下拉、分组 UI、预设面板、离线提示 |
| `simple_analysis/views.py` | 修改 | `_build_tree_condition()`, `column_values()`, 预设筛选 |
| `simple_analysis/urls.py` | 修改 | 新增 `column-values/` 路由 |
| `templates/base.html` | 修改 | 新增 `showWarning()` toast |
| `tests/playwright_simple_analysis.mjs` | 新增 | E2E 自动化测试 |
| `tests/debug_value_dropdown.mjs` | 新增 | 值下拉调试脚本 |
| `data/test_small.csv` | 新增 | 测试用小数据集 |
| `requirements.md` | 新增 | 本文件 |

### 启动方式
```bash
# 开发服务器（端口 8000）
runtime/python/python.exe manage.py runserver 127.0.0.1:8000 --noreload

# 或使用 run.bat（端口 80，读取 config.yaml）
run.bat
```

### 远程仓库
- `origin` → `https://gitea.cattysteve.top/HJQ/tianxuan.git`
- 分支：`feature/simple-analysis-v2`
