Compare commits
1 Commits
master
...
70c13ef622
| Author | SHA1 | Date | |
|---|---|---|---|
| 70c13ef622 |
37
ETL/L1A.py
37
ETL/L1A.py
@@ -1,20 +1,7 @@
|
|||||||
"""
|
|
||||||
L1A Data Ingestion Script
|
|
||||||
|
|
||||||
This script reads raw JSON files from the 'output_arena' directory and ingests them into the SQLite database.
|
|
||||||
It supports incremental updates by default, skipping files that have already been processed.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
python ETL/L1A.py # Standard incremental run
|
|
||||||
python ETL/L1A.py --force # Force re-process all files (overwrite existing data)
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import glob
|
import glob
|
||||||
import argparse # Added
|
|
||||||
|
|
||||||
# Paths
|
# Paths
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
@@ -39,33 +26,17 @@ def init_db():
|
|||||||
return conn
|
return conn
|
||||||
|
|
||||||
def process_files():
|
def process_files():
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('--force', action='store_true', help='Force reprocessing of all files')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
conn = init_db()
|
conn = init_db()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
|
|
||||||
# Get existing match_ids to skip
|
|
||||||
existing_ids = set()
|
|
||||||
if not args.force:
|
|
||||||
try:
|
|
||||||
cursor.execute("SELECT match_id FROM raw_iframe_network")
|
|
||||||
existing_ids = set(row[0] for row in cursor.fetchall())
|
|
||||||
print(f"Found {len(existing_ids)} existing matches in DB. Incremental mode active.")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error checking existing data: {e}")
|
|
||||||
|
|
||||||
# Pattern to match all iframe_network.json files
|
# Pattern to match all iframe_network.json files
|
||||||
# output_arena/*/iframe_network.json
|
# output_arena/*/iframe_network.json
|
||||||
pattern = os.path.join(OUTPUT_ARENA_DIR, '*', 'iframe_network.json')
|
pattern = os.path.join(OUTPUT_ARENA_DIR, '*', 'iframe_network.json')
|
||||||
files = glob.glob(pattern)
|
files = glob.glob(pattern)
|
||||||
|
|
||||||
print(f"Found {len(files)} files in directory.")
|
print(f"Found {len(files)} files to process.")
|
||||||
|
|
||||||
count = 0
|
count = 0
|
||||||
skipped = 0
|
|
||||||
|
|
||||||
for file_path in files:
|
for file_path in files:
|
||||||
try:
|
try:
|
||||||
# Extract match_id from directory name
|
# Extract match_id from directory name
|
||||||
@@ -73,10 +44,6 @@ def process_files():
|
|||||||
parent_dir = os.path.dirname(file_path)
|
parent_dir = os.path.dirname(file_path)
|
||||||
match_id = os.path.basename(parent_dir)
|
match_id = os.path.basename(parent_dir)
|
||||||
|
|
||||||
if match_id in existing_ids:
|
|
||||||
skipped += 1
|
|
||||||
continue
|
|
||||||
|
|
||||||
with open(file_path, 'r', encoding='utf-8') as f:
|
with open(file_path, 'r', encoding='utf-8') as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
@@ -96,7 +63,7 @@ def process_files():
|
|||||||
|
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
print(f"Finished. Processed: {count}, Skipped: {skipped}.")
|
print(f"Finished processing {count} files.")
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
process_files()
|
process_files()
|
||||||
@@ -1,23 +1,7 @@
|
|||||||
# ETL Pipeline Documentation
|
L1A output_arena/iframe_network.json -> L1A.sqlite(Primary Key: match_id)
|
||||||
|
|
||||||
## 1. L1A (Raw Data Ingestion)
|
|
||||||
**Status**: ✅ Supports Incremental Update
|
|
||||||
|
|
||||||
This script ingests raw JSON files from `output_arena/` into `database/L1A/L1A.sqlite`.
|
|
||||||
|
|
||||||
### Usage
|
|
||||||
```bash
|
|
||||||
# Standard Run (Incremental)
|
|
||||||
# Only processes new files that are not yet in the database.
|
|
||||||
python ETL/L1A.py
|
|
||||||
|
|
||||||
# Force Refresh
|
|
||||||
# Reprocesses ALL files, overwriting existing records.
|
|
||||||
python ETL/L1A.py --force
|
|
||||||
```
|
|
||||||
|
|
||||||
L1B demoparser2 -> L1B.sqlite
|
L1B demoparser2 -> L1B.sqlite
|
||||||
|
|
||||||
L2 L1A.sqlite (+L1b.sqlite) -> L2.sqlite
|
L2 L1A.sqlite (+L1b.sqlite) -> L2.sqlite
|
||||||
|
|
||||||
L3 Deep Dive
|
L3 Deep Dive.
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
import sqlite3
|
|
||||||
import os
|
|
||||||
|
|
||||||
# 路径指向正式数据库
|
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
||||||
DB_PATH = os.path.join(BASE_DIR, 'database', 'L1A', 'L1A.sqlite')
|
|
||||||
|
|
||||||
def clean_db():
|
|
||||||
if not os.path.exists(DB_PATH):
|
|
||||||
print(f"Database not found at {DB_PATH}")
|
|
||||||
return
|
|
||||||
|
|
||||||
print(f"Connecting to production DB: {DB_PATH}")
|
|
||||||
conn = sqlite3.connect(DB_PATH)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# 查找脏数据 (假设模拟数据的 match_id 是 match_001, match_002, match_003)
|
|
||||||
dirty_ids = ['match_001', 'match_002', 'match_003']
|
|
||||||
|
|
||||||
# 也可以用 LIKE 'match_%' 如果您想删得更彻底,但要小心误删
|
|
||||||
# 这里我们精准删除
|
|
||||||
|
|
||||||
deleted_count = 0
|
|
||||||
for mid in dirty_ids:
|
|
||||||
cursor.execute("DELETE FROM raw_iframe_network WHERE match_id = ?", (mid,))
|
|
||||||
if cursor.rowcount > 0:
|
|
||||||
print(f"Deleted dirty record: {mid}")
|
|
||||||
deleted_count += 1
|
|
||||||
|
|
||||||
conn.commit()
|
|
||||||
conn.close()
|
|
||||||
|
|
||||||
if deleted_count > 0:
|
|
||||||
print(f"Cleanup complete. Removed {deleted_count} dirty records.")
|
|
||||||
else:
|
|
||||||
print("Cleanup complete. No dirty records found.")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
clean_db()
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
import os
|
|
||||||
import json
|
|
||||||
|
|
||||||
# 定义路径
|
|
||||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(CURRENT_DIR))
|
|
||||||
OUTPUT_ARENA_DIR = os.path.join(PROJECT_ROOT, 'output_arena')
|
|
||||||
|
|
||||||
def create_mock_data():
|
|
||||||
if not os.path.exists(OUTPUT_ARENA_DIR):
|
|
||||||
os.makedirs(OUTPUT_ARENA_DIR)
|
|
||||||
print(f"Created directory: {OUTPUT_ARENA_DIR}")
|
|
||||||
|
|
||||||
# 创建 3 个模拟比赛数据
|
|
||||||
mock_matches = ['match_001', 'match_002', 'match_003']
|
|
||||||
|
|
||||||
for match_id in mock_matches:
|
|
||||||
match_dir = os.path.join(OUTPUT_ARENA_DIR, match_id)
|
|
||||||
if not os.path.exists(match_dir):
|
|
||||||
os.makedirs(match_dir)
|
|
||||||
|
|
||||||
file_path = os.path.join(match_dir, 'iframe_network.json')
|
|
||||||
if not os.path.exists(file_path):
|
|
||||||
mock_content = {
|
|
||||||
"match_id": match_id,
|
|
||||||
"data": "This is mock data for testing."
|
|
||||||
}
|
|
||||||
with open(file_path, 'w', encoding='utf-8') as f:
|
|
||||||
json.dump(mock_content, f)
|
|
||||||
print(f"Created mock file: {file_path}")
|
|
||||||
else:
|
|
||||||
print(f"File already exists: {file_path}")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
create_mock_data()
|
|
||||||
@@ -1,76 +0,0 @@
|
|||||||
import os
|
|
||||||
import sqlite3
|
|
||||||
import subprocess
|
|
||||||
import glob
|
|
||||||
|
|
||||||
# 配置路径
|
|
||||||
# 当前脚本位于 ETL/verify/ 目录下,需要向上两级找到项目根目录
|
|
||||||
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(CURRENT_DIR))
|
|
||||||
|
|
||||||
L1_SCRIPT = os.path.join(PROJECT_ROOT, 'ETL', 'L1A.py')
|
|
||||||
DB_PATH = os.path.join(PROJECT_ROOT, 'database', 'L1A', 'L1A.sqlite')
|
|
||||||
OUTPUT_ARENA_DIR = os.path.join(PROJECT_ROOT, 'output_arena')
|
|
||||||
|
|
||||||
def get_db_count():
|
|
||||||
"""获取数据库中的记录数"""
|
|
||||||
if not os.path.exists(DB_PATH):
|
|
||||||
return 0
|
|
||||||
try:
|
|
||||||
conn = sqlite3.connect(DB_PATH)
|
|
||||||
cursor = conn.cursor()
|
|
||||||
cursor.execute("SELECT COUNT(*) FROM raw_iframe_network")
|
|
||||||
count = cursor.fetchone()[0]
|
|
||||||
conn.close()
|
|
||||||
return count
|
|
||||||
except Exception:
|
|
||||||
return 0
|
|
||||||
|
|
||||||
def get_file_count():
|
|
||||||
"""获取源文件总数"""
|
|
||||||
pattern = os.path.join(OUTPUT_ARENA_DIR, '*', 'iframe_network.json')
|
|
||||||
files = glob.glob(pattern)
|
|
||||||
return len(files)
|
|
||||||
|
|
||||||
def run_l1_script():
|
|
||||||
"""运行 L1 脚本并返回输出"""
|
|
||||||
# 必须在项目根目录下运行,或者正确处理 Python 路径
|
|
||||||
# 这里我们使用绝对路径调用脚本
|
|
||||||
result = subprocess.run(['python', L1_SCRIPT], capture_output=True, text=True)
|
|
||||||
return result.stdout
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("=== 开始 L1 增量逻辑测试 ===")
|
|
||||||
print(f"项目根目录: {PROJECT_ROOT}")
|
|
||||||
|
|
||||||
# 1. 检查环境
|
|
||||||
total_files = get_file_count()
|
|
||||||
initial_db_count = get_db_count()
|
|
||||||
print(f"[环境] 源文件总数: {total_files}")
|
|
||||||
print(f"[环境] 数据库当前记录数: {initial_db_count}")
|
|
||||||
|
|
||||||
# 2. 运行脚本 (第一次)
|
|
||||||
print("\n--- 运行 L1A.py (Run 1) ---")
|
|
||||||
output1 = run_l1_script()
|
|
||||||
print(output1.strip())
|
|
||||||
|
|
||||||
mid_db_count = get_db_count()
|
|
||||||
print(f"[状态] 运行后数据库记录数: {mid_db_count}")
|
|
||||||
|
|
||||||
if mid_db_count < total_files:
|
|
||||||
print("警告: 数据库记录数少于文件数,可能部分文件处理失败或尚未完成。")
|
|
||||||
|
|
||||||
# 3. 运行脚本 (第二次 - 验证增量)
|
|
||||||
print("\n--- 再次运行 L1A.py (Run 2 - 验证增量) ---")
|
|
||||||
output2 = run_l1_script()
|
|
||||||
print(output2.strip())
|
|
||||||
|
|
||||||
# 4. 验证结果
|
|
||||||
expected_msg = f"Skipped: {total_files}"
|
|
||||||
if expected_msg in output2:
|
|
||||||
print("\n✅ 测试通过! 第二次运行跳过了所有文件,增量逻辑生效。")
|
|
||||||
else:
|
|
||||||
print(f"\n❌ 测试未通过。预期输出应包含 '{expected_msg}'")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
67
WebRDD.md
67
WebRDD.md
@@ -3,7 +3,7 @@
|
|||||||
## 1. 项目概述 (Overview)
|
## 1. 项目概述 (Overview)
|
||||||
|
|
||||||
### 1.1 项目背景
|
### 1.1 项目背景
|
||||||
YRTV 是一个面向 CS2 战队数据洞察与战术研判的 Web 平台。该平台基于现有的 `ETL` 数据管线与 `L2_Main.sqlite` 核心数据库,旨在通过 Web 界面提供可视化的数据查询、战队管理、战术模拟及深度分析功能。
|
YRTV 是一个面向 CS2 战队数据洞察与战术研判的 Web 平台,旨在通过 Web 界面提供可视化的数据查询、战队管理、战术模拟及深度分析功能。
|
||||||
|
|
||||||
### 1.2 核心目标
|
### 1.2 核心目标
|
||||||
* **数据可视化**: 将复杂的 SQLite 比赛数据转化为易读的图表、雷达图和趋势线。
|
* **数据可视化**: 将复杂的 SQLite 比赛数据转化为易读的图表、雷达图和趋势线。
|
||||||
@@ -18,7 +18,7 @@ YRTV 是一个面向 CS2 战队数据洞察与战术研判的 Web 平台。该
|
|||||||
* **L3**: SQLite (`database/L3/L3_Features.sqlite`) - 高级衍生特征 (Read-Only for Web)
|
* **L3**: SQLite (`database/L3/L3_Features.sqlite`) - 高级衍生特征 (Read-Only for Web)
|
||||||
* **Web**: SQLite (`database/Web/Web_App.sqlite`) - [新增] 业务数据 (用户、评论、阵容配置、策略板存档)
|
* **Web**: SQLite (`database/Web/Web_App.sqlite`) - [新增] 业务数据 (用户、评论、阵容配置、策略板存档)
|
||||||
* **模板引擎**: Jinja2 (服务端渲染)
|
* **模板引擎**: Jinja2 (服务端渲染)
|
||||||
* **前端样式**: Tailwind CSS (CDN 引入,快速开发)
|
* **前端样式**: Tailwind CSS (CDN 引入,快速开发) + PC-First 响应式设计 (适配手机、平板与桌面端),主题色紫色,可切换黑白模式。
|
||||||
* **前端交互**:
|
* **前端交互**:
|
||||||
* **图表**: Chart.js / ECharts (雷达图、趋势图)
|
* **图表**: Chart.js / ECharts (雷达图、趋势图)
|
||||||
* **交互**: Alpine.js 或原生 JS (处理模态框、异步请求)
|
* **交互**: Alpine.js 或原生 JS (处理模态框、异步请求)
|
||||||
@@ -36,16 +36,18 @@ yrtv/
|
|||||||
│ ├── app.py # Flask 应用入口
|
│ ├── app.py # Flask 应用入口
|
||||||
│ ├── config.py # 配置文件
|
│ ├── config.py # 配置文件
|
||||||
│ ├── routes/ # 路由模块
|
│ ├── routes/ # 路由模块
|
||||||
│ │ ├── main.py # 首页与通用
|
│ │ ├── main.py # 首页与通用 (Home)
|
||||||
│ │ ├── players.py # 玩家模块
|
│ │ ├── players.py # 玩家模块 (List, Detail, Compare)
|
||||||
│ │ ├── teams.py # 战队模块
|
│ │ ├── teams.py # 战队模块 (Lineup, Stats)
|
||||||
│ │ ├── matches.py # 比赛模块
|
│ │ ├── matches.py # 比赛模块 (List, Detail, Demo)
|
||||||
│ │ ├── tactics.py # 战术与分析模块
|
│ │ ├── tactics.py # 战术模块 (Lineup Builder, Map, Nade)
|
||||||
│ │ └── admin.py # 管理后台
|
│ │ ├── wiki.py # 知识库模块 (Wiki, Docs)
|
||||||
│ ├── services/ # 业务逻辑层 (数据计算)
|
│ │ └── admin.py # 管理后台 (ETL Trigger, User Mgmt)
|
||||||
│ │ ├── stats_service.py # 核心指标计算
|
│ ├── services/ # 业务逻辑层 (连接 L2/L3/Web DB)
|
||||||
│ │ ├── feature_store.py # L3 特征读取与计算
|
│ │ ├── stats_service.py # 基础数据查询 (L2)
|
||||||
│ │ └── etl_trigger.py # ETL 调度
|
│ │ ├── feature_service.py # 高级特征查询 (L3)
|
||||||
|
│ │ ├── wiki_service.py # 知识库管理
|
||||||
|
│ │ └── user_service.py # 用户与评论管理
|
||||||
│ ├── static/ # 静态资源
|
│ ├── static/ # 静态资源
|
||||||
│ │ ├── css/
|
│ │ ├── css/
|
||||||
│ │ ├── js/
|
│ │ ├── js/
|
||||||
@@ -53,25 +55,32 @@ yrtv/
|
|||||||
│ └── templates/ # Jinja2 模板
|
│ └── templates/ # Jinja2 模板
|
||||||
│ ├── base.html
|
│ ├── base.html
|
||||||
│ ├── components/
|
│ ├── components/
|
||||||
│ └── ... (各页面模板)
|
│ ├── home/
|
||||||
|
│ ├── players/
|
||||||
|
│ ├── teams/
|
||||||
|
│ ├── matches/
|
||||||
|
│ ├── tactics/
|
||||||
|
│ ├── wiki/
|
||||||
|
│ └── admin/
|
||||||
├── database/ # 数据存储
|
├── database/ # 数据存储
|
||||||
│ ├── L1A/ # 原始爬虫数据
|
│ ├── L1A/ # 原始爬虫数据
|
||||||
│ ├── L2/ # 结构化事实数据
|
│ ├── L2/ # 结构化事实数据
|
||||||
│ └── L3/ # [新增] 衍生特征库 (Feature Store)
|
│ ├── L3/ # 衍生特征库 (Feature Store)
|
||||||
└── ETL/ # ETL 脚本
|
│ └── Web/ # [新增] 业务数据库 (User, Comment, Wiki)
|
||||||
├── L1A.py
|
└── ETL/ # 数据处理层 (ETL Pipeline)
|
||||||
├── L2_Builder.py
|
├── L1A.py # L1A Ingest
|
||||||
└── L3_FeatureEng.py # [新增] L3 特征工程脚本
|
├── L2_Builder.py # L2 Transform
|
||||||
|
└── L3_Builder.py # L3 Feature Engineering (原 feature_store.py 逻辑)
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2.2 数据流向
|
### 2.2 数据流向
|
||||||
1. **ETL 层**:
|
1. **ETL 层 (数据处理核心)**:
|
||||||
* L1 (Raw): 爬虫 -> JSON 存储。
|
* L1 (Raw): 爬虫 -> JSON 存储。
|
||||||
* L2 (Fact): JSON -> 清洗/标准化 -> Fact/Dim Tables。
|
* L2 (Fact): JSON -> 清洗/标准化 -> Fact/Dim Tables。
|
||||||
* **L3 (Features)**: L2 -> 聚合/滑窗计算/模型推理 -> Player/Team Derived Features (e.g., 近期状态分, 地图熟练度, 关键局胜率)。
|
* **L3 (Features)**: L2 -> 聚合/滑窗计算/模型推理 -> Player/Team Derived Features。**数据处理逻辑收敛于 ETL 目录下的脚本,Web 端仅负责读取 L2/L3 结果。**
|
||||||
2. **Service 层**: Flask Service 读取 L2 (基础数据) 和 L3 (高级特征),执行业务逻辑。
|
2. **Service 层**: Flask Service 仅负责 SQL 查询与简单的业务组装(如评论关联),不再包含复杂的数据计算逻辑。
|
||||||
3. **View 层**: Jinja2 渲染 HTML,嵌入计算后的数据。
|
3. **View 层**: Jinja2 渲染 HTML。
|
||||||
4. **Client 层**: 浏览器展示页面,JS 处理局部交互。
|
4. **Client 层**: 浏览器交互。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -92,11 +101,11 @@ yrtv/
|
|||||||
* **比赛解析器**: 输入 5E 比赛链接,点击按钮触发后台 ETL 任务(异步),前端显示 Loading 状态或 Toast 提示。
|
* **比赛解析器**: 输入 5E 比赛链接,点击按钮触发后台 ETL 任务(异步),前端显示 Loading 状态或 Toast 提示。
|
||||||
|
|
||||||
### 3.2 玩家模块 (Players)
|
### 3.2 玩家模块 (Players)
|
||||||
#### 3.2.1 玩家列表
|
#### 3.2.1 玩家列表 PlayerList
|
||||||
* **筛选/搜索**: 按 ID/昵称搜索,按 K/D、Rating、MVP 等指标排序。
|
* **筛选/搜索**: 按 ID/昵称搜索,按 K/D、Rating、MVP 等指标排序。
|
||||||
* **展示**: 卡片式布局,显示头像、ID、主队、核心数据 (Rating, K/D, ADR)。
|
* **展示**: 卡片式布局,显示头像、ID、主队、核心数据 (Rating, K/D, ADR)。
|
||||||
#### 3.2.2 玩家详情
|
#### 3.2.2 玩家详情 PlayerProfile
|
||||||
* **基础信息**: 头像、SteamID、5E ID、注册时间。可以手动分配Tag。
|
* **基础信息**: 头像、SteamID、5E ID、注册时间。可以手动分配Tag。玩家列表 Players
|
||||||
* **核心指标**: 赛季平均 Rating, ADR, KAST, 首杀成功率等。
|
* **核心指标**: 赛季平均 Rating, ADR, KAST, 首杀成功率等。
|
||||||
* **能力雷达图**: *计算规则需在 Service 层定义*。
|
* **能力雷达图**: *计算规则需在 Service 层定义*。
|
||||||
* **趋势图**: 近 10/20 场比赛 Rating 走势 (Chart.js)。
|
* **趋势图**: 近 10/20 场比赛 Rating 走势 (Chart.js)。
|
||||||
@@ -109,11 +118,11 @@ yrtv/
|
|||||||
* **统计概览**: 战队整体胜率、近期战绩、地图胜率分布,个人关键数据。
|
* **统计概览**: 战队整体胜率、近期战绩、地图胜率分布,个人关键数据。
|
||||||
|
|
||||||
### 3.4 比赛模块 (Matches)
|
### 3.4 比赛模块 (Matches)
|
||||||
#### 3.4.1 比赛列表
|
#### 3.4.1 比赛列表 MatchList
|
||||||
* **筛选**: 按地图、日期范围筛选。
|
* **筛选**: 按地图、日期范围筛选。
|
||||||
* **展示**: 列表视图,显示时间、地图、比分、胜负、MVP。
|
* **展示**: 列表视图,显示时间、地图、比分、胜负、MVP。
|
||||||
|
|
||||||
#### 3.4.2 比赛详情
|
#### 3.4.2 比赛详情 MatchDetail
|
||||||
* **头部**: 比分板(CT/T 分数)、地图、时长、Demo 下载链接。
|
* **头部**: 比分板(CT/T 分数)、地图、时长、Demo 下载链接。
|
||||||
* **数据表**: 双方队伍的完整数据表(K, D, A, FK, FD, ADR, Rating, KAST, AWP Kills 等)。
|
* **数据表**: 双方队伍的完整数据表(K, D, A, FK, FD, ADR, Rating, KAST, AWP Kills 等)。
|
||||||
* *利用 `fact_match_players` 中的丰富字段*。
|
* *利用 `fact_match_players` 中的丰富字段*。
|
||||||
@@ -126,7 +135,7 @@ yrtv/
|
|||||||
* **共同经历**: 查询这 5 人共同参与过的比赛场次及胜率。
|
* **共同经历**: 查询这 5 人共同参与过的比赛场次及胜率。
|
||||||
* **协同矩阵**: 选择特定阵容,展示两两之间的协同数据(如:A 补枪 B 的次数,A 与 B 同时在场时的胜率)。
|
* **协同矩阵**: 选择特定阵容,展示两两之间的协同数据(如:A 补枪 B 的次数,A 与 B 同时在场时的胜率)。
|
||||||
* **最佳/短板分析**: 基于历史数据分析该阵容在特定地图上的强弱项。
|
* **最佳/短板分析**: 基于历史数据分析该阵容在特定地图上的强弱项。
|
||||||
#### 3.5.2 数据对比
|
#### 3.5.2 数据对比 Data Center
|
||||||
* **多选对比**: 选择多名玩家,并在同一雷达图/柱状图中对比各项数据。
|
* **多选对比**: 选择多名玩家,并在同一雷达图/柱状图中对比各项数据。
|
||||||
* **地图筛选**: 查看特定玩家在特定地图上的表现差异。
|
* **地图筛选**: 查看特定玩家在特定地图上的表现差异。
|
||||||
#### 3.5.3 道具与策略板 (Grenades & Strategy Board)
|
#### 3.5.3 道具与策略板 (Grenades & Strategy Board)
|
||||||
|
|||||||
Reference in New Issue
Block a user