From d5edbc072344fdf6bb7434d5814ce732b71d3a94 Mon Sep 17 00:00:00 2001 From: zhanghuilai Date: Thu, 5 Feb 2026 15:13:54 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0web=5Fapi?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/vcs.xml | 6 + API_DOCUMENTATION.md | 1734 +++++++++++++++++ API_ENDPOINTS.md | 782 ++++++++ ENVIRONMENT_VARIABLES.md | 255 +++ EXE_BUILD_README.md | 203 ++ GASFLUX_CONFIG_DOCUMENTATION.md | 422 ++++ README.md | 57 + WAITRESS_DEPLOYMENT.md | 271 +++ build_exe.bat | 62 + build_exe.sh | 55 + build_gasflux_exe.bat | 48 - build_gasflux_exe.sh | 48 - env.example | 30 + examples/basic_usage/advanced_config.yaml | 52 - .../data/08_34_01_间隔高度5m.xlsx | Bin 192340 -> 0 bytes .../data/08_51_15_间隔高度10m.xlsx | Bin 179889 -> 0 bytes examples/basic_usage/data/gasflux_config.yaml | 69 - examples/basic_usage/data/testdata.csv | 1001 ---------- examples/basic_usage/gasflux_config.yaml | 61 - examples/basic_usage/result.csv | 820 -------- examples/basic_usage/testconfig.yaml | 95 - requirements.txt | 6 +- run_api.py | 30 + server_waitress.py | 117 ++ src/gasflux/app.py | 728 +++++++ src/gasflux/blueprints/__init__.py | 1 + src/gasflux/blueprints/config.py | 67 + src/gasflux/blueprints/download.py | 68 + src/gasflux/blueprints/health.py | 94 + src/gasflux/blueprints/reports.py | 192 ++ src/gasflux/blueprints/stats.py | 93 + src/gasflux/blueprints/task_pool.py | 228 +++ src/gasflux/blueprints/tasks.py | 220 +++ src/gasflux/blueprints/upload.py | 134 ++ src/gasflux/blueprints/web.py | 233 +++ src/gasflux/data_processor.py | 382 ++-- src/gasflux/processing_pipelines.py | 12 +- src/gasflux/reporting.py | 29 +- src/gasflux/run_example.py | 4 +- src/gasflux/shared.py | 704 +++++++ tests/__init__.py | 0 tests/test_processing.py | 205 -- tests/test_processing_pipelines.py | 58 - 43 files changed, 7036 insertions(+), 2640 deletions(-) create mode 100644 .idea/vcs.xml create mode 100644 API_DOCUMENTATION.md create mode 100644 API_ENDPOINTS.md create mode 100644 ENVIRONMENT_VARIABLES.md create mode 100644 EXE_BUILD_README.md create mode 100644 GASFLUX_CONFIG_DOCUMENTATION.md create mode 100644 WAITRESS_DEPLOYMENT.md create mode 100644 build_exe.bat create mode 100644 build_exe.sh delete mode 100644 build_gasflux_exe.bat delete mode 100644 build_gasflux_exe.sh create mode 100644 env.example delete mode 100644 examples/basic_usage/advanced_config.yaml delete mode 100644 examples/basic_usage/data/08_34_01_间隔高度5m.xlsx delete mode 100644 examples/basic_usage/data/08_51_15_间隔高度10m.xlsx delete mode 100644 examples/basic_usage/data/gasflux_config.yaml delete mode 100644 examples/basic_usage/data/testdata.csv delete mode 100644 examples/basic_usage/gasflux_config.yaml delete mode 100644 examples/basic_usage/result.csv delete mode 100644 examples/basic_usage/testconfig.yaml create mode 100644 run_api.py create mode 100644 server_waitress.py create mode 100644 src/gasflux/app.py create mode 100644 src/gasflux/blueprints/__init__.py create mode 100644 src/gasflux/blueprints/config.py create mode 100644 src/gasflux/blueprints/download.py create mode 100644 src/gasflux/blueprints/health.py create mode 100644 src/gasflux/blueprints/reports.py create mode 100644 src/gasflux/blueprints/stats.py create mode 100644 src/gasflux/blueprints/task_pool.py create mode 100644 src/gasflux/blueprints/tasks.py create mode 100644 src/gasflux/blueprints/upload.py create mode 100644 src/gasflux/blueprints/web.py create mode 100644 src/gasflux/shared.py delete mode 100644 tests/__init__.py delete mode 100644 tests/test_processing.py delete mode 100644 tests/test_processing_pipelines.py diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/API_DOCUMENTATION.md b/API_DOCUMENTATION.md new file mode 100644 index 0000000..e67b419 --- /dev/null +++ b/API_DOCUMENTATION.md @@ -0,0 +1,1734 @@ +# GasFlux Web API 文档 + +## 概述 + +GasFlux Web API 是一个基于 Flask 的 RESTful API,用于上传数据文件、执行气体通量分析处理,并下载处理结果。该 API 支持异步处理,能够处理大量数据并提供实时状态监控。 + +## 快速开始 + +### 基础信息 + +- **基础 URL**: `http://localhost:5000` +- **认证**: 无需认证 +- **数据格式**: JSON +- **文件大小限制**: 100MB +- **支持的文件类型**: + - 数据文件: `.xlsx`, `.xls` + - 配置文件: `.yaml`, `.yml` + +### 完整工作流程示例 + +```python +import requests +import time + +def process_gasflux_data(): + # 1. 检查 API 健康状态 + health_response = requests.get('http://localhost:5000/health') + print(f"API Status: {health_response.json()['status']}") + + # 2. 上传数据文件 + with open('data.xlsx', 'rb') as f: + files = {'file': f} + upload_response = requests.post('http://localhost:5000/upload', files=files) + + result = upload_response.json() + task_id = result['job_id'] + print(f"任务已创建: {task_id}") + + # 3. 监控处理状态 + while True: + status_response = requests.get(f'http://localhost:5000/task/{task_id}') + status = status_response.json() + + print(f"Status: {status['status']} - {status['message']}") + + if status['status'] == 'completed': + # 4. 下载结果文件 + for result_file in status['results']: + download_url = f"http://localhost:5000{result_file['download_url']}" + download_response = requests.get(download_url) + + with open(result_file['name'], 'wb') as f: + f.write(download_response.content) + print(f"Downloaded: {result_file['name']}") + break + elif status['status'] == 'failed': + print(f"Task failed: {status.get('error', '未知错误')}") + break + + time.sleep(3) # 每3秒检查一次状态 +``` + +## API 端点 + +### 🔍 监控和健康检查 + +#### 1. 获取健康状态 +**端点**: `GET /health` + +**描述**: 获取 API 的健康状态、系统信息和性能指标。健康检查会评估多个关键指标,当任何指标超出正常范围时,服务状态会标记为 `degraded`。 + +**健康检查逻辑**: +- **存储检查**: 验证上传和输出文件夹是否可写 +- **负载检查**: 活跃任务数量超过20个时发出警告 +- **错误率检查**: HTTP错误率超过10%时标记为不健康 +- **综合评估**: 任何一项检查失败都会影响整体健康状态 + +**响应示例** - 健康状态 (200): +```json +{ + "code": 200, + "message": "健康检查完成", + "data": { + "status": "healthy", + "version": "1.0.0", + "timestamp": 1705257600.123, + "uptime": "2h 30m 15s", + "storage": { + "uploads_writable": true, + "outputs_writable": true + }, + "tasks": { + "active_count": 2, + "total_tracked": 15, + "total_processed": 13, + "success_rate_percent": 92.31 + }, + "performance": { + "requests_per_second": 0.08, + "avg_response_time_ms": 234.56, + "error_rate_percent": 1.5 + } + } +} +``` + +**响应示例** - 不健康状态 (503): +```json +{ + "code": 503, + "message": "服务不可用", + "data": { + "status": "degraded", + "version": "1.0.0", + "timestamp": 1705257600.123, + "uptime": "1h 30m 45s", + "storage": { + "uploads_writable": true, + "outputs_writable": true + }, + "tasks": { + "active_count": 0, + "total_tracked": 5, + "total_processed": 3, + "success_rate_percent": 60.0 + }, + "performance": { + "requests_per_second": 0.12, + "avg_response_time_ms": 145.67, + "error_rate_percent": 50.0 + }, + "issues": [ + "错误率过高 (50.0%)", + "活跃任务数量过多 (25)" + ] + } +} +``` + +**状态码**: +- `200`: API 健康 (`status: "healthy"`) +- `503`: API 不健康 (`status: "degraded"`) - 服务不可用 + +**健康状态说明**: +- **healthy**: 所有检查通过,服务正常运行 +- **degraded**: 部分检查失败,服务仍可运行但需要关注 + - 错误率 > 10%: HTTP请求错误率过高 + - 活跃任务 > 20: 系统负载过高 + - 存储不可写: 文件系统权限问题 + +**字段说明**: +- `storage.uploads_writable`: 上传文件夹是否可写 +- `storage.outputs_writable`: 输出文件夹是否可写 +- `tasks.active_count`: 当前活跃的任务数量 +- `performance.error_rate_percent`: HTTP请求错误率百分比 +- `issues`: 当状态为degraded时的具体问题列表 + +--- + +#### 2. 获取系统统计信息 +**端点**: `GET /stats` + +**描述**: 获取详细的 API 统计信息、性能指标和系统监控数据,包括请求统计、任务状态、性能指标和系统资源使用情况。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "统计信息获取成功", + "data": { + "summary": { + "uptime_seconds": 3600.5, + "uptime_formatted": "1h 0m 0s", + "requests_total": 150, + "requests_per_second": 0.04, + "error_rate_percent": 2.0, + "active_tasks": 1 + }, + "requests": { + "by_method": { + "GET": 120, + "POST": 30 + }, + "by_status": { + "200": 145, + "400": 3, + "500": 2 + }, + "top_endpoints": { + "/task/abc123": 45, + "/health": 30, + "/": 25 + } + }, + "tasks": { + "total_created": 25, + "total_completed": 20, + "total_failed": 2, + "success_rate_percent": 90.91, + "by_status": { + "pending": 1, + "processing": 1, + "completed": 20, + "failed": 2 + } + }, + "performance": { + "avg_response_time_ms": 245.67, + "max_response_time_ms": 1250.34, + "min_response_time_ms": 12.45 + }, + "system": { + "memory_usage_percent": 45.2, + "memory_used_gb": 7.3, + "memory_total_gb": 16.0, + "disk_usage_percent": 23.1, + "disk_used_gb": 46.8, + "disk_total_gb": 203.2 + }, + "recent_tasks": [ + { + "task_id": "abc123-def456", + "status": "completed", + "age_seconds": 45.2, + "message": "处理完成成功" + } + ] + } +} +``` + +**字段说明**: +- `summary.uptime_seconds`: API运行时间(秒) +- `summary.uptime_formatted`: 格式化的运行时间(如 "1h 0m 0s") +- `summary.requests_total`: 总请求数 +- `summary.requests_per_second`: 平均每秒请求数 +- `summary.error_rate_percent`: 请求错误率百分比 +- `summary.active_tasks`: 当前活跃任务数(pending或processing状态) +- `requests.by_method`: 按HTTP方法分组的请求统计 +- `requests.by_status`: 按HTTP状态码分组的请求统计 +- `requests.top_endpoints`: 请求最多的前10个端点 +- `tasks.total_created`: 创建的总任务数 +- `tasks.total_completed`: 完成的任务数 +- `tasks.total_failed`: 失败的任务数 +- `tasks.success_rate_percent`: 任务成功率百分比 +- `tasks.by_status`: 按状态分组的任务统计 +- `performance.avg_response_time_ms`: 平均响应时间(毫秒) +- `performance.max_response_time_ms`: 最大响应时间(毫秒) +- `performance.min_response_time_ms`: 最小响应时间(毫秒) +- `system.memory_usage_percent`: 内存使用率百分比 +- `system.memory_used_gb`: 已用内存(GB) +- `system.memory_total_gb`: 总内存(GB) +- `system.disk_usage_percent`: 磁盘使用率百分比(输出目录所在磁盘) +- `system.disk_used_gb`: 已用磁盘空间(GB) +- `system.disk_total_gb`: 总磁盘空间(GB) +- `recent_tasks[]`: 最近20个任务的状态信息 + +--- + +#### 3. 重置统计信息 +**端点**: `POST /stats/reset` + +**描述**: 重置所有 API 统计数据(管理员功能)。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "统计信息重置成功", + "data": { + "timestamp": 1705257600.123 + } +} +``` + +--- + +#### 4. 获取配置信息 +**端点**: `GET /config` + +**描述**: 获取当前应用配置信息和支持的环境变量。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "配置信息获取成功", + "data": { + "configuration": { + "host": "0.0.0.0", + "port": 5000, + "debug": false, + "base_dir": "/app", + "upload_folder": "/app/web_api_data/uploads", + "output_folder": "/app/web_api_data/outputs", + "max_content_length": 104857600, + "log_level": "INFO", + "log_file": "logs/gasflux_api.log", + "cors_origins": ["*"], + "task_cleanup_interval": 3600, + "max_task_age": 86400, + "threads": 8, + "connection_limit": 100, + "channel_timeout": 300 + }, + "environment_variables": { + "supported": [ + "GASFLUX_HOST", "GASFLUX_PORT", "GASFLUX_DEBUG", + "GASFLUX_UPLOAD_FOLDER", "GASFLUX_OUTPUT_FOLDER", + "GASFLUX_MAX_CONTENT_LENGTH", "GASFLUX_LOG_LEVEL", + "GASFLUX_LOG_FILE", "GASFLUX_CORS_ORIGINS", + "GASFLUX_TASK_CLEANUP_INTERVAL", "GASFLUX_MAX_TASK_AGE", + "GASFLUX_THREADS", "GASFLUX_CONNECTION_LIMIT", + "GASFLUX_CHANNEL_TIMEOUT" + ], + "current_values": { + "GASFLUX_HOST": "0.0.0.0", + "GASFLUX_PORT": "5000", + "GASFLUX_DEBUG": "false" + } + } + } +} +``` + +### 📤 文件上传和管理 + +#### 5. 文件上传和处理 +**端点**: `POST /upload` + +**描述**: 上传数据文件并启动异步处理任务。 + +**请求参数** (multipart/form-data): +- `file` (必需): 数据文件 (.xlsx 或 .xls 格式) +- `config` (可选): 配置文件 (.yaml 或 .yml 格式) + +**请求示例** (cURL): +```bash +curl -X POST \ + -F "file=@data.xlsx" \ + -F "config=@config.yaml" \ + http://localhost:5000/upload +``` + +**请求示例** (Python): +```python +import requests + +files = {'file': open('data.xlsx', 'rb')} +config = {'config': open('config.yaml', 'rb')} # 可选 + +response = requests.post('http://localhost:5000/upload', files={**files, **config}) +result = response.json() +print(f"Task ID: {result['job_id']}") +``` + +**成功响应** (202): +```json +{ + "code": 202, + "message": "任务已接受并加入处理队列", + "data": { + "status": "accepted", + "job_id": "abc123-def456-ghi789", + "task_status_url": "/task/abc123-def456-ghi789" + } +} +``` + +**错误响应示例**: + +- 文件类型不支持 (400): +```json +{ + "code": 400, + "message": "无效的数据文件类型。只允许 .xlsx 和 .xls 格式。", + "data": {} +} +``` + +- 文件过大 (413): +```json +{ + "code": 413, + "message": "文件过大。最大尺寸为 100MB。", + "data": {} +} +``` + +- 配置文件格式错误 (400): +```json +{ + "code": 400, + "message": "无效的配置文件类型。只允许 .yaml 和 .yml 格式。", + "data": {} +} +``` + +### 📊 任务管理和监控 + +#### 6. 查询任务状态 +**端点**: `GET /task/{task_id}` + +**描述**: 查询异步处理任务的当前状态和进度信息。 + +**路径参数**: +- `task_id`: 任务 ID (UUID 格式) + +**响应示例** - 处理中 (200): +```json +{ + "code": 200, + "message": "任务查询成功", + "data": { + "task_id": "abc123-def456-ghi789", + "status": "processing", + "message": "GasFlux 分析完成,正在生成报告...", + "updated_at": 1705257600.123, + "progress": { + "stage": "report_generation", + "completed_steps": 4, + "total_steps": 5, + "estimated_time_remaining": 45 + } + } +} +``` + +**响应示例** - 处理完成 (200): +```json +{ + "code": 200, + "message": "任务查询成功", + "data": { + "task_id": "abc123-def456-ghi789", + "status": "completed", + "message": "处理完成成功", + "updated_at": 1705257600.123, + "processing_time_seconds": 125.67, + "results": [ + { + "name": "08_34_01_5m.processed_ch4_report.html", + "rel_path": "abc123-def456-ghi789/08_34_01_5m.processed_ch4_report.html", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed_ch4_report.html", + "size": 245760, + "type": "report" + }, + { + "name": "08_34_01_5m.processed_data.csv", + "rel_path": "abc123-def456-ghi789/08_34_01_5m.processed_data.csv", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed_data.csv", + "size": 153600, + "type": "data" + }, + { + "name": "08_34_01_5m.processed_config.yaml", + "rel_path": "abc123-def456-ghi789/08_34_01_5m.processed_config.yaml", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed_config.yaml", + "size": 2048, + "type": "config" + }, + { + "name": "08_34_01_5m.processed_output_vars.json", + "rel_path": "abc123-def456-ghi789/08_34_01_5m.processed_output_vars.json", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed_output_vars.json", + "size": 4096, + "type": "metadata" + } + ] + } +} +``` + +**响应示例** - 处理失败 (200): +```json +{ + "task_id": "abc123-def456-ghi789", + "status": "failed", + "message": "处理失败", + "updated_at": 1705257600.123, + "processing_time_seconds": 23.45, + "error": "处理失败: Invalid data format in column 'temperature'", + "error_details": { + "stage": "data_validation", + "error_code": "INVALID_DATA_FORMAT", + "traceback": "..." + } +} +``` + +**响应示例** - 任务不存在 (404): +```json +{ + "code": 404, + "message": "任务未找到", + "data": {} +} +``` + +**任务状态说明**: +- `pending`: 任务已排队,等待处理 +- `processing`: 正在处理中(包含进度信息) +- `completed`: 处理完成(包含结果文件列表) +- `failed`: 处理失败(包含错误信息) + +--- + +#### 7. 更新任务状态 +**端点**: `PUT /task/{task_id}` + +**描述**: 更新任务的状态、信息或优先级。 + +**路径参数**: +- `task_id`: 任务 ID (UUID 格式) + +**请求参数** (JSON): +- `status` (可选): 新的任务状态 + - `pending`: 重新排队等待处理 + - `processing`: 标记为处理中 + - `completed`: 标记为完成 + - `failed`: 标记为失败 +- `message` (可选): 状态消息或错误描述 +- `priority` (可选): 任务优先级 (normal/high/low) + +**请求示例** (cURL): +```bash +# 标记任务为完成 +curl -X PUT http://localhost:5000/task/abc123-def456-ghi789 \ + -H "Content-Type: application/json" \ + -d '{"status": "completed", "message": "手动标记为完成"}' + +# 更新任务消息 +curl -X PUT http://localhost:5000/task/abc123-def456-ghi789 \ + -H "Content-Type: application/json" \ + -d '{"message": "更新的状态消息"}' + +# 设置高优先级 +curl -X PUT http://localhost:5000/task/abc123-def456-ghi789 \ + -H "Content-Type: application/json" \ + -d '{"priority": "high"}' +``` + +**请求示例** (Python): +```python +import requests + +# 标记任务为失败 +response = requests.put( + 'http://localhost:5000/task/abc123-def456-ghi789', + json={ + 'status': 'failed', + 'message': '处理失败 due to invalid input data' + } +) + +# 更新任务优先级 +response = requests.put( + 'http://localhost:5000/task/abc123-def456-ghi789', + json={'priority': 'high'} +) +``` + +**成功响应** (200): +```json +{ + "code": 200, + "message": "任务更新成功", + "data": { + "task_id": "abc123-def456-ghi789", + "status": "updated", + "task_info": { + "status": "completed", + "message": "手动标记为完成", + "updated_at": 1705257600.123, + "priority": "normal" + } + } +} +``` + +**错误响应示例**: + +- 任务不存在 (404): +```json +{ + "code": 404, + "message": "任务未找到", + "data": {} +} +``` + +- 无效状态 (400): +```json +{ + "code": 400, + "message": "无效状态。必须是以下之一: pending, processing, completed, failed", + "data": {} +} +``` + +- 无效请求 (400): +```json +{ + "code": 400, + "message": "请求体必须是 JSON 格式", + "data": {} +} +``` + +--- + +#### 8. 删除任务 +**端点**: `DELETE /task/{task_id}` + +**描述**: 删除任务及其所有相关的文件和数据。 + +**路径参数**: +- `task_id`: 任务 ID (UUID 格式) + +**请求示例** (cURL): +```bash +# 删除指定任务 +curl -X DELETE http://localhost:5000/task/abc123-def456-ghi789 +``` + +**请求示例** (Python): +```python +import requests + +# 删除任务 +response = requests.delete('http://localhost:5000/task/abc123-def456-ghi789') + +if response.status_code == 200: + result = response.json() + print(f"Task {result['task_id']} deleted") + print(f"Files deleted: {result['details']['folders_deleted']}") + print(f"Size freed: {result['details']['total_size_deleted']} bytes") +else: + print(f"Failed to delete task: {response.json()}") +``` + +**成功响应** (200): +```json +{ + "code": 200, + "message": "任务及相关文件删除成功", + "data": { + "task_id": "abc123-def456-ghi789", + "status": "deleted", + "details": { + "folders_deleted": 1, + "total_size_deleted": 307200, + "task_status": "completed" + } + } +} +``` + +**错误响应示例**: + +- 任务不存在 (404): +```json +{ + "code": 404, + "message": "任务未找到", + "data": {} +} +``` + +- 任务正在处理 (409): +```json +{ + "code": 409, + "message": "无法删除当前正在处理或等待处理的任务", + "data": { + "task_status": "processing" + } +} +``` + +- 删除文件失败 (500): +```json +{ + "code": 500, + "message": "删除任务文件失败: Permission denied", + "data": {} +} +``` + +**注意事项**: +- 只能删除已完成或失败的任务 +- 无法删除正在处理或等待处理的任务 +- 删除操作会同时删除任务记录和所有相关文件 +- 删除操作不可逆,请谨慎使用 + +--- + +### 📋 报告管理和查询 + +#### 9. 分页查询已生成报告 +**端点**: `GET /reports` + +**描述**: 分页查询所有已生成的处理报告,支持排序和过滤。 + +**查询参数**: +- `page` (可选): 页码 (默认: 1) +- `per_page` (可选): 每页报告数量 (默认: 20, 最大: 100) +- `sort_by` (可选): 排序字段 (默认: created_at) + - `created_at`: 按创建时间排序 + - `task_id`: 按任务ID排序 + - `file_size`: 按文件总大小排序 + - `processing_time`: 按处理时间排序 +- `sort_order` (可选): 排序顺序 (默认: desc) + - `asc`: 升序 + - `desc`: 降序 +- `status` (可选): 按任务状态过滤 + - `completed`: 只显示完成的任务 + - `failed`: 只显示失败的任务 + - 不指定: 显示所有任务 + +**请求示例** (cURL): +```bash +# 获取第一页,每页20个报告,按创建时间倒序 +curl "http://localhost:5000/reports?page=1&per_page=20&sort_by=created_at&sort_order=desc" + +# 获取第二页,只显示完成的任务 +curl "http://localhost:5000/reports?page=2&status=completed" + +# 按处理时间升序排序 +curl "http://localhost:5000/reports?sort_by=processing_time&sort_order=asc" +``` + +**请求示例** (Python): +```python +import requests + +# 基本查询 +response = requests.get('http://localhost:5000/reports') +reports = response.json() + +# 分页查询 +params = { + 'page': 1, + 'per_page': 10, + 'sort_by': 'created_at', + 'sort_order': 'desc', + 'status': 'completed' +} +response = requests.get('http://localhost:5000/reports', params=params) +data = response.json() + +print(f"总报告数: {data['pagination']['total_reports']}") +print(f"当前页: {data['pagination']['page']}/{data['pagination']['total_pages']}") + +for report in data['reports']: + print(f"任务: {report['task_id']}") + print(f"状态: {report['status']}") + print(f"创建时间: {report['created_at']}") + print(f"文件数量: {report['file_count']}") + if report['main_report']: + print(f"主报告: {report['main_report']['download_url']}") +``` + +**成功响应** (200): +```json +{ + "code": 200, + "message": "报告列表获取成功", + "data": { + "reports": [ + { + "task_id": "abc123-def456-ghi789", + "report_name": "08_34_01_5m", + "status": "completed", + "created_at": 1705257600.123, + "file_count": 4, + "total_size": 307200, + "processing_time_seconds": 125.67, + "main_report": { + "name": "08_34_01_5m.processed_ch4_report.html", + "size": 245760, + "type": "report", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_ch4_report.html" + }, + "all_files": [ + { + "name": "08_34_01_5m.processed_ch4_report.html", + "size": 245760, + "type": "report", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_ch4_report.html" + }, + { + "name": "08_34_01_5m.processed_data.csv", + "size": 153600, + "type": "data", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_data.csv" + }, + { + "name": "08_34_01_5m.processed_config.yaml", + "size": 2048, + "type": "config", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_config.yaml" + }, + { + "name": "08_34_01_5m.processed_output_vars.json", + "size": 4096, + "type": "metadata", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_output_vars.json" + } + ], + "run_directory": "abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run" + } + ], + "pagination": { + "page": 1, + "per_page": 20, + "total_reports": 45, + "total_pages": 3, + "has_next": true, + "has_prev": false + }, + "filters": { + "sort_by": "created_at", + "sort_order": "desc", + "status": null + } + } +} +``` + +**错误响应示例**: + +- 参数无效 (400): +```json +{ + "code": 400, + "message": "Invalid parameter: per_page must be between 1 and 100", + "data": {} +} +``` + +--- + +### 📁 文件下载 + +#### 10. 下载处理结果 +**端点**: `GET /download/{filename}` + +**描述**: 下载处理后的结果文件。 + +**路径参数**: +- `filename`: 文件的相对路径 (包含任务ID) + +**请求示例** (cURL): +```bash +# 下载 HTML 报告 +curl -O http://localhost:5000/download/abc123-def456-ghi789/report.html + +# 下载 CSV 数据 +curl -O http://localhost:5000/download/abc123-def456-ghi789/data.csv + +# 使用 Python 下载 +import requests + +response = requests.get('http://localhost:5000/download/abc123-def456-ghi789/report.html') +with open('report.html', 'wb') as f: + f.write(response.content) +``` + +**状态码**: +- `200`: 成功下载文件 +- `403`: 访问被拒绝 (路径遍历攻击防护) +- `404`: 文件不存在 +- `400`: 路径不是文件 + +--- + +### 🌐 Web 界面 + +#### 11. Web 管理界面 +**端点**: `GET /` + +**描述**: 访问用户友好的 Web 界面,支持文件上传、任务监控和结果下载。 + +**响应**: HTML 页面,包含: +- 文件上传表单 +- 任务状态监控面板 +- 结果文件下载链接 +- 系统状态信息 + +--- + +## 🔄 处理流程详解 + +### 完整处理流程 + +1. **文件上传阶段** + - 客户端验证文件类型和大小 + - 上传数据文件和可选的配置文件 + - 服务器进行安全检查和文件存储 + +2. **任务队列管理** + - 服务器为上传任务分配唯一的 UUID + - 任务进入处理队列,根据系统负载进行调度 + +3. **异步数据处理** + - **数据预处理**: 格式转换、数据验证、单位转换 + - **配置合并**: 默认配置 + 用户配置 + - **GasFlux 核心分析**: + - 背景校正算法 + - 气体通量计算 + - 空间插值 (克里金插值) + - 统计分析和可视化 + - **结果生成**: HTML 报告、CSV 数据、配置文件备份 + +4. **实时状态监控** + - 客户端通过任务 ID 轮询状态 + - 支持进度跟踪和预计完成时间 + +5. **结果获取和清理** + - 处理完成后提供下载链接 + - 定期清理过期任务和文件 + +### 处理时间估计 + +- 小文件 (< 10MB): 1-3 分钟 +- 中等文件 (10-50MB): 3-10 分钟 +- 大文件 (> 50MB): 10-30 分钟 +- 受计算复杂度、数据质量和系统负载影响 + +## ⚠️ 错误处理 + +### HTTP 状态码 + +| 状态码 | 说明 | 处理建议 | +|--------|------|----------| +| 200 | 请求成功 | 正常处理响应数据 | +| 202 | 请求已接受 (异步处理) | 记录任务 ID,开始状态轮询 | +| 400 | 请求参数错误 | 检查请求格式和参数 | +| 403 | 访问被拒绝 | 检查文件路径和权限 | +| 404 | 资源不存在 | 验证任务 ID 或文件路径 | +| 413 | 文件过大 | 压缩文件或联系管理员 | +| 500 | 服务器内部错误 | 检查系统状态,重试请求 | + +### 常见错误响应 + +**文件上传错误**: +```json +{ + "code": 400, + "message": "无效的数据文件类型。只允许 .xlsx 和 .xls 格式。", + "data": {} +} +``` + +```json +{ + "code": 413, + "message": "文件过大。最大尺寸为 100MB。", + "data": {} +} +``` + +**任务查询错误**: +```json +{ + "code": 404, + "message": "任务未找到", + "data": { + "task_id": "invalid-task-id" + } +} +``` + +**文件下载错误**: +```json +{ + "code": 404, + "message": "文件未找到", + "data": { + "filename": "task-id/file.html" + } +} +``` + +## 📊 性能监控和日志 + +### 日志记录 + +所有 API 请求都会记录到 `gasflux_api.log` 文件,包含: + +``` +2026-01-14 10:30:15,123 - INFO - [task:abc123] POST /upload - 202 - 2.34s +2026-01-14 10:30:17,456 - INFO - [task:abc123] Processing started: data.xlsx (15.2MB) +2026-01-14 10:32:22,789 - INFO - [task:abc123] Processing completed: 4 files generated +2026-01-14 10:32:25,012 - INFO - [task:abc123] GET /download/abc123/report.html - 200 - 0.45s +``` + +### 性能指标 + +通过 `/stats` 端点获取: +- 请求响应时间统计 +- 任务成功率 +- 系统资源使用情况 +- 错误率和热点端点 + +## 💻 编程示例 + +### Python 完整示例 + +#### 基础用法 +```python +import requests +import time +import os +from pathlib import Path + +class GasFluxClient: + def __init__(self, base_url="http://localhost:5000"): + self.base_url = base_url.rstrip('/') + + def check_health(self): + """检查 API 健康状态""" + response = requests.get(f"{self.base_url}/health") + return response.json() + + def upload_and_process(self, data_file, config_file=None, output_dir="./results"): + """上传文件并处理""" + files = {'file': open(data_file, 'rb')} + if config_file and os.path.exists(config_file): + files['config'] = open(config_file, 'rb') + + # 上传文件 + print(f"Uploading {data_file}...") + response = requests.post(f"{self.base_url}/upload", files=files) + result = response.json() + + if response.status_code != 202: + raise Exception(f"Upload failed: {result.get('error', 'Unknown error')}") + + task_id = result['job_id'] + print(f"任务已创建: {task_id}") + + # 监控处理状态 + while True: + status_response = requests.get(f"{self.base_url}/task/{task_id}") + status = status_response.json() + + print(f"Status: {status['status']} - {status['message']}") + + if status['status'] == 'completed': + # 下载结果文件 + os.makedirs(output_dir, exist_ok=True) + for result_file in status['results']: + download_url = f"{self.base_url}{result_file['download_url']}" + output_path = Path(output_dir) / result_file['name'] + + print(f"Downloading {result_file['name']}...") + download_response = requests.get(download_url) + with open(output_path, 'wb') as f: + f.write(download_response.content) + + print(f"Processing completed! Results saved to {output_dir}") + return status + + elif status['status'] == 'failed': + error_msg = status.get('error', 'Unknown error') + raise Exception(f"Task failed: {error_msg}") + + time.sleep(3) # 每3秒检查一次状态 + +# 使用示例 +client = GasFluxClient() +try: + # 检查 API 状态 + health = client.check_health() + print(f"API Status: {health['status']}") + + # 处理数据 + result = client.upload_and_process( + data_file="data.xlsx", + config_file="config.yaml", + output_dir="./gasflux_results" + ) + print("处理完成成功!") + +except Exception as e: + print(f"Error: {e}") +``` + +#### 异步版本 (使用 asyncio) +```python +import asyncio +import aiohttp +import aiofiles +from pathlib import Path + +class AsyncGasFluxClient: + def __init__(self, base_url="http://localhost:5000"): + self.base_url = base_url.rstrip('/') + + async def upload_and_process(self, data_file, config_file=None, output_dir="./results"): + async with aiohttp.ClientSession() as session: + # 准备文件上传 + data = aiohttp.FormData() + data.add_field('file', open(data_file, 'rb'), filename=Path(data_file).name) + if config_file and Path(config_file).exists(): + data.add_field('config', open(config_file, 'rb'), filename=Path(config_file).name) + + # 上传文件 + print(f"Uploading {data_file}...") + async with session.post(f"{self.base_url}/upload", data=data) as response: + result = await response.json() + if response.status != 202: + raise Exception(f"Upload failed: {result.get('error', 'Unknown error')}") + + task_id = result['job_id'] + print(f"任务已创建: {task_id}") + + # 监控处理状态 + while True: + async with session.get(f"{self.base_url}/task/{task_id}") as response: + status = await response.json() + + print(f"Status: {status['status']} - {status['message']}") + + if status['status'] == 'completed': + # 下载结果文件 + Path(output_dir).mkdir(exist_ok=True) + for result_file in status['results']: + download_url = f"{self.base_url}{result_file['download_url']}" + output_path = Path(output_dir) / result_file['name'] + + print(f"Downloading {result_file['name']}...") + async with session.get(download_url) as response: + async with aiofiles.open(output_path, 'wb') as f: + await f.write(await response.read()) + + print(f"Processing completed! Results saved to {output_dir}") + return status + + elif status['status'] == 'failed': + error_msg = status.get('error', 'Unknown error') + raise Exception(f"Task failed: {error_msg}") + + await asyncio.sleep(3) + +# 使用异步客户端 +async def main(): + client = AsyncGasFluxClient() + try: + result = await client.upload_and_process( + data_file="large_dataset.xlsx", + config_file="config.yaml", + output_dir="./async_results" + ) + print("Async processing completed!") + except Exception as e: + print(f"Error: {e}") + +# 运行异步示例 +# asyncio.run(main()) +``` + +### JavaScript/Node.js 示例 + +#### 完整实现 +```javascript +const axios = require('axios'); +const FormData = require('form-data'); +const fs = require('fs').promises; +const path = require('path'); + +class GasFluxAPI { + constructor(baseURL = 'http://localhost:5000') { + this.baseURL = baseURL.replace(/\/$/, ''); + this.client = axios.create({ + baseURL: this.baseURL, + timeout: 30000 + }); + } + + async checkHealth() { + try { + const response = await this.client.get('/health'); + return response.data; + } catch (error) { + throw new Error(`Health check failed: ${error.message}`); + } + } + + async uploadFile(dataFilePath, configFilePath = null) { + const formData = new FormData(); + + // 添加数据文件 + if (!await fs.access(dataFilePath).then(() => true).catch(() => false)) { + throw new Error(`Data file not found: ${dataFilePath}`); + } + formData.append('file', fs.createReadStream(dataFilePath), { + filename: path.basename(dataFilePath) + }); + + // 添加配置文件(如果提供) + if (configFilePath) { + if (!await fs.access(configFilePath).then(() => true).catch(() => false)) { + console.warn(`Config file not found: ${configFilePath}, skipping...`); + } else { + formData.append('config', fs.createReadStream(configFilePath), { + filename: path.basename(configFilePath) + }); + } + } + + try { + const response = await this.client.post('/upload', formData, { + headers: formData.getHeaders(), + maxContentLength: Infinity, + maxBodyLength: Infinity + }); + + return response.data; + } catch (error) { + if (error.response) { + throw new Error(`Upload failed: ${error.response.data.error}`); + } + throw error; + } + } + + async getTaskStatus(taskId) { + try { + const response = await this.client.get(`/task/${taskId}`); + return response.data; + } catch (error) { + if (error.response && error.response.status === 404) { + throw new Error(`任务未找到: ${taskId}`); + } + throw error; + } + } + + async downloadFile(downloadUrl, outputPath) { + try { + const response = await this.client.get(downloadUrl, { + responseType: 'stream' + }); + + const writer = fs.createWriteStream(outputPath); + response.data.pipe(writer); + + return new Promise((resolve, reject) => { + writer.on('finish', resolve); + writer.on('error', reject); + }); + } catch (error) { + throw new Error(`Download failed: ${error.message}`); + } + } + + async processData(dataFilePath, configFilePath = null, outputDir = './results', pollInterval = 3000) { + try { + // 1. 检查 API 健康状态 + console.log('Checking API health...'); + const health = await this.checkHealth(); + console.log(`API Status: ${health.status}`); + + // 2. 上传文件 + console.log(`Uploading ${dataFilePath}...`); + const uploadResult = await this.uploadFile(dataFilePath, configFilePath); + const taskId = uploadResult.job_id; + console.log(`任务已创建: ${taskId}`); + + // 3. 监控处理状态 + console.log('Monitoring processing status...'); + while (true) { + const status = await this.getTaskStatus(taskId); + + console.log(`[${new Date().toISOString()}] Status: ${status.status} - ${status.message}`); + + if (status.status === 'completed') { + console.log('处理完成成功!'); + + // 4. 下载结果文件 + console.log('Downloading result files...'); + await fs.mkdir(outputDir, { recursive: true }); + + for (const resultFile of status.results) { + const downloadUrl = `${this.baseURL}${resultFile.download_url}`; + const outputPath = path.join(outputDir, resultFile.name); + + console.log(`Downloading ${resultFile.name}...`); + await this.downloadFile(downloadUrl, outputPath); + console.log(`Saved to ${outputPath}`); + } + + return { + taskId, + status: status.status, + results: status.results, + outputDir + }; + + } else if (status.status === 'failed') { + const errorMsg = status.error || 'Unknown error'; + throw new Error(`处理失败: ${errorMsg}`); + } + + // 等待后重试 + await new Promise(resolve => setTimeout(resolve, pollInterval)); + } + + } catch (error) { + console.error(`Error in processData: ${error.message}`); + throw error; + } + } +} + +// 使用示例 +async function main() { + const api = new GasFluxAPI(); + + try { + const result = await api.processData( + 'data.xlsx', + 'config.yaml', // 可选 + './gasflux_results', + 5000 // 5秒检查一次状态 + ); + + console.log('All done!', result); + + } catch (error) { + console.error('处理失败:', error.message); + process.exit(1); + } +} + +// 如果直接运行此文件 +if (require.main === module) { + main(); +} + +module.exports = GasFluxAPI; +``` + +### cURL 命令示例 + +#### 基本上传和监控 +```bash +#!/bin/bash + +# API 基础 URL +API_URL="http://localhost:5000" + +# 检查健康状态 +echo "Checking API health..." +curl -s "${API_URL}/health" | jq '.' + +# 上传文件 +echo "Uploading data file..." +UPLOAD_RESPONSE=$(curl -s -X POST \ + -F "file=@data.xlsx" \ + -F "config=@config.yaml" \ + "${API_URL}/upload") + +echo "Upload response: $UPLOAD_RESPONSE" + +# 提取任务 ID +TASK_ID=$(echo "$UPLOAD_RESPONSE" | jq -r '.job_id') + +if [ "$TASK_ID" = "null" ] || [ -z "$TASK_ID" ]; then + echo "Upload failed!" + exit 1 +fi + +echo "Task ID: $TASK_ID" + +# 监控任务状态 +echo "Monitoring task status..." +while true; do + STATUS_RESPONSE=$(curl -s "${API_URL}/task/${TASK_ID}") + STATUS=$(echo "$STATUS_RESPONSE" | jq -r '.status') + MESSAGE=$(echo "$STATUS_RESPONSE" | jq -r '.message') + + echo "[$(date '+%Y-%m-%d %H:%M:%S')] Status: $STATUS - $MESSAGE" + + if [ "$STATUS" = "completed" ]; then + echo "Processing completed!" + + # 下载结果文件 + echo "Downloading result files..." + mkdir -p results + + echo "$STATUS_RESPONSE" | jq -r '.results[].download_url' | while read -r download_url; do + filename=$(basename "$download_url") + echo "Downloading $filename..." + curl -s -o "results/$filename" "${API_URL}${download_url}" + done + + echo "All files downloaded to ./results/" + break + + elif [ "$STATUS" = "failed" ]; then + ERROR=$(echo "$STATUS_RESPONSE" | jq -r '.error // "Unknown error"') + echo "Task failed: $ERROR" + exit 1 + fi + + sleep 3 +done +``` + +## 🔧 故障排除指南 + +### 常见问题和解决方案 + +#### 1. 连接问题 +**问题**: 无法连接到 API 服务器 +```bash +curl: (7) 连接被拒绝,无法连接到 localhost 端口 5000 +``` + +**解决方案**: +- 检查服务器是否正在运行:`ps aux | grep gasflux` +- 验证端口配置:检查环境变量 `GASFLUX_PORT` +- 确认防火墙设置:`sudo ufw status` 或 `sudo firewall-cmd --list-all` + +#### 2. 文件上传失败 + +**问题**: 文件上传被拒绝 +```json +{"error": "无效的数据文件类型。只允许 .xlsx 和 .xls 格式。"} +``` + +**解决方案**: +- 检查文件扩展名(必须是 .xlsx 或 .xls) +- 验证文件不是空的 +- 确保文件大小不超过 100MB + +**问题**: 文件过大 +```json +{"error": "文件过大。最大尺寸为 100MB。"} +``` + +**解决方案**: +- 压缩数据文件 +- 分割成多个较小的文件 +- 联系管理员增加文件大小限制 + +#### 3. 任务处理问题 + +**问题**: 任务长时间处于 pending 状态 +```json +{"status": "pending", "message": "Task queued for processing"} +``` + +**解决方案**: +- 检查系统负载:`GET /stats` +- 查看服务器资源使用情况 +- 等待队列处理或联系管理员 + +**问题**: 处理失败 +```json +{ + "status": "failed", + "error": "处理失败: Invalid data format in column 'temperature'" +} +``` + +**解决方案**: +- 检查输入数据格式和列名 +- 验证数据范围(温度、压力等) +- 查看详细的错误信息和日志 + +#### 4. 文件下载问题 + +**问题**: 下载失败 +```json +{"error": "File not found or access denied"} +``` + +**解决方案**: +- 确认任务已完成(status: "completed") +- 检查下载 URL 格式 +- 验证文件路径是否存在 + +#### 5. 服务器性能问题 + +**问题**: 响应缓慢或超时 + +**解决方案**: +- 检查系统资源:`GET /stats` +- 查看并发任务数量 +- 监控内存和 CPU 使用率 +- 考虑增加服务器资源或优化配置 + +### 调试技巧 + +#### 启用详细日志 +```bash +# 设置环境变量启用 DEBUG 模式 +export GASFLUX_LOG_LEVEL=DEBUG +export GASFLUX_DEBUG=true + +# 重启服务器 +python server_waitress.py +``` + +#### 查看实时日志 +```bash +# 监控日志文件 +tail -f logs/gasflux_api.log + +# 过滤特定任务的日志 +tail -f logs/gasflux_api.log | grep "task:abc123" +``` + +#### 使用健康检查进行诊断 +```bash +# 基本健康检查 +curl -s http://localhost:5000/health | jq '.' + +# 详细统计信息 +curl -s http://localhost:5000/stats | jq '.' + +# 配置信息 +curl -s http://localhost:5000/config | jq '.' +``` + +## 🛡️ 安全考虑 + +### 数据保护 +- **文件类型验证**: 只接受指定的文件类型 (.xlsx, .xls, .yaml, .yml) +- **路径遍历保护**: 防止通过 `../` 等路径访问敏感文件 +- **文件大小限制**: 防止拒绝服务攻击 + +### 访问控制 +- **无认证设计**: 适用于内部网络或受控环境 +- **IP 白名单**: 可通过反向代理实现 +- **HTTPS 推荐**: 在生产环境中使用 HTTPS + +### 数据清理 +- **自动清理**: 过期的任务和文件会被自动删除 +- **配置选项**: 可通过环境变量调整清理间隔和过期时间 + +## 📈 最佳实践 + +### 客户端实现 + +#### 1. 错误处理 +```python +def safe_api_call(url, max_retries=3, backoff_factor=2): + for attempt in range(max_retries): + try: + response = requests.get(url, timeout=30) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + if attempt == max_retries - 1: + raise e + wait_time = backoff_factor ** attempt + print(f"Request failed, retrying in {wait_time}s...") + time.sleep(wait_time) +``` + +#### 2. 状态轮询优化 +```python +def monitor_task_efficiently(task_id, max_wait_time=3600): + start_time = time.time() + check_interval = 2 # 初始检查间隔 + + while time.time() - start_time < max_wait_time: + status = get_task_status(task_id) + + if status['status'] in ['completed', 'failed']: + return status + + # 根据任务阶段调整检查间隔 + if 'progress' in status: + stage = status['progress'].get('stage', '') + if 'data_processing' in stage: + check_interval = 5 # 数据处理阶段检查频率降低 + elif 'report_generation' in stage: + check_interval = 10 # 报告生成阶段进一步降低 + + time.sleep(check_interval) + + raise TimeoutError(f"Task monitoring timed out after {max_wait_time}s") +``` + +#### 3. 大文件处理 +```python +def upload_large_file(file_path, chunk_size=1024*1024): # 1MB chunks + file_size = os.path.getsize(file_path) + + # 对于大文件,考虑压缩或分块上传 + if file_size > 50*1024*1024: # 50MB + print(f"Large file detected ({file_size/1024/1024:.1f}MB)") + print("Consider compressing the data or splitting into smaller files") + + # 标准上传 + with open(file_path, 'rb') as f: + files = {'file': f} + response = requests.post('http://localhost:5000/upload', files=files) + return response.json() +``` + +### 服务器部署 + +#### 生产环境配置 +```bash +# 环境变量配置 +export GASFLUX_HOST=0.0.0.0 +export GASFLUX_PORT=5000 +export GASFLUX_LOG_LEVEL=INFO +export GASFLUX_MAX_CONTENT_LENGTH=104857600 # 100MB + +# 使用进程管理器 +# systemd 服务示例 +cat > /etc/systemd/system/gasflux.service << EOF +[Unit] +Description=GasFlux Web API +After=network.target + +[Service] +User=gasflux +Group=gasflux +WorkingDirectory=/opt/gasflux +ExecStart=/opt/gasflux/venv/bin/python server_waitress.py +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +EOF + +# 启用和启动服务 +sudo systemctl enable gasflux +sudo systemctl start gasflux +``` + +#### 监控和告警 +```bash +#!/bin/bash +# 健康检查脚本 +API_URL="http://localhost:5000" + +# 检查健康状态 +if ! curl -f -s "${API_URL}/health" > /dev/null; then + echo "API is unhealthy, sending alert..." + # 发送告警邮件、Slack 通知等 +fi + +# 检查队列长度 +STATS=$(curl -s "${API_URL}/stats") +ACTIVE_TASKS=$(echo "$STATS" | jq '.summary.active_tasks') + +if [ "$ACTIVE_TASKS" -gt 10 ]; then + echo "High task queue detected: $ACTIVE_TASKS active tasks" + # 发送告警 +fi +``` + +## 📚 附录 + +### 支持的环境变量 + +| 变量名 | 默认值 | 描述 | +|--------|--------|------| +| `GASFLUX_HOST` | `0.0.0.0` | 服务器监听地址 | +| `GASFLUX_PORT` | `5000` | 服务器监听端口 | +| `GASFLUX_DEBUG` | `false` | 调试模式开关 | +| `GASFLUX_UPLOAD_FOLDER` | `web_api_data/uploads` | 上传文件存储目录 | +| `GASFLUX_OUTPUT_FOLDER` | `web_api_data/outputs` | 输出文件存储目录 | +| `GASFLUX_MAX_CONTENT_LENGTH` | `104857600` | 最大文件大小 (字节) | +| `GASFLUX_LOG_LEVEL` | `INFO` | 日志级别 | +| `GASFLUX_LOG_FILE` | `logs/gasflux_api.log` | 日志文件路径 | +| `GASFLUX_CORS_ORIGINS` | `["*"]` | 允许的 CORS 源 | +| `GASFLUX_TASK_CLEANUP_INTERVAL` | `3600` | 任务清理间隔 (秒) | +| `GASFLUX_MAX_TASK_AGE` | `86400` | 任务最大年龄 (秒) | +| `GASFLUX_THREADS` | `8` | Waitress 线程数 | +| `GASFLUX_CONNECTION_LIMIT` | `100` | 最大连接数 | +| `GASFLUX_CHANNEL_TIMEOUT` | `300` | 通道超时 (秒) | + +### API 响应时间基准 + +- `/health`: < 100ms +- `/stats`: < 500ms +- `/config`: < 200ms +- `/upload`: < 2s (文件处理时间) +- `/task/{id}`: < 300ms +- `/download/{file}`: 根据文件大小 (通常 < 5s) + +### 文件格式规范 + +#### 数据文件要求 +- **格式**: Excel (.xlsx 或 .xls) +- **必需列**: latitude, longitude, height_ato, windspeed, winddir, temperature, pressure +- **可选列**: ch4, co2, c2h6 等气体浓度 +- **数据类型**: 数值型 (float/int) +- **缺失值**: NaN 或空值 + +#### 配置文件格式 +```yaml +output_dir: ~/gasflux_reports + +required_cols: + latitude: [-90, 90] + longitude: [-180, 180] + height_ato: [-200, 500] + windspeed: [0, 50] + winddir: [0, 360] + temperature: [-50, 60] + pressure: [900, 1100] + +gases: + ch4: [1.5, 500] + co2: [300, 5000] + c2h6: [-0.5, 10] + +strategies: + background: "algorithmic" + sensor: "insitu" + spatial: "curtain" + interpolation: "kriging" +``` + +--- + +*最后更新: 2026年1月14日* + +*GasFlux Web API 版本: 1.0.0* +*文档维护: API 开发团队* \ No newline at end of file diff --git a/API_ENDPOINTS.md b/API_ENDPOINTS.md new file mode 100644 index 0000000..456ab7e --- /dev/null +++ b/API_ENDPOINTS.md @@ -0,0 +1,782 @@ +# GasFlux Web API 端点参考 + +## 概述 + +GasFlux Web API 是一个基于 Flask 的 RESTful API,用于上传数据文件、执行气体通量分析处理,并下载处理结果。 + +**基础信息:** +- **基础 URL**: `http://localhost:5000` +- **认证**: 无需认证 +- **数据格式**: JSON +- **文件大小限制**: 100MB +- **支持的文件类型**: `.xlsx`, `.xls`, `.yaml`, `.yml` + +--- + +## 🔍 监控和健康检查 + +### 1. 获取健康状态 +**端点**: `GET /health` + +**描述**: 获取 API 的健康状态、系统信息和性能指标。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "健康检查完成", + "data": { + "status": "healthy", + "version": "1.0.0", + "timestamp": 1705257600.123, + "uptime": "2h 30m 15s", + "storage": { + "uploads_writable": true, + "outputs_writable": true + }, + "tasks": { + "active_count": 2, + "total_tracked": 15, + "total_processed": 13, + "success_rate_percent": 92.31 + }, + "performance": { + "requests_per_second": 0.08, + "avg_response_time_ms": 234.56, + "error_rate_percent": 1.5 + } + } +} +``` + +**响应示例** (503): +```json +{ + "code": 503, + "message": "服务不可用", + "data": { + "status": "degraded", + "version": "1.0.0", + "timestamp": 1705257600.123, + "uptime": "1h 30m 45s", + "storage": { + "uploads_writable": true, + "outputs_writable": true + }, + "tasks": { + "active_count": 0, + "total_tracked": 5, + "total_processed": 3, + "success_rate_percent": 60.0 + }, + "performance": { + "requests_per_second": 0.12, + "avg_response_time_ms": 145.67, + "error_rate_percent": 50.0 + }, + "issues": [ + "错误率过高 (50.0%)", + "活跃任务数量过多 (25)" + ] + } +} +``` + +**状态码**: +- `200`: API 健康 +- `503`: API 不健康 + +--- + +### 2. 获取系统统计信息 +**端点**: `GET /stats` + +**描述**: 获取详细的 API 统计信息、性能指标和系统监控数据。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "统计信息获取成功", + "data": { + "summary": { + "uptime_seconds": 3600.5, + "uptime_formatted": "1h 0m 0s", + "requests_total": 150, + "requests_per_second": 0.04, + "error_rate_percent": 2.0, + "active_tasks": 1 + }, + "requests": { + "by_method": { + "GET": 120, + "POST": 30 + }, + "by_status": { + "200": 145, + "400": 3, + "500": 2 + }, + "top_endpoints": { + "/task/abc123": 45, + "/health": 30, + "/": 25 + } + }, + "tasks": { + "total_created": 25, + "total_completed": 20, + "total_failed": 2, + "success_rate_percent": 90.91, + "by_status": { + "pending": 1, + "processing": 1, + "completed": 20, + "failed": 2 + } + }, + "performance": { + "avg_response_time_ms": 245.67, + "max_response_time_ms": 1250.34, + "min_response_time_ms": 12.45 + }, + "system": { + "memory_usage_percent": 45.2, + "memory_used_gb": 7.3, + "memory_total_gb": 16.0, + "disk_usage_percent": 23.1, + "disk_used_gb": 46.8, + "disk_total_gb": 203.2 + }, + "recent_tasks": [ + { + "task_id": "abc123-def456", + "status": "completed", + "age_seconds": 45.2, + "message": "处理完成成功" + } + ] + } +} +``` + +**状态码**: +- `200`: 成功 + +--- + +### 3. 重置统计信息 +**端点**: `POST /stats/reset` + +**描述**: 重置所有 API 统计数据(管理员功能)。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "统计信息重置成功", + "data": { + "timestamp": 1705257600.123 + } +} +``` + +**状态码**: +- `200`: 成功 + +--- + +### 4. 获取配置信息 +**端点**: `GET /config` + +**描述**: 获取当前应用配置信息和支持的环境变量。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "配置信息获取成功", + "data": { + "configuration": { + "host": "0.0.0.0", + "port": 5000, + "debug": false, + "base_dir": "/app", + "upload_folder": "/app/web_api_data/uploads", + "output_folder": "/app/web_api_data/outputs", + "max_content_length": 104857600, + "log_level": "INFO", + "log_file": "logs/gasflux_api.log", + "cors_origins": ["*"], + "task_cleanup_interval": 3600, + "max_task_age": 86400, + "threads": 8, + "connection_limit": 100, + "channel_timeout": 300 + }, + "environment_variables": { + "supported": [ + "GASFLUX_HOST", "GASFLUX_PORT", "GASFLUX_DEBUG", + "GASFLUX_UPLOAD_FOLDER", "GASFLUX_OUTPUT_FOLDER", + "GASFLUX_MAX_CONTENT_LENGTH", "GASFLUX_LOG_LEVEL", + "GASFLUX_LOG_FILE", "GASFLUX_CORS_ORIGINS", + "GASFLUX_TASK_CLEANUP_INTERVAL", "GASFLUX_MAX_TASK_AGE", + "GASFLUX_THREADS", "GASFLUX_CONNECTION_LIMIT", + "GASFLUX_CHANNEL_TIMEOUT" + ], + "current_values": { + "GASFLUX_HOST": "0.0.0.0", + "GASFLUX_PORT": "5000", + "GASFLUX_DEBUG": "false" + } + } + } +} +``` + +**状态码**: +- `200`: 成功 + +--- + +## 📤 文件上传和管理 + +### 5. 文件上传和处理 +**端点**: `POST /upload` + +**描述**: 上传数据文件并启动异步处理任务。 + +**请求参数** (multipart/form-data): +- `file` (必需): 数据文件 (.xlsx 或 .xls 格式) +- `config` (可选): 配置文件 (.yaml 或 .yml 格式) + +**响应示例** (202): +```json +{ + "code": 202, + "message": "任务已接受并加入处理队列", + "data": { + "status": "accepted", + "job_id": "abc123-def456-ghi789", + "task_status_url": "/task/abc123-def456-ghi789" + } +} +``` + +**错误响应示例** (400): +```json +{ + "code": 400, + "message": "无效的数据文件类型。只允许 .xlsx 和 .xls 格式。", + "data": {} +} +``` + +**错误响应示例** (413): +```json +{ + "code": 413, + "message": "文件过大。最大尺寸为 100MB。", + "data": {} +} +``` + +**状态码**: +- `202`: 任务已接受 +- `400`: 请求参数错误 +- `413`: 文件过大 + +--- + +## 📊 任务管理和监控 + +### 6. 分页查询任务列表 +**端点**: `GET /tasks` + +**描述**: 分页查询所有任务,支持按状态过滤、排序和分页。返回体为前端友好的瘦身结构:仅包含任务状态信息;当任务已完成时会包含 `downloads` 直达下载链接。 + +**查询参数**: +- `page` (可选): 页码 (默认: 1) +- `page_size` (可选): 每页任务数量 (默认: 20, 最大: 100) +- `sort_by` (可选): 排序字段 ('created_at', 'updated_at', 'status') (默认: 'updated_at') +- `sort_order` (可选): 排序顺序 ('asc', 'desc') (默认: 'desc') +- `status` (可选): 按任务状态过滤,支持多个状态用逗号分隔 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "任务列表查询成功", + "data": { + "tasks": [ + { + "task_id": "abc123-def456-ghi789", + "status": "completed", + "message": "Processing completed successfully", + "updated_at": 1705257700.456, + "downloads": { + "data_xlsx": "/download/abc123-def456-ghi789/08_34_01_5m.processed_data.xlsx", + "report_ch4": "/download/abc123-def456-ghi789/08_34_01_5m.processed_ch4_report.html", + "report_co2": "/download/abc123-def456-ghi789/08_34_01_5m.processed_co2_report.html", + "config": "/download/abc123-def456-ghi789/08_34_01_5m.processed_config.yaml", + "metadata": "/download/abc123-def456-ghi789/08_34_01_5m.processed_output_vars.json" + } + }, + { + "task_id": "xyz789-abc123-def456", + "status": "processing", + "message": "正在进行数据分析...", + "updated_at": 1705257650.321 + } + ], + "total": 25, + "page": 1, + "page_size": 20, + "total_pages": 2, + "has_next": true, + "has_prev": false + } +} +``` + +**状态码**: +- `200`: 成功 +- `400`: 参数无效 + +--- + +### 7. 获取任务池统计信息 +**端点**: `GET /tasks/stats` + +**描述**: 获取任务池的统计信息,包括各状态任务数量、活跃任务数等。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "任务池统计信息查询成功", + "data": { + "total_tasks": 25, + "status_counts": { + "pending": 3, + "processing": 2, + "completed": 18, + "failed": 2 + }, + "active_tasks": 2, + "queued_tasks": 3, + "completed_tasks": 18, + "failed_tasks": 2 + } +} +``` + +**状态码**: +- `200`: 成功 + +--- + +### 8. 获取活跃任务列表 +**端点**: `GET /tasks/active` + +**描述**: 获取当前正在处理的任务列表(瘦身结构)。当列表中包含已完成任务时会附带 `downloads` 直达下载链接。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "活跃任务查询成功", + "data": { + "active_tasks": [ + { + "task_id": "abc123-def456-ghi789", + "status": "processing", + "message": "正在生成报告...", + "updated_at": 1705257650.321 + }, + { + "task_id": "completed-task-123", + "status": "completed", + "message": "Processing completed successfully", + "updated_at": 1705257550.456, + "downloads": { + "data_xlsx": "/download/completed-task-123/processed_data.xlsx", + "report_html": "/download/completed-task-123/data_report.html" + } + } + ], + "count": 2 + } +} +``` + +**状态码**: +- `200`: 成功 + +--- + +### 9. 获取队列任务列表 +**端点**: `GET /tasks/queue` + +**描述**: 获取等待处理的任务队列(瘦身结构)。当列表中包含已完成任务时会附带 `downloads` 直达下载链接。 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "队列任务查询成功", + "data": { + "queued_tasks": [ + { + "task_id": "xyz789-abc123-def456", + "status": "pending", + "message": "等待处理", + "updated_at": 1705257400.123 + }, + { + "task_id": "completed-queued-task", + "status": "completed", + "message": "Processing completed successfully", + "updated_at": 1705257450.321, + "downloads": { + "data_xlsx": "/download/completed-queued-task/processed_data.xlsx", + "report_html": "/download/completed-queued-task/analysis_report.html" + } + } + ], + "count": 2, + "queue_position_info": "任务按创建时间排序,较早的任务优先处理" + } +} +``` + +**状态码**: +- `200`: 成功 + +--- + +### 10. 查询任务状态 +**端点**: `GET /task/{task_id}` + +**描述**: 查询异步处理任务的当前状态和进度信息。 + +**路径参数**: +- `task_id`: 任务 ID (UUID 格式) + +**响应示例** - 处理中 (200): +```json +{ + "code": 200, + "message": "任务查询成功", + "data": { + "task_id": "abc123-def456-ghi789", + "status": "processing", + "message": "GasFlux 分析完成,正在生成报告...", + "updated_at": 1705257600.123 + } +} +``` + +**响应示例** - 处理完成 (200): +```json +{ + "code": 200, + "message": "任务查询成功", + "data": { + "task_id": "abc123-def456-ghi789", + "status": "completed", + "message": "处理完成成功", + "updated_at": 1705257600.123, + "results": [ + { + "name": "08_34_01_5m.processed_ch4_report.html", + "rel_path": "abc123-def456-ghi789/08_34_01_5m.processed_ch4_report.html", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed_ch4_report.html", + "size": 245760, + "type": "report" + }, + { + "name": "08_34_01_5m.processed_data.xlsx", + "rel_path": "abc123-def456-ghi789/08_34_01_5m.processed_data.xlsx", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed_data.xlsx", + "size": 153600, + "type": "data" + }, + { + "name": "08_34_01_5m.processed_config.yaml", + "rel_path": "abc123-def456-ghi789/08_34_01_5m.processed_config.yaml", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed_config.yaml", + "size": 2048, + "type": "config" + }, + { + "name": "08_34_01_5m.processed_output_vars.json", + "rel_path": "abc123-def456-ghi789/08_34_01_5m.processed_output_vars.json", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed_output_vars.json", + "size": 4096, + "type": "metadata" + } + ], + "downloads": { + "data_xlsx": "/download/abc123-def456-ghi789/08_34_01_5m.processed_data.xlsx", + "report_ch4": "/download/abc123-def456-ghi789/08_34_01_5m.processed_ch4_report.html", + "report_co2": "/download/abc123-def456-ghi789/08_34_01_5m.processed_co2_report.html", + "config": "/download/abc123-def456-ghi789/08_34_01_5m.processed_config.yaml", + "metadata": "/download/abc123-def456-ghi789/08_34_01_5m.processed_output_vars.json" + } + } +} +``` + +**响应示例** - 任务不存在 (404): +```json +{ + "code": 404, + "message": "任务未找到", + "data": {} +} +``` + +**状态码**: +- `200`: 成功 +- `404`: 任务不存在 + +--- + +### 11. 更新任务状态 +**端点**: `PUT /task/{task_id}` + +**描述**: 更新任务的状态、信息或优先级。 + +**路径参数**: +- `task_id`: 任务 ID (UUID 格式) + +**请求参数** (JSON): +- `status` (可选): 新的任务状态 +- `message` (可选): 状态消息或错误描述 +- `priority` (可选): 任务优先级 (normal/high/low) + +**响应示例** (200): +```json +{ + "code": 200, + "message": "任务更新成功", + "data": { + "task_id": "abc123-def456-ghi789", + "status": "updated", + "task_info": { + "status": "completed", + "message": "手动标记为完成", + "updated_at": 1705257600.123, + "priority": "normal" + } + } +} +``` + +**错误响应示例** (404): +```json +{ + "code": 404, + "message": "任务未找到", + "data": {} +} +``` + +**错误响应示例** (400): +```json +{ + "code": 400, + "message": "无效状态。必须是以下之一: pending, processing, completed, failed", + "data": {} +} +``` + +**状态码**: +- `200`: 成功 +- `400`: 无效请求 +- `404`: 任务不存在 + +--- + +### 12. 删除任务 +**端点**: `DELETE /task/{task_id}` + +**描述**: 删除任务及其所有相关的文件和数据。 + +**路径参数**: +- `task_id`: 任务 ID (UUID 格式) + +**响应示例** (200): +```json +{ + "code": 200, + "message": "任务及相关文件删除成功", + "data": { + "task_id": "abc123-def456-ghi789", + "status": "deleted", + "details": { + "folders_deleted": 1, + "total_size_deleted": 307200, + "task_status": "completed" + } + } +} +``` + +**错误响应示例** (404): +```json +{ + "code": 404, + "message": "任务未找到", + "data": {} +} +``` + +**错误响应示例** (409): +```json +{ + "code": 409, + "message": "无法删除当前正在处理或等待处理的任务", + "data": { + "task_status": "processing" + } +} +``` + +**状态码**: +- `200`: 成功 +- `404`: 任务不存在 +- `409`: 任务正在处理 + +--- + +## 📋 报告管理和查询 + +### 13. 分页查询已生成报告 +**端点**: `GET /reports` + +**描述**: 分页查询所有已生成的处理报告,支持排序和过滤。 + +**查询参数**: +- `page` (可选): 页码 (默认: 1) +- `per_page` (可选): 每页报告数量 (默认: 20, 最大: 100) +- `sort_by` (可选): 排序字段 (默认: created_at) +- `sort_order` (可选): 排序顺序 (默认: desc) +- `status` (可选): 按任务状态过滤 + +**响应示例** (200): +```json +{ + "code": 200, + "message": "报告列表获取成功", + "data": { + "reports": [ + { + "task_id": "abc123-def456-ghi789", + "report_name": "08_34_01_5m", + "status": "completed", + "created_at": 1705257600.123, + "file_count": 4, + "total_size": 307200, + "processing_time_seconds": 125.67, + "main_report": { + "name": "08_34_01_5m.processed_ch4_report.html", + "size": 245760, + "type": "report", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_ch4_report.html" + }, + "all_files": [ + { + "name": "08_34_01_5m.processed_ch4_report.html", + "size": 245760, + "type": "report", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_ch4_report.html" + }, + { + "name": "08_34_01_5m.processed_data.csv", + "size": 153600, + "type": "data", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_data.csv" + }, + { + "name": "08_34_01_5m.processed_config.yaml", + "size": 2048, + "type": "config", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_config.yaml" + }, + { + "name": "08_34_01_5m.processed_output_vars.json", + "size": 4096, + "type": "metadata", + "download_url": "/download/abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run/08_34_01_5m.processed_output_vars.json" + } + ], + "run_directory": "abc123-def456-ghi789/08_34_01_5m.processed/2026-01-14_10-33-29-961698_processing_run" + } + ], + "pagination": { + "page": 1, + "per_page": 20, + "total_reports": 45, + "total_pages": 3, + "has_next": true, + "has_prev": false + }, + "filters": { + "sort_by": "created_at", + "sort_order": "desc", + "status": null + } + } +} +``` + +**错误响应示例** (400): +```json +{ + "code": 400, + "message": "Invalid parameter: per_page must be between 1 and 100", + "data": {} +} +``` + +**状态码**: +- `200`: 成功 +- `400`: 参数无效 + +--- + +## 📁 文件下载 + +### 14. 下载处理结果 +**端点**: `GET /download/{filename}` + +**描述**: 下载处理后的结果文件,支持传入完整文件路径。 + +**路径参数**: +- `filename`: 文件的完整路径或相对路径 (包含任务ID) + +**状态码**: +- `200`: 成功下载文件 +- `403`: 访问被拒绝 +- `404`: 文件不存在 +- `400`: 路径不是文件 + +--- + +## 🌐 Web 界面 + +### 15. Web 管理界面 +**端点**: `GET /` + +**描述**: 访问用户友好的 Web 界面,支持文件上传、任务监控和结果下载。 + +**状态码**: +- `200`: 成功 + +--- + +*最后更新: 2026年1月29日* + +*GasFlux Web API 版本: 1.0.0* \ No newline at end of file diff --git a/ENVIRONMENT_VARIABLES.md b/ENVIRONMENT_VARIABLES.md new file mode 100644 index 0000000..e3a709b --- /dev/null +++ b/ENVIRONMENT_VARIABLES.md @@ -0,0 +1,255 @@ +# GasFlux Web API - 环境变量配置指南 + +本文档介绍如何使用环境变量来配置 GasFlux Web API 的行为。 + +## 概述 + +GasFlux 支持通过环境变量进行灵活配置,无需修改代码即可适应不同的部署环境和需求。 + +## 环境变量列表 + +### 服务器配置 + +| 变量名 | 默认值 | 描述 | +|--------|--------|------| +| `GASFLUX_HOST` | `0.0.0.0` | 服务器监听主机地址 | +| `GASFLUX_PORT` | `5000` | 服务器监听端口 | +| `GASFLUX_DEBUG` | `false` | 是否启用调试模式 (true/false) | + +### 目录配置 + +| 变量名 | 默认值 | 描述 | +|--------|--------|------| +| `GASFLUX_UPLOAD_FOLDER` | `web_api_data/uploads` | 上传文件存储目录 | +| `GASFLUX_OUTPUT_FOLDER` | `web_api_data/outputs` | 处理结果输出目录 | + +### 文件和性能配置 + +| 变量名 | 默认值 | 描述 | +|--------|--------|------| +| `GASFLUX_MAX_CONTENT_LENGTH` | `104857600` (100MB) | 最大上传文件大小(字节) | +| `GASFLUX_THREADS` | `8` | Waitress 服务器线程数 | +| `GASFLUX_CONNECTION_LIMIT` | `100` | 最大并发连接数 | +| `GASFLUX_CHANNEL_TIMEOUT` | `300` | 连接超时时间(秒) | + +### 日志配置 + +| 变量名 | 默认值 | 描述 | +|--------|--------|------| +| `GASFLUX_LOG_LEVEL` | `INFO` | 日志级别 (DEBUG/INFO/WARNING/ERROR/CRITICAL) | +| `GASFLUX_LOG_FILE` | `logs/gasflux_api.log` | 日志文件路径 | + +### 安全和跨域配置 + +| 变量名 | 默认值 | 描述 | +|--------|--------|------| +| `GASFLUX_CORS_ORIGINS` | `*` | 允许的 CORS 源,用逗号分隔 | + +### 任务管理配置 + +| 变量名 | 默认值 | 描述 | +|--------|--------|------| +| `GASFLUX_TASK_CLEANUP_INTERVAL` | `3600` | 任务清理检查间隔(秒) | +| `GASFLUX_MAX_TASK_AGE` | `86400` | 任务最大保留时间(秒,24小时) | + +## 配置示例 + +### 开发环境 + +```bash +# 开发环境配置 +export GASFLUX_HOST=127.0.0.1 +export GASFLUX_PORT=5000 +export GASFLUX_DEBUG=true +export GASFLUX_LOG_LEVEL=DEBUG +``` + +### 生产环境 + +```bash +# 生产环境配置 +export GASFLUX_HOST=0.0.0.0 +export GASFLUX_PORT=80 +export GASFLUX_DEBUG=false +export GASFLUX_LOG_LEVEL=INFO +export GASFLUX_THREADS=16 +export GASFLUX_CONNECTION_LIMIT=200 +export GASFLUX_MAX_CONTENT_LENGTH=524288000 # 500MB +``` + +### Docker 部署 + +```bash +# Docker 环境变量 +docker run -p 80:5000 \ + -e GASFLUX_HOST=0.0.0.0 \ + -e GASFLUX_PORT=5000 \ + -e GASFLUX_LOG_LEVEL=INFO \ + gasflux-api +``` + +### Windows 服务 + +创建 `start_gasflux.bat`: + +```batch +@echo off +set GASFLUX_HOST=0.0.0.0 +set GASFLUX_PORT=80 +set GASFLUX_LOG_LEVEL=INFO +set GASFLUX_THREADS=8 + +GasFluxAPI.exe +``` + +### Linux systemd + +创建 `/etc/systemd/system/gasflux.service`: + +```ini +[Unit] +Description=GasFlux Web API +After=network.target + +[Service] +Type=simple +User=gasflux +Environment=GASFLUX_HOST=0.0.0.0 +Environment=GASFLUX_PORT=80 +Environment=GASFLUX_LOG_LEVEL=INFO +Environment=GASFLUX_THREADS=8 +ExecStart=/path/to/GasFluxAPI +Restart=always + +[Install] +WantedBy=multi-user.target +``` + +## 运行时配置检查 + +启动应用后,可以通过以下方式检查当前配置: + +### API 端点 + +```bash +curl http://localhost:5000/config +``` + +### 启动日志 + +应用启动时会输出当前配置信息: + +``` +Configuration: { + 'host': '0.0.0.0', + 'port': 5000, + 'debug': False, + ... +} +``` + +## 最佳实践 + +### 安全考虑 + +1. **生产环境**: + - 设置 `GASFLUX_DEBUG=false` + - 配置适当的 `GASFLUX_CORS_ORIGINS` + - 使用防火墙限制访问 + +2. **文件权限**: + - 确保上传和输出目录有适当的权限 + - 定期清理旧文件 + +### 性能调优 + +1. **线程数**:根据 CPU 核心数设置 `GASFLUX_THREADS` +2. **连接限制**:根据服务器容量设置 `GASFLUX_CONNECTION_LIMIT` +3. **文件大小**:根据实际需求调整 `GASFLUX_MAX_CONTENT_LENGTH` + +### 日志管理 + +1. **日志轮转**:定期备份和清理日志文件 +2. **日志级别**: + - 开发环境:`DEBUG` + - 生产环境:`INFO` 或 `WARNING` + +## 故障排除 + +### 常见问题 + +1. **端口占用**: + ```bash + # 检查端口使用 + netstat -tulpn | grep :5000 + ``` + +2. **权限问题**: + ```bash + # 确保目录权限正确 + chmod -R 755 web_api_data/ + ``` + +3. **配置不生效**: + - 确保环境变量在应用启动前设置 + - 检查变量名拼写是否正确 + +### 调试配置 + +启用详细日志查看配置加载: + +```bash +export GASFLUX_LOG_LEVEL=DEBUG +# 启动应用 +``` + +## 配置验证脚本 + +创建一个验证脚本 `check_config.py`: + +```python +#!/usr/bin/env python3 +"""Configuration validation script""" + +import os +from pathlib import Path + +def check_config(): + """Check current configuration.""" + print("GasFlux Configuration Check") + print("=" * 40) + + # Server config + print(f"Host: {os.getenv('GASFLUX_HOST', '0.0.0.0')}") + print(f"Port: {os.getenv('GASFLUX_PORT', '5000')}") + print(f"Debug: {os.getenv('GASFLUX_DEBUG', 'false')}") + print(f"Log Level: {os.getenv('GASFLUX_LOG_LEVEL', 'INFO')}") + + # Directory config + base_dir = Path.cwd() + upload_dir = base_dir / os.getenv('GASFLUX_UPLOAD_FOLDER', 'web_api_data/uploads') + output_dir = base_dir / os.getenv('GASFLUX_OUTPUT_FOLDER', 'web_api_data/outputs') + + print(f"Upload Folder: {upload_dir}") + print(f"Output Folder: {output_dir}") + + # Check directories + print(" +Directory Status:") + print(f"Upload dir exists: {upload_dir.exists()}") + print(f"Output dir exists: {output_dir.exists()}") + + # Performance config + print(" +Performance Config:") + print(f"Threads: {os.getenv('GASFLUX_THREADS', '8')}") + print(f"Connection Limit: {os.getenv('GASFLUX_CONNECTION_LIMIT', '100')}") + +if __name__ == "__main__": + check_config() +``` + +运行验证: +```bash +python check_config.py +``` \ No newline at end of file diff --git a/EXE_BUILD_README.md b/EXE_BUILD_README.md new file mode 100644 index 0000000..0365054 --- /dev/null +++ b/EXE_BUILD_README.md @@ -0,0 +1,203 @@ +# GasFlux Web API - Executable Build Guide + +This guide explains how to build and deploy the GasFlux Web API as a standalone executable using Waitress WSGI server. + +## Prerequisites + +Before building the executable, ensure you have all dependencies installed: + +```bash +# Install all required packages +pip install -r requirements.txt + +# Install build tools +pip install pyinstaller waitress +``` + +## Building the Executable + +### Windows + +Run the build script: + +```cmd +build_exe.bat +``` + +Or manually: + +```cmd +pyinstaller --onefile ^ + --name GasFluxAPI ^ + --hidden-import waitress ^ + --hidden-import flask ^ + --hidden-import werkzeug ^ + --hidden-import yaml ^ + --hidden-import pandas ^ + --hidden-import numpy ^ + --hidden-import flask_cors ^ + --add-data "src\gasflux\gasflux_config.yaml;src\gasflux" ^ + --add-data "API_DOCUMENTATION.md;." ^ + --exclude-module matplotlib ^ + --exclude-module tkinter ^ + server_waitress.py +``` + +### Linux/macOS + +Make the build script executable and run it: + +```bash +chmod +x build_exe.sh +./build_exe.sh +``` + +Or manually: + +```bash +pyinstaller --onefile \ + --name GasFluxAPI \ + --hidden-import waitress \ + --hidden-import flask \ + --hidden-import werkzeug \ + --hidden-import yaml \ + --hidden-import pandas \ + --hidden-import numpy \ + --hidden-import flask_cors \ + --add-data "src/gasflux/gasflux_config.yaml:src/gasflux" \ + --add-data "API_DOCUMENTATION.md:." \ + --exclude-module matplotlib \ + --exclude-module tkinter \ + server_waitress.py +``` + +## Running the Executable + +After successful build, you'll find `GasFluxAPI.exe` (Windows) or `GasFluxAPI` (Linux/macOS) in the `dist/` directory. + +### Basic Usage + +```bash +# Windows +GasFluxAPI.exe + +# Linux/macOS +./GasFluxAPI +``` + +The server will start on `http://localhost:5000` by default. + +### Command Line Options + +Waitress supports various command-line options. You can create a wrapper script to customize: + +```bash +# Custom port and host +waitress-serve --listen=0.0.0.0:8080 server_waitress:app + +# Production settings +waitress-serve --threads=16 --connection-limit=200 server_waitress:app +``` + +## Deployment Considerations + +### Directory Structure + +When deploying the executable, ensure these directories exist and are writable: + +``` +your-deployment-directory/ +├── GasFluxAPI(.exe) # The executable +├── web_api_data/ # Data directory (auto-created) +│ ├── uploads/ # Uploaded files +│ └── outputs/ # Processing results +├── logs/ # Log files (auto-created) +└── gasflux_config.yaml # Configuration (optional) +``` + +### Firewall Configuration + +Make sure the server port (default 5000) is open in your firewall. + +### Production Deployment + +For production use: + +1. **Use a reverse proxy**: Place Nginx or Apache in front of the executable +2. **SSL/TLS**: Configure HTTPS +3. **Process management**: Use systemd, supervisor, or similar +4. **Logging**: Configure log rotation + +### Example systemd Service (Linux) + +Create `/etc/systemd/system/gasflux-api.service`: + +```ini +[Unit] +Description=GasFlux Web API +After=network.target + +[Service] +Type=simple +User=your-user +WorkingDirectory=/path/to/deployment +ExecStart=/path/to/deployment/GasFluxAPI +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +Then: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable gasflux-api +sudo systemctl start gasflux-api +``` + +## Troubleshooting + +### Common Issues + +1. **"Permission denied"**: Ensure the executable has execute permissions and data directories are writable + +2. **"Port already in use"**: Change the port or stop other services using port 5000 + +3. **"Module not found"**: Some dependencies might be missing. Try: + ```bash + pip install --upgrade pyinstaller + pip install -r requirements.txt + ``` + +4. **Large executable size**: The `--exclude-module` options help reduce size, but some scientific packages are large + +### Performance Tuning + +- **Threads**: Increase `--threads` for more concurrent requests +- **Connection limit**: Adjust `--connection-limit` based on server capacity +- **Memory**: Monitor memory usage, especially with large data processing + +### Logs + +Logs are written to: +- Console output (when running interactively) +- `logs/gasflux_api.log` file + +Check logs for startup errors and runtime issues. + +## API Documentation + +Once running, access the API documentation at: +- Web interface: `http://your-server:5000` +- API docs: See `API_DOCUMENTATION.md` +- Statistics: `GET /stats` - Real-time API statistics and monitoring +- Health check: `GET /health` - System health with performance metrics + +## Security Notes + +- Default configuration listens on all interfaces (`0.0.0.0`) +- No authentication is configured by default +- Consider adding authentication and SSL for production use +- Regularly update dependencies for security patches \ No newline at end of file diff --git a/GASFLUX_CONFIG_DOCUMENTATION.md b/GASFLUX_CONFIG_DOCUMENTATION.md new file mode 100644 index 0000000..2ccbb2f --- /dev/null +++ b/GASFLUX_CONFIG_DOCUMENTATION.md @@ -0,0 +1,422 @@ +# GasFlux 配置清单完整说明文档 + +## 概述 + +GasFlux 是一个用于处理无人机气体通量数据的完整管道系统。本文档详细说明了所有配置文件参数及其使用方法。 + +## 配置文件结构 + +GasFlux 使用 YAML 格式的配置文件,主要包含以下几个部分: + +1. **输出设置** (`output_dir`) +2. **必需列定义** (`required_cols`) +3. **气体配置** (`gases`) +4. **处理策略** (`strategies`) +5. **背景校正设置** (`algorithmic_baseline_settings`) +6. **半变异函数设置** (`semivariogram_settings`) +7. **普通克里金设置** (`ordinary_kriging_settings`) +8. **可选过滤器** (`filters`) - 高级配置 + +## 详细参数说明 + +### 1. 输出设置 + +```yaml +output_dir: ./output # 输出目录路径 +``` + +- **类型**: 字符串 +- **描述**: 指定处理结果的输出目录 +- **默认值**: `./output` +- **注意**: 可以使用相对路径或绝对路径 + +### 2. 必需列定义 + +```yaml +required_cols: + latitude: [-90, 90] # 纬度范围 + longitude: [-180, 180] # 经度范围 + height_ato: [0, 200] # 相对起飞高度(米) + windspeed: [0, 20] # 风速(m/s) + winddir: [0, 360] # 风向(度) + temperature: [-50, 60] # 温度(°C) + pressure: [900, 1100] # 气压(hPa) +``` + +- **格式**: 字典,键为列名,值为 `[最小值, 最大值]` 数组 +- **作用**: 数据验证和范围检查 +- **注意**: + - 所有值必须是 float64 类型 + - 不允许 NaN 值 + - 超出范围的值会报错 + +### 3. 气体配置 + +```yaml +gases: + co2: [300, 500] # 二氧化碳浓度范围(ppm) + ch4: [1.5, 10.0] # 甲烷浓度范围(ppm) +``` + +- **格式**: 字典,键为气体名称,值为 `[最小值, 最大值]` 数组 +- **作用**: 指定要处理的 gases 及其合理浓度范围 +- **注意**: + - 气体名称必须与数据中的列名完全匹配 + - 范围用于数据验证 + +### 4. 处理策略 + +```yaml +strategies: + background: "algorithm" # 背景校正方法 + sensor: "insitu" # 传感器类型 + spatial: "curtain" # 空间处理模式: "curtain" 或 "spiral" + interpolation: "kriging" # 插值方法 +``` + +- **background**: 目前只支持 `"algorithm"` +- **sensor**: 目前只支持 `"insitu"` +- **spatial**: `"curtain"` (平面模式) 或 `"spiral"` (螺旋模式) +- **interpolation**: 目前只支持 `"kriging"` + +## 背景校正算法设置 + +### 算法总览 + +```yaml +algorithmic_baseline_settings: + algorithm: fastchrom # 选择使用的算法 + # 以下是各算法的具体参数 +``` + +### 支持的算法 + +#### 1. FastChrom 算法 (推荐用于大多数情况) + +```yaml +algorithmic_baseline_settings: + algorithm: fastchrom + fastchrom: + half_window: 6 # 半窗口大小,用于基线拟合 + threshold: "custom" # 阈值方法,推荐使用 "custom" + min_fwhm: ~ # 最小峰宽,~ 表示 null + interp_half_window: 3 # 插值半窗口 + smooth_half_window: 3 # 平滑半窗口 + weights: ~ # 权重,~ 表示 null + max_iter: 100 # 最大迭代次数 + min_length: 2 # 最小段长度 +``` + +#### 2. Dietrich 算法 + +```yaml +algorithmic_baseline_settings: + algorithm: dietrich + dietrich: + poly_order: 5 # 多项式阶数 + smooth_half_window: 5 # 平滑半窗口 +``` + +#### 3. FABC (Fully Automatic Baseline Correction) 算法 + +```yaml +algorithmic_baseline_settings: + algorithm: fabc + fabc: + lam: 10000 # 平滑参数,值越大基线越平滑 + scale: 10 # 小波变换尺度 + diff_order: 2 # 微分矩阵阶数 +``` + +#### 4. Golotvin 算法 + +```yaml +algorithmic_baseline_settings: + algorithm: golotvin + golotvin: + half_window: 2 # 半窗口大小 + sections: 10 # 分段数量 +``` + +## 克里金插值设置 + +### 半变异函数设置 + +```yaml +semivariogram_settings: + model: spherical # 变异函数模型: spherical, gaussian, exponential + estimator: cressie # 估计器: cressie, matheron, dowd + n_lags: 20 # 滞后期数 + bin_func: even # 分箱函数: even, uniform + fit_method: lm # 拟合方法: lm, manual + maxlag: 100 # 最大滞后距离(米) + tolerance: 10 # 方向容差(度) + azimuth: 0 # 方位角(度,0为水平向右) + bandwidth: 20 # 带宽(米) +``` + +### 普通克里金设置 + +```yaml +ordinary_kriging_settings: + min_points: 3 # 最小邻点数 + max_points: 100 # 最大邻点数 + grid_resolution: 500 # 网格分辨率 + min_nodes: 10 # 最小网格节点数 + y_min: ~ # 最小y值覆盖,手动覆盖,~表示使用默认 + cut_ground: True # 是否切割地面以下的值 +``` + +## 空间处理模式详解 + +### 平面模式 (Curtain Mode) + +**适用场景**: 直线飞行路径,沿着固定方向的多个平行航线 + +```yaml +strategies: + spatial: "curtain" + +# 推荐的半变异函数设置(平面模式) +semivariogram_settings: + model: spherical + estimator: cressie + n_lags: 15 + bin_func: even + fit_method: lm + maxlag: 80 # 根据飞行距离调整 + tolerance: 15 # 较大的容差适应直线路径 + azimuth: 0 # 沿着飞行方向 + bandwidth: 25 # 较大的带宽适应航线间距 + +ordinary_kriging_settings: + min_points: 5 + max_points: 80 + grid_resolution: 200 # 较高的分辨率 + min_nodes: 10 + y_min: 20 # 设置最小高度 + cut_ground: True +``` + +**特点**: +- 应用风偏移校正 +- 沿着最大单调序列提取航线 +- 适合规则的栅格飞行模式 + +### 螺旋模式 (Spiral Mode) + +**适用场景**: 螺旋或圆形飞行路径 + +```yaml +strategies: + spatial: "spiral" + +# 推荐的半变异函数设置(螺旋模式) +semivariogram_settings: + model: spherical + estimator: cressie + n_lags: 20 + bin_func: even + fit_method: lm + maxlag: 100 # 较大的最大距离适应螺旋半径 + tolerance: 30 # 更大的容差适应圆形路径 + azimuth: 0 # 径向方向 + bandwidth: 20 # 适应螺旋间距 + +ordinary_kriging_settings: + min_points: 3 + max_points: 100 + grid_resolution: 500 # 较低的分辨率适应圆形区域 + min_nodes: 10 + y_min: ~ # 自动确定最小高度 + cut_ground: True +``` + +**特点**: +- 不应用风偏移校正(假设风垂直于螺旋) +- 计算圆偏差和中心 +- 重新居中方位角 +- 使用周长距离作为x坐标 + +## 可选过滤器(高级配置) + +```yaml +filters: + course_filter: + azimuth_filter: 15 # 方位角过滤容差(度) + azimuth_window: 8 # 滚动中位数窗口大小 + elevation_filter: 8 # 海拔过滤容差(度) +``` + +## 完整配置示例 + +### 平面模式完整配置 + +```yaml +output_dir: ./curtain_output + +required_cols: + latitude: [-90, 90] + longitude: [-180, 180] + height_ato: [0, 200] + windspeed: [0, 20] + winddir: [0, 360] + temperature: [-50, 60] + pressure: [900, 1100] + +gases: + co2: [300, 500] + ch4: [1.5, 10.0] + +strategies: + background: "algorithm" + sensor: "insitu" + spatial: "curtain" + interpolation: "kriging" + +algorithmic_baseline_settings: + algorithm: fastchrom + fastchrom: + half_window: 6 + threshold: "custom" + min_fwhm: ~ + interp_half_window: 3 + smooth_half_window: 3 + weights: ~ + max_iter: 100 + min_length: 2 + +semivariogram_settings: + model: spherical + estimator: cressie + n_lags: 15 + bin_func: even + fit_method: lm + maxlag: 80 + tolerance: 15 + azimuth: 0 + bandwidth: 25 + +ordinary_kriging_settings: + min_points: 5 + max_points: 80 + grid_resolution: 200 + min_nodes: 10 + y_min: 20 + cut_ground: True +``` + +### 螺旋模式完整配置 + +```yaml +output_dir: ./spiral_output + +required_cols: + latitude: [-90, 90] + longitude: [-180, 180] + height_ato: [0, 200] + windspeed: [0, 20] + winddir: [0, 360] + temperature: [-50, 60] + pressure: [900, 1100] + +gases: + co2: [300, 500] + ch4: [1.5, 10.0] + +strategies: + background: "algorithm" + sensor: "insitu" + spatial: "spiral" + interpolation: "kriging" + +algorithmic_baseline_settings: + algorithm: fastchrom + fastchrom: + half_window: 6 + threshold: "custom" + min_fwhm: ~ + interp_half_window: 3 + smooth_half_window: 3 + weights: ~ + max_iter: 100 + min_length: 2 + +semivariogram_settings: + model: spherical + estimator: cressie + n_lags: 20 + bin_func: even + fit_method: lm + maxlag: 100 + tolerance: 30 + azimuth: 0 + bandwidth: 20 + +ordinary_kriging_settings: + min_points: 3 + max_points: 100 + grid_resolution: 500 + min_nodes: 10 + y_min: ~ + cut_ground: True +``` + +## 参数调优指南 + +### 空间模式选择 + +1. **平面模式 (curtain)**: + - 飞行路径呈直线或平行航线 + - 数据点分布在规则的栅格中 + - 适合大型区域的系统采样 + +2. **螺旋模式 (spiral)**: + - 飞行路径呈螺旋或圆形 + - 数据点围绕中心点分布 + - 适合点源排放的详细采样 + +### 半变异函数调优 + +1. **maxlag**: 设置为数据空间范围的 80-100% +2. **tolerance**: 平面模式 10-20°,螺旋模式 20-40° +3. **bandwidth**: 根据飞行间距调整,过大会影响精度 +4. **n_lags**: 通常 10-25,根据数据量调整 + +### 克里金插值调优 + +1. **grid_resolution**: 影响输出网格密度 +2. **min_points/max_points**: 平衡计算速度和精度 +3. **cut_ground**: 根据是否有地面高程数据决定 + +## 故障排除 + +### 常见错误 + +1. **"autodetected range of [nan, nan] is not finite"** + - 检查气体列是否有NaN值 + - 确认气体名称与数据列名匹配 + +2. **"`ydata` must not be empty!"** + - 检查半变异函数参数是否过于严格 + - 减少 maxlag 或增加 tolerance + +3. **Missing columns** + - 确认数据列名与配置中的 gases 键匹配 + - 检查必需列是否存在 + +### 性能优化 + +1. **减少计算时间**: 降低 grid_resolution,减少 max_points +2. **提高精度**: 增加 n_lags,调整半变异函数参数 +3. **内存优化**: 适当减少 max_points 和 grid_resolution + +## 版本信息 + +- **GasFlux 版本**: 开发版 +- **最后更新**: 2025年2月 +- **文档版本**: 1.0 + +--- + +*本配置文档基于 GasFlux 系统的实际代码实现编写。如有疑问,请参考源码注释或提交 Issue。* \ No newline at end of file diff --git a/README.md b/README.md index 3358bd8..a6b35ac 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ GasFlux 是一个专门用于处理无人机或飞行器采集的气体浓度数 - **灵活配置**: 基于 YAML 的配置系统,支持自定义参数 - **可视化输出**: 自动生成数据分析图表和报告 - **命令行工具**: 提供直观的 CLI 接口 +- **Web API 服务**: 提供完整的 RESTful API,支持异步处理、实时监控和详细统计 - **跨平台支持**: 支持 Windows、macOS 和 Linux ## 📋 系统要求 @@ -77,6 +78,62 @@ gasflux process /path/to/data/directory python src/gasflux/run_example.py your_data.xlsx ``` +## 🌐 Web API 服务 + +GasFlux 提供完整的 Web API 服务,支持通过 HTTP 接口进行数据处理。详见以下文档: + +- [API 文档](API_DOCUMENTATION.md) - 完整的 API 接口说明 +- [环境变量配置](ENVIRONMENT_VARIABLES.md) - 环境变量配置指南 +- [Waitress 部署](WAITRESS_DEPLOYMENT.md) - 生产环境部署指南 + +### 启动 Web 服务 + +```bash +# 启动 API 服务(使用默认配置) +python run_api.py + +# 或使用自定义环境变量 +export GASFLUX_PORT=8080 +export GASFLUX_LOG_LEVEL=DEBUG +python run_api.py + +# 服务将在配置的地址和端口启动 +# 访问 http://localhost:5000 查看 Web 界面 +``` + +### 环境变量配置 + +GasFlux 支持通过环境变量进行灵活配置。详见 [ENVIRONMENT_VARIABLES.md](ENVIRONMENT_VARIABLES.md) 获取完整配置指南。 + +**常用配置示例**: + +```bash +# 端口配置 +export GASFLUX_PORT=8080 + +# 日志配置 +export GASFLUX_LOG_LEVEL=DEBUG +export GASFLUX_LOG_FILE=/var/log/gasflux/api.log + +# 性能配置 +export GASFLUX_THREADS=16 +export GASFLUX_MAX_CONTENT_LENGTH=524288000 # 500MB + +# 目录配置 +export GASFLUX_UPLOAD_FOLDER=/data/uploads +export GASFLUX_OUTPUT_FOLDER=/data/outputs +``` + +### API 特性 + +- **异步处理**: 支持大文件处理,不阻塞客户端 +- **实时监控**: 通过任务 ID 实时查询处理状态 +- **文件管理**: 自动文件上传、处理和下载 +- **健康检查**: 系统状态监控和诊断 +- **详细日志**: 完整的请求日志和性能监控 +- **统计监控**: 实时 API 统计、性能指标和系统资源监控 +- **环境变量配置**: 通过环境变量灵活配置,无需修改代码 + ## 📖 使用指南 ### 配置文件 diff --git a/WAITRESS_DEPLOYMENT.md b/WAITRESS_DEPLOYMENT.md new file mode 100644 index 0000000..608f611 --- /dev/null +++ b/WAITRESS_DEPLOYMENT.md @@ -0,0 +1,271 @@ +# GasFlux Web API - Waitress WSGI 部署指南 + +本文档介绍如何使用 Waitress WSGI 服务器打包和部署 GasFlux Web API 为独立可执行文件。 + +## 概述 + +Waitress 是一个纯 Python WSGI 服务器,适合生产环境使用。本部署方案将 Flask 应用与 Waitress 打包为单个可执行文件,无需额外安装 Python 环境。 + +## 文件说明 + +### 核心文件 + +- **`server_waitress.py`** - Waitress 服务器入口点 +- **`src/gasflux/app.py`** - Flask 应用定义 +- **`build_exe.bat`** - Windows 构建脚本 +- **`build_exe.sh`** - Linux/macOS 构建脚本 +- **`EXE_BUILD_README.md`** - 详细构建和部署指南 + +### 构建产物 + +- **`dist/GasFluxAPI.exe`** (Windows) 或 **`dist/GasFluxAPI`** (Linux/macOS) - 独立可执行文件 + +## 快速开始 + +### 1. 安装依赖 + +```bash +pip install -r requirements.txt +pip install pyinstaller waitress +``` + +### 2. 构建可执行文件 + +**Windows:** +```cmd +build_exe.bat +``` + +**Linux/macOS:** +```bash +chmod +x build_exe.sh +./build_exe.sh +``` + +### 3. 运行服务器 + +```bash +# Windows +dist\GasFluxAPI.exe + +# Linux/macOS +./dist/GasFluxAPI +``` + +服务器将在 `http://localhost:5000` 启动。 + +## 服务器配置 + +### 默认配置 + +```python +host = '0.0.0.0' # 监听所有接口 +port = 5000 # 默认端口 +threads = 8 # 工作线程数 +connection_limit = 100 # 最大并发连接 +timeout = 300 # 请求超时(秒) +``` + +### 自定义配置 + +修改 `server_waitress.py` 中的参数: + +```python +# 自定义端口 +port = 8080 + +# 增加线程数 +threads = 16 + +# 调整超时时间 +timeout = 600 # 10分钟 +``` + +## 部署架构 + +### 单文件部署 + +``` +部署目录/ +├── GasFluxAPI(.exe) # 可执行文件 +├── web_api_data/ # 数据目录(自动创建) +│ ├── uploads/ # 上传文件 +│ └── outputs/ # 处理结果 +├── logs/ # 日志文件(自动创建) +└── gasflux_config.yaml # 配置文件(可选) +``` + +### 生产部署建议 + +#### 使用反向代理 + +```nginx +# nginx 配置示例 +server { + listen 80; + server_name your-domain.com; + + location / { + proxy_pass http://127.0.0.1:5000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } +} +``` + +#### 使用进程管理器 + +**systemd (Linux):** + +```ini +[Unit] +Description=GasFlux Web API +After=network.target + +[Service] +Type=simple +User=www-data +WorkingDirectory=/path/to/deployment +ExecStart=/path/to/deployment/GasFluxAPI +Restart=always +RestartSec=5 + +[Install] +WantedBy=multi-user.target +``` + +**Windows 服务:** + +使用 NSSM (Non-Sucking Service Manager) 将 exe 注册为 Windows 服务。 + +## 性能优化 + +### Waitress 参数调优 + +```python +serve( + app, + host='0.0.0.0', + port=5000, + threads=16, # 根据 CPU 核心数调整 + connection_limit=200, # 最大并发连接 + timeout=300, # 长请求超时 + backlog=2048, # 连接队列长度 + recv_bytes=8192, # 接收缓冲区 + send_bytes=8192, # 发送缓冲区 +) +``` + +### 监控和日志 + +- **访问日志**: `logs/gasflux_api.log` +- **控制台输出**: 启动时显示的控制台信息 +- **健康检查**: `GET /health` 端点 + +### 资源使用 + +- **内存**: 每个工作进程约 50-200MB(取决于数据处理量) +- **CPU**: 多线程处理,建议 4+ 核心 +- **磁盘**: 日志和临时文件存储 + +## 安全考虑 + +### 基本安全措施 + +1. **防火墙配置**: 只开放必要端口 +2. **用户权限**: 以非 root 用户运行 +3. **文件权限**: 数据目录限制访问权限 + +### 生产环境建议 + +1. **HTTPS**: 使用反向代理配置 SSL +2. **认证**: 添加 API 密钥或 JWT 认证 +3. **限流**: 实现请求频率限制 +4. **监控**: 设置日志监控和告警 + +## 故障排除 + +### 常见问题 + +1. **端口占用** + ```bash + # 检查端口使用 + netstat -tulpn | grep :5000 + # 或 Windows: netstat -ano | findstr :5000 + ``` + +2. **权限问题** + ```bash + # 确保可执行文件有执行权限 + chmod +x GasFluxAPI + # 确保数据目录可写 + chmod -R 755 web_api_data/ + ``` + +3. **构建失败** + ```bash + # 清理旧构建 + rm -rf build/ dist/ + # 重新安装依赖 + pip install --upgrade pyinstaller waitress + ``` + +4. **内存不足** + - 减少线程数 + - 增加服务器内存 + - 优化数据处理流程 + +### 调试模式 + +临时启用详细日志: + +```bash +# 修改 server_waitress.py +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +## 维护和更新 + +### 更新部署 + +1. 构建新版本可执行文件 +2. 备份数据目录 +3. 停止旧服务 +4. 替换可执行文件 +5. 启动新服务 + +### 备份策略 + +- **数据**: `web_api_data/` 目录 +- **配置**: `gasflux_config.yaml` +- **日志**: `logs/` 目录 + +## API 使用 + +部署完成后,API 端点与开发环境相同: + +- **健康检查**: `GET /health` +- **文件上传**: `POST /upload` +- **任务状态**: `GET /task/{task_id}` +- **文件下载**: `GET /download/{filename}` +- **Web 界面**: `GET /` + +详细 API 文档请参考 `API_DOCUMENTATION.md`。 + +## 总结 + +使用 Waitress 打包的方案提供了: + +- ✅ **零依赖部署**: 单文件可执行 +- ✅ **生产就绪**: WSGI 服务器,适合高并发 +- ✅ **跨平台**: 支持 Windows/Linux/macOS +- ✅ **易于维护**: 简单的部署和更新流程 +- ✅ **完整功能**: 保留所有 Flask 应用功能 + +这种部署方式特别适合: +- Windows 服务器环境 +- 简单的生产部署需求 +- 需要独立可执行文件的场景 \ No newline at end of file diff --git a/build_exe.bat b/build_exe.bat new file mode 100644 index 0000000..3b1dfb0 --- /dev/null +++ b/build_exe.bat @@ -0,0 +1,62 @@ +@echo off +REM GasFlux Web API - Build executable with PyInstaller +REM This script builds a standalone executable using Waitress WSGI server + +echo Building GasFlux Web API executable... + +REM Check if PyInstaller is installed +python -c "import PyInstaller" >nul 2>&1 +if errorlevel 1 ( + echo Error: PyInstaller is not installed. Please run: + echo pip install pyinstaller waitress + pause + exit /b 1 +) + +REM Check if Waitress is installed +python -c "import waitress" >nul 2>&1 +if errorlevel 1 ( + echo Error: Waitress is not installed. Please run: + echo pip install waitress + pause + exit /b 1 +) + +REM Create dist directory if it doesn't exist +if not exist "dist" mkdir dist + +echo Creating executable with PyInstaller... + +REM Build the executable +pyinstaller --onefile ^ + --name GasFluxAPI ^ + --hidden-import waitress ^ + --hidden-import flask ^ + --hidden-import werkzeug ^ + --hidden-import yaml ^ + --hidden-import pandas ^ + --hidden-import numpy ^ + --hidden-import flask_cors ^ + --hidden-import psutil ^ + --add-data "src\gasflux\gasflux_config.yaml;src\gasflux" ^ + --add-data "API_DOCUMENTATION.md;." ^ + --exclude-module matplotlib ^ + --exclude-module tkinter ^ + server_waitress.py + +if errorlevel 1 ( + echo Error: Failed to build executable + pause + exit /b 1 +) + +echo. +echo Build completed successfully! +echo Executable created: dist\GasFluxAPI.exe +echo. +echo To run the server: +echo GasFluxAPI.exe +echo. +echo The server will start on http://localhost:5000 +echo. +pause \ No newline at end of file diff --git a/build_exe.sh b/build_exe.sh new file mode 100644 index 0000000..e9cd096 --- /dev/null +++ b/build_exe.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# GasFlux Web API - Build executable with PyInstaller +# This script builds a standalone executable using Waitress WSGI server + +echo "Building GasFlux Web API executable..." + +# Check if PyInstaller is installed +if ! python3 -c "import PyInstaller" 2>/dev/null; then + echo "Error: PyInstaller is not installed. Please run:" + echo "pip install pyinstaller waitress" + exit 1 +fi + +# Check if Waitress is installed +if ! python3 -c "import waitress" 2>/dev/null; then + echo "Error: Waitress is not installed. Please run:" + echo "pip install waitress" + exit 1 +fi + +# Create dist directory if it doesn't exist +mkdir -p dist + +echo "Creating executable with PyInstaller..." + +# Build the executable +pyinstaller --onefile \ + --name GasFluxAPI \ + --hidden-import waitress \ + --hidden-import flask \ + --hidden-import werkzeug \ + --hidden-import yaml \ + --hidden-import pandas \ + --hidden-import numpy \ + --hidden-import flask_cors \ + --hidden-import psutil \ + --add-data "src/gasflux/gasflux_config.yaml:src/gasflux" \ + --add-data "API_DOCUMENTATION.md:." \ + --exclude-module matplotlib \ + --exclude-module tkinter \ + server_waitress.py + +if [ $? -ne 0 ]; then + echo "Error: Failed to build executable" + exit 1 +fi + +echo "" +echo "Build completed successfully!" +echo "Executable created: dist/GasFluxAPI" +echo "" +echo "To run the server:" +echo "./dist/GasFluxAPI" +echo "" +echo "The server will start on http://localhost:5000" \ No newline at end of file diff --git a/build_gasflux_exe.bat b/build_gasflux_exe.bat deleted file mode 100644 index d04a53c..0000000 --- a/build_gasflux_exe.bat +++ /dev/null @@ -1,48 +0,0 @@ -@echo off -echo ======================================== -echo GasFlux RunExample EXE打包工具 (Windows) -echo ======================================== -echo. - -cd /d "%~dp0" - -echo [1/4] 检查Python环境... -python --version -if errorlevel 1 ( - echo 错误:未找到Python - pause - exit /b 1 -) - -echo [2/4] 升级pip... -python -m pip install --upgrade pip - -echo [3/4] 安装PyInstaller... -python -m pip install pyinstaller -if errorlevel 1 ( - echo 错误:PyInstaller安装失败 - pause - exit /b 1 -) - -echo [4/4] 开始打包... -python -m PyInstaller --clean gasflux_run_example.spec -if errorlevel 1 ( - echo 错误:打包失败 - pause - exit /b 1 -) - -echo. -echo ======================================== -echo 打包完成! -echo ======================================== -echo. -echo 可执行文件位置:dist\GasFlux_RunExample.exe -echo. -echo 使用方法: -echo GasFlux_RunExample.exe input.xlsx -echo GasFlux_RunExample.exe input.xlsx --output result.csv -echo GasFlux_RunExample.exe input.xlsx --no-gasflux -echo. -pause diff --git a/build_gasflux_exe.sh b/build_gasflux_exe.sh deleted file mode 100644 index ce4472f..0000000 --- a/build_gasflux_exe.sh +++ /dev/null @@ -1,48 +0,0 @@ -#!/bin/bash - -echo "========================================" -echo "GasFlux RunExample EXE打包工具 (Linux/Mac)" -echo "========================================" -echo - -cd "$(dirname "$0")" - -echo "[1/4] 检查Python环境..." -if ! command -v python3 &> /dev/null; then - echo "错误:未找到Python3" - exit 1 -fi - -python3 --version - -echo "[2/4] 升级pip..." -python3 -m pip install --upgrade pip - -echo "[3/4] 安装PyInstaller..." -python3 -m pip install pyinstaller - -if [ $? -ne 0 ]; then - echo "错误:PyInstaller安装失败" - exit 1 -fi - -echo "[4/4] 开始打包..." -python3 -m PyInstaller --clean gasflux_run_example.spec - -if [ $? -ne 0 ]; then - echo "错误:打包失败" - exit 1 -fi - -echo -echo "========================================" -echo "打包完成!" -echo "========================================" -echo -echo "可执行文件位置:dist/GasFlux_RunExample" -echo -echo "使用方法:" -echo " ./GasFlux_RunExample input.xlsx" -echo " ./GasFlux_RunExample input.xlsx --output result.csv" -echo " ./GasFlux_RunExample input.xlsx --no-gasflux" -echo diff --git a/env.example b/env.example new file mode 100644 index 0000000..e25604b --- /dev/null +++ b/env.example @@ -0,0 +1,30 @@ +# GasFlux Web API - Environment Variables Example +# Copy this file to your environment and modify as needed + +# Server Configuration +GASFLUX_HOST=0.0.0.0 +GASFLUX_PORT=5000 +GASFLUX_DEBUG=false + +# Directory Configuration +GASFLUX_UPLOAD_FOLDER=web_api_data/uploads +GASFLUX_OUTPUT_FOLDER=web_api_data/outputs + +# File Size Limits (in bytes) +GASFLUX_MAX_CONTENT_LENGTH=104857600 # 100MB + +# Logging Configuration +GASFLUX_LOG_LEVEL=INFO +GASFLUX_LOG_FILE=logs/gasflux_api.log + +# CORS Configuration +GASFLUX_CORS_ORIGINS=* + +# Task Management +GASFLUX_TASK_CLEANUP_INTERVAL=3600 # 1 hour +GASFLUX_MAX_TASK_AGE=86400 # 24 hours + +# Performance Tuning +GASFLUX_THREADS=8 +GASFLUX_CONNECTION_LIMIT=100 +GASFLUX_CHANNEL_TIMEOUT=300 \ No newline at end of file diff --git a/examples/basic_usage/advanced_config.yaml b/examples/basic_usage/advanced_config.yaml deleted file mode 100644 index 5ee39da..0000000 --- a/examples/basic_usage/advanced_config.yaml +++ /dev/null @@ -1,52 +0,0 @@ -output_dir: ./advanced_output - -required_cols: - latitude: [-90, 90] - longitude: [-180, 180] - height_ato: [-100, 500] - windspeed: [1, 15] - winddir: [0, 360] - temperature: [0, 30] - pressure: [980, 1030] - -gases: - ch4: [1.8, 5.0] - co2: [400, 500] - - -strategies: - background: "algorithm" - sensor: "insitu" - spatial: "curtain" - interpolation: "kriging" - -algorithmic_baseline_settings: - algorithm: dietrich - dietrich: - poly_order: 4 - smooth_half_window: 4 - -semivariogram_settings: - model: gaussian - estimator: cressie - n_lags: 15 - bin_func: even - fit_method: lm - maxlag: 80 - tolerance: 15 - azimuth: 0 - bandwidth: 25 - -ordinary_kriging_settings: - min_points: 5 - max_points: 80 - grid_resolution: 200 - min_nodes: 10 - y_min: 20 - cut_ground: True - -filters: - course_filter: - azimuth_filter: 15 - azimuth_window: 8 - elevation_filter: 8 diff --git a/examples/basic_usage/data/08_34_01_间隔高度5m.xlsx b/examples/basic_usage/data/08_34_01_间隔高度5m.xlsx deleted file mode 100644 index 693ae7d52026429de006550b15a58b8eda0d9a22..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 192340 zcmeFY^LJ!nw=EpoR>$h39s&de9RvnUTiD*t#njG4U&YhG z)LEC_!`6nl00N9U4+QM{{r`9U58i>w|o0 z-H6noOwp_3!J-!vgkkZf2WkzDaFL^8Y3KYZxqvWAsNS<#-Er^JUS z@*e!P8d-0~vOW9RVf!kEuSbHSh)OrVJ=A|E$*g`d!^$^7NWLkA{Jk?CHVp3ePS!^D z_SXOLTSY3e_HzO#9b`8|l^t!5b_B-Sj^#e8B48?lmJBHcF&&ZWO&w+(c1o%pq)5ah zpi5E__|Ws*yIaXHpOD5>a{Nv|t4y!Fc+UX#W}7nB-Z}lNOi7&BWpUT3k*ujur28lH z;x$ihF3^^(1(p)V3Cu*zf`4BMvTe>{0MJ;)yyPr z(>uVXh>Mm$la7EGjT=|X(I^yA1hQw92+cYo*iAVqXG*u&dep*#m&t?84JyJS7Yz@H z*n&3Jk$3Fy|3-|q4>9P(4Q?aR3^N&OAK%GHX}lU4)iYjEH(2&z#DlU_)w&_}coMO$ zZbhB9ll2&XOq*S>NbSgzOfSQ+WSUqY-v1NI=LIIsWX#StOnQdJ14Zj7Gb(}Mo#_{Fxa`(1nf8^iUsqfRO;q&b1 zAzSLwK=UbF&Pq*YS}94tWe*MBB2|;RSDlF0j{G)7Oi^>pL=IzuQhn+UgWDaWh$n#@ zC-(yy9Ux7x!Iq1%L8_+jzG0`owbG;8W}F1&xWVEewyH;Lkzb&CDQ!Wd8Gnp8=gd_@ zw|oyrs5isGrk(IIT{bt@8NzqQC+*CCV z?h>`?2S%xCL#i5^0asxd8#7bmW&N6(p;*TKsPaBzzbf7t5jY-n-m4Q0dqzs#<>iI{ zH^%=v3j+C))9${%0e&+94+IAEn+5;NTl}9~_}`ub^jpY%bLoHfRjDK^-Oq^90sk4! z=$`3@iMry#Kzgovh72>(_;ZbtgvIZE3!et4tFb7}0B#@Vc{MTQ_QZy?3y%KKLtFX> z4Z;iE=87BAe&TKd9LDIPiZDnV5*_XQ^sM3n9x1~ay_GL<_*V`F$Kf+I1#Wm@38~0^ z>ueaS)g+@Uvz7DokI`?-Io7xz@EW3gUKP{`01#*VG&}fe6<&LU$_?@)nz_S;mE=M6 z1erOWC!u}r5q*TYZGShD^_;ZsDSSmZAgGWgENA2O33DM19~LL_|C#7~P>|lHdbQ&s z^fvmlYwh;e*!`8+{>ae6*7r&OcgVCEq$3;-gMpyWA%ft1zw!SYPG<{KQx|83|N3D1 zj|I(2*0s-M#0a~hy5QI7L^hC+5Fk-qMW}M0TdZ1&bqvCcPQUQ6T=lt%Qi1L>B2hTl3Wvf@A zBE`Tbe_8Ez)eU8W){4YW`On}z>xf;CWI6kqS$DDxNF7E>?GVK9>-aO9(eI_NJ3P(Fq|^942GS4mgczN`fhtye1i3rPATo1Y9F(SN z=g;Uh|g7Bfh5ez-^(M`(1xOfsUE{S{JXTaZkZ`Qt|em@>V)g#{R=HBCX zAG0@#KkY}St}pAf2rwFH8>gHL^NqOolDva9j!qs67SfkB+)mW;#Y{i!C5!EgRtph@ z_lUS91Ui<%I;wtfAW+r^2hQ{jZf4gS>c4Yj1bpO%z1ZgdFO>Zk)Utf7CThz57B)%c~x*icW} z;P-X(_58N=^>DxB|M3vF^>xGl^_2U0leYC|>+^o<>tX5ZmFn}1fAkc<|B+kpdA9Yr z*Y&mauj}j1-v6t{|Kk8~Q1E{7{X?_mFSG3(wD+&C{;#(y{?x9omo5MI&!euGh_C2~ zAk;0g0+W7ak@;wk*oZ%<45%}VyKJOdVwGeC#{HC}USjQ{{eMueSjZ@YUzxxY6hFQP z!;MJ+lX*856x`Pj1QD@EM(~7u-tR{?{@;Ux{P*(>=a6J(VD5kJYH){Hf&NfQ)h;wd z`ALCax0y3%TmJK=y2JQyQ)62dJ68ti%Bo{$@_NHz zTrI~L2j#BY)35k|&4&u0+lQ)@Qh1walT9XH<%PwmbvwIwo9ntSPUiDLyaqsb$?I0EFqujJ7M$9t4Ke$3KtN_}m zb$t$SK7Iss^;jTFF|8V#;mXFW8#O{x;a&&=aAkkA2hXg((upaDCq+M&dgDy^whUOr zb&gn^UjHiO-K8w##fiRLZO%OG{F^}L+Gw$V_MCKRqPsgr#n&4-RHwQ4*KsL#{X%9! z)u}<=PRj0HbZK+s=oOU@pJV7~Z0qXi4syyzFVgvbU+^cNcl3=W^kpbs8DnN9*9(#! z@3S_*LM`SCY)Zo#n5X+oT^9W#0WlwsRohLpae}akRleX%SN$NRYb?7Wb^J0q{SjMb zaZZ(WVm-83A*up%`9d*1lmJv8Z&8dbCcTNPW1Vw9~b%h{2R;^-Jrvl?`ugc zanzr0{vEe}$k!;^>LUGF>=OjIhr)t1Lb`=rRb;3bDA#PZldSx`S}1r4yktHzNB)i9 zVfEGskfrP6hCMUK%LeK`w?t?+BB8D(LF2dLyPJQMW|tiZ*x5;q$S!S4US6U);d6*z zeglQ-336)xl9l67uxpr3`7E8$ATZ+*8*H5v0FAsKmM%Nh*VJt-QZF?G&V+y;gbio#|SAByN{fEco!{|V_iI83E z0~TS5efGqa+-v7AS|WI*vCA1^|E}}7!Po7cKBYG2d@-h)Z=BGi!F!hHvRc%}f z34Nd+ApN3VA-W`e<2q%1t=w&ZW5D`aZyHA!hBp9b7%#eFT|zS}*U81pD3Sb|y@P zX!dlM5y=N=`#fz2Ude9#5!8T(lD;3cSBV_hL}}MC&?Vj+%@jwdZle>g<`?Wz_I?1v zXQIUhRu#xDA$1#e_v+Abx`VmUr=DzgFBXmsSZS)yPGq|@bU*5QH8@<&Y&(QMuih&F zJtx@3i`r$L?vHzTAGNV(SLHJzQ#y#du7-KIZ>qF8^iRUMl*a-m=aZ+?tJ{+%2X0aM zkdF!S#k>2D@j#ExY9hJU_8?_W#IlO1gkrK5FQUw$s?<{K^xGMCu_|DB>8EJrwNz7< z6*3|Q-97^CwLB&#L({@n9o{HNhgE6K4q_M$Udk=rcQuTU^GAS%5sF2e`hkpY>Q%LL zc|2);8pOWon-;6L+PVU(wNAPCp2NDwu5G{O&Q5uI1mDnZI$Sier)xJZq=ezb?P{&m zdo+)Ze%fa00tR7%Ai2~8?W!&AW$_S3G5J&aS$%Jso^IEZ>7T}W=z)J51BzQfD09eu zb+mtGu3Ql^5a(dit~h#-&JR1sMDVh}RNOOL^!tr?L46>4=)J`!%5)Krl@^tR!#SIcQZ3-dv)s_|Nl%-(73>R~^}>(z8f8~^JD z9CDiJ)q3mhY6sHw7$ST7{7Zyi)%5^b7BP=C9e#os%JEn*GXjA=8Lf?De}O!0m0tU; z{e19F-Vf&pWJVw6M8zXgv1s$T4_V{8+c;?DKll-m^epO6fBAv-SMBhVp+{6R=><&( zWG)RP6UkRG?rT?X@+qj13!4eQxyeH(2ZqfL7B1a+3PDTNw#JT!j7GVFV+^emVxG-L z%r(Y+zMwfK10XMs2;l0lZ8PN2*#;u`@fw~hE2!&{P;LR;?a=Nf`1)2Xe&`!ogI3ZL z@Ee8b0Yl_L+mB4Py~o%ELJF>k_tO2)>Wy1Md#t}K6Vn>0I65H@<;=&sBsTG0mBQ>n zEjLGtX-qsWjk>BYOyzQ5n0=n=pglfD5T|(f5Bly#0CVxZ*k3PtubGX9ey{o=9JsIg zAm|@&rh9%YNc@uC^!NIwRxf4OrTX{kOtisjW1R_CNAP4)=eba$4Ic3IQInZfxT8q{ z@R@GSKM42tAcB@)0W2M4Ut${&2R-y%WHr{FLBhr1bPbE%@GOhDuXAKU3oh0CiqQAM z!1>hY`r@uE?MpNaK^zgxamJEx+|c*mt$JJN1hftz?LQLsSv-hYq1sR20SZba1o?IQ zJ=Q9jQ*#($g=&tpY+#^DTgmCB9WtVw8&vN4ix2lhZ4>Q39WIYvL~D|EULYB7d{nUx z;P5Nb{8@OYAg71##Ps@=cNEE{9xC3(^9<)w#E(N!HF8x?)83dimBs(Qg<#lrVZ{}!`1zR_Mj1?WV1DQ9lJ_C zY#qB>^xSwTSf2^)7Ha|M@Qai^cbR7#XgD-UxL^^ z+JQJQ!bj?lpQyj?BXtJug-plTso@Z!4t*|gz$MSh$IcrsUfA5D4rW={T3!me`y&3) z-6gDs4XKFeC9PI9ooC-8O-bs(^x~yk%%)G%NW3LCv*yQq@%YC6y`Wrf&Aq_(ln=YW z?(TQXO<5Ibxb7hCnf{wretSYCq`QnnK|S?g4i$J2;{m}C9T2zWiP+-9tJi8B1sB-v z0%JvYToOPW!`(sx6Qts+{H8N#aI$p_*0Ae9nZ-@Tch23D@NJ%|zWz>lJd?Wy$7wZZ zPuZs5Tx+^R*DGTPcr_Li`daUB84m1Y{jeG+pODUEb@LZEuR%?gkW(o~x^-H*A%ScIHoGn9W8= zV+e>GQ@vtVDztsoi^nKjK8M;_Sh15OV3#ht4{`LSZ@|EK&3O1iW-EyE2U27>{e8HQ)X9}1_AA+B@jcZvoa0(8zdQ`f!Dj7nKo{0LTf60` zHQK$_^j|ZYg=K08NiW5^Yq8QEKWyo=PQ1q*ABHYW1MpuSvExK8;L}~@QcIuAuTT6% z(h;1$qL;F0oJyu%O9r;8n{ysMPiF}xAaBJFvw7z(7GckG0E^VUBaDkZA7i0 zKwS32X*#uXtmEq%oZDI1cc^HHI~cpKDVIyEJMGcdB2+8N1qwYHk@!ob{lcA=4uk0Y z=07$gIoV|+GPOzmE`Ya-PGvg4GK)_e_(|Ew#6=a#iMFc3X!tQv)2x>tB>L*@Gz(at z#?~yfU2nGI^_|T<;Ou%{_1651AFOUKuEPgKA8R`;ulRY2i|yAQpb@M3w?3=iQ8=4A zqH-WiSsA7?9}k>N4+k_A+X~RiJ-v-d&Z3z3W`_yikJ}L~dT=w}4Ea}3{Ky*$^z3^P zJ~gOla&~-O=mb9Yf>EtfibdtY(E1B{I4s7!#Y8w5Qb!AH98Z1sl+TqTz1#*sG`z)m zw}l*i0;?bm--@C&Ig2&*At#2F#{_-&-P<}dj1DvMj$Px)Dd%0Jg3idA-u&_)mZ^HN zs@8k`4d+fgo?Q3gv2a#ra4zdEQI>EZwXWyKfaaaA%?wMMXrpGjEL(l_$}-0+f}7Ro z-zwq9UKo#~a!0R&OX8MAvu5kXps?%*zv}U4#H|exQokW2W3t?Gc3}5kSaFj`&)4Y^Z&Dg1)Dv(cmSe z4T|Eop1M7)5Va`F5t&Oqv{3|!Mn(=h*zQW)D^mO`w2mQkoeV5KhLY4%yGhM zUw>#OnY4?aJ+(48KQ(R&kK-3st>7j@@@ICd?F7PG5x+5NKkwUa#hj17#4q(tw6U0g zila>iMW-E_Kj}05ao8wZbuEgW&~-YI^IQkqf#c5D@NYuwES=h5s-vJj2i1n25vNGF zok^syS6JS^N78E(n)4N{&3CUufbl#@YEN+Wc%e9I=B9-K))|Xf@!t z+ewNLSpXR6%xSgVko_#;Ps3Aq{+@k^zXZhh?0aGYBCjENa?b22*ZK4WLWYDCVC7Gn zdh%;r1#-%MIv*DXnxhgNdTEp=HNy}=6p5QlOj^)NP;6}=@^4-L_zPQy4mYl6ta76fh% zcz(D-Tg`HQx^>7PH1nU;mmg}hFc_?bq2?=KpD~f;{@($h+-mD_dV`9*8m9hwHtQHs zVoM5u{Cg!vtqgOW8F%A_{fN?S2Bo}K%bda7SZccvU}J2lFU*gZw~{@rsMKdz&0`u? z5ZMHwL(jssB~S^Xfv%cfS1!WGSidW3K6(u(?+1gJeLkqqqyWQZzBV4h)o~1G57=XJ zKG&Ei&q7rD4L{9oINf2x4eraIR;?-`z)Y9{ZU_LEHZ9T`@1VR{vRjaMJ)*7@@O-*; zC<-UabulymUe%cSF;$l-?M=y@aM#+&oP?Lz5TMHY5&-ATjVh!GSvo)ShaC)w*6iKLIO>U_n~|NJk0M1Rude9kr3%J zfJMVCC+KU!bgi)O{edN(c4VQ<;Y8#S2|;4Y6n4BL_g%l038n97;js0`ZpDWsv(MzL z@G9Uo_0bkWW(zcguUabIYO*R$s0FEl-SsROrTV-HVdBo?D&fHFx{neFw}a625fReuuaT8;c%fV3i<_^6qsS z2D@;I=}jm|+b29joVnyqW>YwZcy3)$d{kP|d=OXbuO-}mM8)zF%)YxzOaR0CYvlyg zb3Qgi>_#^_f_}v^wcvA|MtSGBA04%+Okt1qMUv~TeP(=I-ZvS4QGefQuKQY31{rq0 zJnECK%Hszp`dNYnqoujo?cyN(yAdHOW4f4p2(aa)gX?Arp?;u!yb)V$*@zG~=Q~4q zpE8Z2k|$P&tTHyPahDYEplUZHJIywq*hyow-*pc~#H;f5wt|6la$4`FBrEFylR5pA z#V5pwnSLx#{x;sacJ+m&ny=7u|qK0JJxvDw1Sm?iZoOAb#$GdZiJYW zleQiax3KZ#D-fwE|=Vre)QOhp@N33*$FS zcv&pAM=zOJQJ}XFVhA*B69{Cmz_T38O)pHtgV#Uxpn&UUFWEFoJ?E#L=PKr6#os>u z%I--`=_Kt->WC@SmfAXiqf+1V=E8Y!I>Um7DfPdMo0B8$pJqYN%mUQ>;kuRkuc~!d`G)`25jp(ufneOB9TeMA%N6{g9N! z>p&zi^++69%PiE+IHK5~v#5yf+}(oZAV2xP3cHaLvF?74WdwB>%E8A$_=p?ThznFJ z6Q5;U^XV+c9MnfytbflbD8xP+$(?~0^}c{s92+Sj{pUuJJpbv?jLz>1(<M0nY4xA7&baw3GSO8GrVN%!cx^EUgTrT`FP)&6e0kVo=SNq%HST zh|QL`39f%nywy6GpRAX6J3^sV7h<7mim}?7g-*JzD`cUO_`;4U3KKJpb{ZJlzD~sd zJq4sMglRL*7?hYL^K>q_T+LEkHm_35FA3!R#>8CtNk9bihNkOp)#UpmXmsA%DjFts zW|Af7A{=Oi7$=oHQ_wH7$Vnldl_kH28cjwzXs_JgED z$5onv9gNaWor84y)qc{PN^~YZCpMZv*5F4bg`GIOoE1mZD0HswgHy)trxZI_6aIXW z1C}_#XU4Wfnm~a#&}v3(`bWR+ipVYdi7eoBESmT{d)sZ;JO9lZFZYNRB^TV2C&hg6 z_*Yrqh-@iNT4ToAbTOeM50Ab3pB{5==hx?)Z3CY9j+0S;XratpIn!d38Ru^ZJ*eN$ zS$6hT8$YGz9+3n3n@bsb|9RW<{B*h7RW1p*?73Tba{43*)OD3M_+G%|l3ERnbl1+7 z4BEIy;L~aX4GISlgqy|{3}|lIPic4+N*65R3c^^?c;S-o_B{xC>hGx#vcAu`6;kprwbfTuFs4^;KxRmc9e>j zHY}*!RZxR;mNKGbfz0He~&Ma{mRYAulFED>?15$FA0-f zIedlTdz6)h`4kF18;_mU0ni-0Nk?)QlJ~`LWgtpK%#9c5%qKsowdHT7GkRl zSQP2PoMM#QU@-r=Mqw>9uDOMU%iOC3O2sk}jpDFNe;Nr{CC><*q8SSrKZo=hvtRD* zDtMcyfO~G=XMEN-DgJ$vqHX6w?ud8dqFnSlXnjz?omj4PUq)?R(q&7TI{`d~X7(5n z+}`ic-lR;6-6xjIhq`j|j!ax=4 zck)tW$74^9mNs&*Za{zb#RAts%!!`a#B2A!!uyH{Hk6SCypm&FrRPvI%{NGkGB{D` znqo2xo{LH)k6^WT$vqrd<>n(KDHMGfaL4)4fzwT=|DxTAd0nAZxvDozH^zmnT2E88 z>>y-VbLE_Ze9XiM`Sk?E2%Pm|JQT)O#*{M9VfcX6Ao|BNf1&(`(@%Njk5*H~{L{1M~Gk#K`b*Y%!;+@>>REwHe9EEl; zV$|IyF?iMC1bh@DAEIn+=n(c%?s})lg$smhHp@Gt3|X=*GhycFm4vt6mQEj2)uUkn zAA|a;(k+uo;>SRkBxPUBHm?)1Q=2}tkoGj9@o=FVIZUE@u|NOI1bu?YvGSPeJ0&|S zMX~lji8XUO?FuHJQHu)DqlzS`c0Y;t-+2w><~NxODEGx?yCo~j1!SB?O?arDr1KI^ zh?)E>7Lx%iM%Qz5-oI-?#5U}*@=JyQRjqFv*8=W4ze7N-P|RKR4rF+Lw$cUFFGora zCT$i&OHhyq9w5||nPt7Cmu^Z{xM8+Q$DxFcc@Am^^%Vx_EE5TqI=Y4hH(UqKevamB z^OAp*rtm-LJ$n=16MM`NgpdjT!ld7Cyhho>fVNrv^OE{T?5aIg%;8MSmBeQn51uV) znO1TqyxRPl+H4E)UcaJ7H!ZDeb}ZxdV~r}8-b+@*oJ)>bF^Jk1cG5wz*Qy9+D!FtllO4 z%$-($%sdh#1N0N!?N*5^#z^PzO69mpO1fB=c`l4UfdY!`B2OVF`J4?&Sju=!-!Vo= zlT6h=vM$X1mC^Fd&(8Yk{4H_Rd&$d~J+c!QZ@ahgJ8^8c+}qCu$)o>@^j6ij5}W@M z>0P!^nA}dFz_ zTv`UW2B<&t_ii)XY*yav12TkM{L!km!jx8imXjR)ntamy%ZT&N1A zR%{t7WAVa^0tP3>mw%&N*E!dAOXlKTj95cCk<%G&4htPEuEE1cpdSp5Hf4XY=p)d?3N{1z)056(=a4qS$?cmUCfF0Sl8hXV-d$v z?)K6=7lBShUvTIlm;p7D;ENi|x|tyEZBv3oPGp&K8Y+rd_zF@u8>gs|MYwx zg%*-Nfw(YVyiAHv?J8F?oJJwN;8S&6!{n(_bbVMv^jJjVzttnSN|E?Gf`m3KMMsZe z+Cv#2#cA&&6fF1Vt=hrC;T1A7IGuqP5)aL2vy~WOJ^#WD(g5ee0oiXeWHnPmoxMs~ zBG&O6`JJYe2|91a-dmHPDQTTYg3!Wgr@}vAm?5^9k4y+*5q}$_i*U;CibUhE!jISw zOPUWzMd4>1#QFf_Un~ChGT6QA(SF6RR&^G;ed~+^+cyq(ggK8u_XS7l+VTw;*Khsa z+#P&3PF&~%)0{*&xw!fH4Duq-uH-G|X)(dl)@kjBLEBMHF~trRh}W1d$wz|G4v(>U zy~>Q6<$g)^xR=nJPRIP#qizrdld@C@{V4nzh`>Ecgh=a%Ru#C$k>6K?j zm|Q>X%JbM@%5}w$IK=b%nL>R)T!^ec;O;18wKJIU2-C%>ivFiPy8jcn>= z*k9GcmEU7aJ99V|VFCQf52tpd_HghzSL7tL1`5xSO{V5cF)wfzt2V_0`JEX!v4tj8 z#1Q%6;U@-jcfFl zvoH}98=)bSVK`RpIn5c{xTpl)SFff|_b4BcU|HQstXno`hO^p%b4lCIv^YMb;U>H; zlPcohB@kUiDj2_DZ>W2l#Vcwj4dF70EYG4YstX~{hrwqK*1Raj7oGM2@T8U<&Lo7y zCf}oXV+;l_fHYa&^syt{0IuZxW8;9&U_&<(C-et+{{2md5mfxY2>DWTO^OwWRT&Eo z#%sCQ;1hcmlK{e2N8$+1Efh3~=osSR*#R1@ab^+)LL?ILCl@@LH0|APYK9nW?89+) zb@&PJ3RWz(HD9b(5?gKO?~)W4B|IX4$=mGP6U^Yu!W27lKs5E{gYNn&D@W{wG%V`j z7-HdtFHyO7!rHP9{^sO6Thgzc)=`81p&)PECm#hKLDEPLU+(1sG&m08_p&YH-dZ=E zhZUCtVhayF{MHW~&NCe|0dy8i^CC@NYx8F)mlQ^{fSo>4b}VPR!RXm+b0V^O#M<^2 z(YoY8L65#!wcKWNq5%i;6%9LNVWR35M7d5q6WL>Jie*-SEL*&_xkVwPet5~=G0(z6 zfWy@3Z+~`P;|u#YJ!JL<%!ps3+IG%uYCsdks_d+Is5Wql0ASpyP?W_Qf{7B!6ep#Z4b}@ zRy~veFI$3>%;y~G&=$Q*?*>L9$97i~3-N&pWjY*~ft#q{!>H`O026boJ*=b}b!87b zS)=wz-Lc)G3=O)N(QDpmyv-R?ahrwLm3D-DvX-I!+KkG0vo6Qc0;gkkZ@UMXb40yC zzxOCoT1U{ZsOe)Cf1=HL7L9sXY2J@s6Vl0X*1A5x#t*l+5_Jb#@zU4N*adjn8LrP3;DY$y`E1euB%1C^9mfr`S?1jh5?vEu`k6<$LT6 zjEb>rd_v=`K=2_-E$xKpC{(`Gc60bLNpZrOiYQW)Q$Kx@+)kudsA;0=%(h-OESmc6 zryHKYk@+Kq6}4cyr$qI)7`3)fsCZ#XaR4f0>F(5DCohDLUgjuduR`=b+RV)EVcOb{ zIrGESSJc>vO6LH&fWte9B$e0;8Z;AOAdq4>R7lyJ)31Amwic9Rh%E^nYQY~1Oz5oN zxx_vuOw*+r3r5MoXlJSGVdsw&Iz3Sc#hthmHLPb7JtjeGPBfPVM0Uzr*8G2@O zCrYYPgAkMn!$&GRNUb6&| zRPo<=hl#LU$(Q;#+9=YGv%2MS9OPq=^^yuZ&d=!egfd%VozTX#w9F73?*bdS4jHZDsZ1>BEI*Yw8tP+*wR zDsVF2No`srE3zg{@yws;%bVdSwwVuBkD&}0dLbPC_KN+bUI`n0^wif$wjs{fj|oQs z9)l*ZN=eVxPGa$-mdYM7J@K4Estsp|dk{xpy4KAv%tIX{3?cUR)V@r>&|)7^95VM( zrDH`PzX;Xe#&A2Y8Gd9ZHQmI_vieI5FRr82#{|$$N_l2$|IlgyGElVc?pK|sw2jZ0arc+bOTA_bj+INVB zwF*Y{SC=@^@Fd%AAe4nz7B?66&I zBuTnc?Lk)P>woz+xu+krMTpz*`zDU$i2HJc2S9%i8ygXA-rtCc3jstI!t(G)@VLho z?C&iVWX&Lz6r3=1pD(S7J&y;Z?qX&M{3Xg4^-Ne#oFl&5>m0dysnt&3ziV#<;(h?9 z^)YvP0`U%^y4m^U3n$v$P}FUYl*p!bwADW-WGnm~S$cO#-7Q8lr9S_pENBT3#E$YN zRNV8#F0Q$4%8SNOJJAUSkPK??#8Ylqc3J6v$-LMI;T{mpk`Z9Rjr@5tyCJiGfpTjn z-9JBxxz66Dr3GfM!0ssm`T{=8I!hAJR>4m9^-4EGr?02qYWXRZk!Zi7ub;_TAYf@f zmrW1>HyhwjCo&obo#h+V{{I5J-r_rL1ZIu)n=^Z%dricen9jJlbkKkt1QilvNr9CqL|TE$FDP7Xr6vL-x&d83{&O zw;6lne9Me`P~W|P`X28pwdLN?sUkfT4LM*1v-+8YHwY{;=n>Cm4__mN6QljRzqV=# z>-vY#qFh}Gsw<)8yqw4fE8DeSQMfJ(hJ{?m2^rgRFYoT6-)(ytouy9S@$IAfa6{S< z&z&wnyRGd)&o8DOxB zTCO~Ep~9TGNIoXbEEcBKELjUF{-VD@yGYtZ z=}EmWP{fWg)XirAyHLyUysvSBw}zv1=4;OEJx}8M`12naRuD|Cf*kJgN7BJKt;nxq zKAeuwRRK07ZtLmOB)x;m0=y1Yjj7k&L}(RF*x52>4|GONv$R^NEZT;~Q6jc5fO!jV z-^tL`j^+RjU&_&5B#)x4v6N7?V-KSo%%*vt4m<5yxo zb_sGnV#1TaMzsIlv`MmsX@Jf0nlGO#E`JDPm3^s=c(2?xkmw*O2GrzMmb3nVmlzke zHOWEEbGS4q1J8$sd&3}jc_o5Hyciw#T16HxW^4GUE7~Q`ZmCh5rch>k9G{b?Jo+aG za&YI1*7Qgcxf(E8UCL0OKB7W4;;K|To^Ca}f}onNz{hdTX)!MV2g)0l-DYRYz5}5s zhJboEo`anaO+`TEKy`7QvF)%@r=Rtk0^gRv>CBJbOAe31n3E=z;_aH=S3r`9Mw7A6 zY3ma}c*Cw0JM_GX3>@h8n0J(-sRX=dE$`diWSvkg|0KkyYlu;*f8gb>tJ{ht>iBfY zzaUy#-gmnk^y6OkcC&4?3B7n!I&z@J zszIuG^Q6B7JMoIeLvjmU905j=Z)lCH6Qf)7RbHlGNE{b!woTS7Ef~$JCH-Wv#;C zz%|6+ITo$p+Ml#VC|!qox(gs%Pz<&$a?Yf^?whnqxAjVg(B0dE|OR6&`O$B z1>LSFf$p!IqjWpi!lAi+`?>7e}U}L9=P7@TF#6b%KUn;pip~vg?Ioq!|e*8uE>nSrCb{f`xZ$X zv9x9Q@hEMLoG`9q4gqR(mAh-Crm?#8%iSdwwH5toV&gCjjf5}$`$+EOA_-|Jz&l-KNq6t71>Y&7rA^E%nvu%@h}=hu zI7}%Q1UBT1ba4%%r~V)%9eHxyR}>x%zfRZg6NAp?`^LfLPn~~q^4lEF!B)I{o)+Zo zgA@OLzQ=1+wNa>jCnrLw>flI6SP&>RCkz@$I_DoAmdmA)g;iua55DW<7M=!-lAF;z zl%KB;%E(!(;OQhalcgn#5MBFHCgtq5Ew0Hc133vSO)Ta?V~FuuVR+V-%5o0W)-N_m zI5l5+@QnIWSw^Ya319I@RSJ4NH9KsXB17m8IQP?M*)Dj2w|us7PtkUY?z2mA@eC({ z(^y){bbARXYG+&_bI-AesSN2t-c+7=DwJ~AW@;?E%p)>t~en21SZ88YR;#!O^=$f%-Ux82`X)7WBLm;`WJzXoP`;1j{;$~uTQtJ2AvyleT@KoXUhi!VbjeR)PUFmG>x&e}i9Vy0-(Mj}E z?ez`d*^!HQNAKgQ5@UXG+wMGM3ej+Z(u5DZ!d&oU{~EZFLkLe9MD5aL+TFKt4w(8yhtx{exE;JfupmWe(L@87%|HY6!OsoJ8 z7^9C)Qc3VE^y%oq7I{c&)o7UK;@u&wG@8&sA+1FJObN(dj2b zT?~31$}uqrJl9Cv3nR>+XOzdUgf^a|vTtTboZ#R&B0zeC!iF#w`vqTIkS1n?(gi~; z^n6{zce(%$fCa6xTVZqo*5x=afzK?_1QU5qG)`eI@szhbik32uoVD~pK|5(4g`aU| zYm%aSo9dUkJ@aLkLry_1B+KjJNaus!wyTftt3GCo|B%3~yCrZN0TcawK^H4KAP@O{ynyHrRh5L<33Z`A1X>z&BNd!g zFb@7aAwYj5S0vxoE8W!S4{b&|M!F`r(I1JQ(!~uw2UMomF>q6RhW@_Cu&+k*X zbq^`r26WbM7lj+VKz}6B456dK4LhisW-{K_`|UB!gh6J{b>EB;wr>`K=%o09G^8w1 zaP0ci0!`E)NL=D+(-Nl*!7T$7*_=`J_eL=D&{6tihPpIz_&7ian_D7%NV6zXVa*0} z&6>J_x#<$-TYV&iTz#!pd67FAFq|-otdk`D_5{ar4$gXrtf^=X$q&gFcBBR|(fV*8 zFd<5mNJVH-N$hWFP8QxHL(bBQ?s@Z{mAUj{)1kG8)M?!S zF&raioD-_=31r;Gp{dCx4}>1fa^^78BcZMAWDJ&*BF$)=)1JzEL!E6OZ;~fFb6>Tt z`zJrReqg?2T#}ylLyENSmLhEgO!TgtqQ#D<51uv&doBUBQ0?+*Y}V7ei`&~ONeihY z+>9A!%}049h;h2aYtL|+WmY(I0L6il({H1#_fqv^#?|&WzzM&$(@hI!8PIpS1@st@ zw>XN$*!&0gX{ahioS3oQ{Lr@4_Bvb5?dBU`sckO0bB0yl!gwT`W0TIkg7~G|(-lS6 zBKt&3I1pqYzTdJ45jOvXEvn^2`|S~>N$^!2exh4K)qtWcvRXcKuOh`o^`;h_{+zbp z4{6rATbi{IFj0Iuj0pcVqc0yX;Kj7&#XS+q7Sj+lXb|?9L30(58DSL9@bQ(<#&#rd zr{zv-f?LcSn##0WxzX0GEJIdP{YFK&3( z5FB+@n;F?NsghfMW@vhmIGd53%ZLs~k>(tAdR!14bTZx znX&NW1x){5x6BB2p+81WVrl=6vUBT^B*(GzUm4VU6~gcCgXsnaofUbH$jr{m^PqQL z2aD;!V)x(gM>7jIo#4&+E5|0o0S;*xmQD5Pl9QYJ}K-|O{lTMDH;*#(|j z|D?N%QcMJh`lZ^QZTcPArMY^Xr;+IKn0#r2>WV@=WsbU)_f1IX0bJ8X^^rs)D2i>i zx4l$6a)UNZKXg^afu_%Mi0m(W13gXsd>4vsq8{?GbqC!ONE|jCT4oeJCZ)!F;zBj^ zacMpKrxFDpn{0bjir{0B)#>(07edpL9XL3V73LE7Y}*T_&Rpj@xxc&(CAa!7?^(zLT|+Wi z@_tnhxo#7#1}3a0<*SecD@(~pPXZTe>;Wo#n1@^+36_9X70%WAwJ_~++``@By1>fS z0_${#{V#h1U9P?hxpuyVTw4g%+nJX`ggH=|>Z2;HwolxP4+AYc7(73*(>eu9R%U&cq#ZUScBmYKHxn0M4wl@lg066VB zK)&et2}U2dw7v@UgfwwS`!^w>&K%(jXLKplwxu3Ou7YjU-|ds*MRzB^_4uz!=gb6CBN3Y z!W)zb^>m8^Ez=p_2~Bk1l_vD{`h4$`l74X^%I^?0eJy_=iz3+T4z)s+Rf30xi7f*{ zyF|TNUqB-kCIjHm3-E+~lGIb(-F}e1`s}llT9dtRj!M<-o^Zc;l;o?B1X_(l(6z$9 z2{rxGVDQ9Iv)s4WCno~E_{ zC6%saTN*dF)QXxt=erUZTc%B(^D{P)-`shsD1+*$+K)O-FtrQrw?Z=E(n!ndy29Tn z`59eX$g3amy^gp9WDDQJ_19u>tKr7o*YKhF!f_z)TWoDb0lM$PGm#Dsh@sgrNs?YnP69fG)C&jLQ<;fI`G>)2D7 zFBB}GLEX6e4_&LD(x0}K!q`?_ZI$%uFuMzUcD@BZTL@OhPHl$hs?%U>fz&LFy|ruU z%`$CfZ0htoV;lGk)!lFF0zQ+{-%9;f9y;CDXONuh&o6EPQ=y(yfu!^x(K)%^fs-{- zNQ4Zkh$|+wqH%8`2VB%I1z8%+L6-sQ8~32X8=;!4?=vwNXLYI0 z#vSh7tw)_E?(pm(D%Q6^)Fzzi-e}&*jPxDb?ukwhs-y}%S3V4^X<$V|m*QVEJfd!y z_??QfZmXt$_NIc0KIy^%+#g8~StqXT#6SC1(a|4rNi<`OC#8=#*ol1mo}_#havC=9 zCSBrxu_5s#Qm;MCEi)oc8<)9EGC9@OC%SU*UFM6+Twv$ksX(aFY4kw< z)IU3a8+fh9-*XSdz-w;bzFDTtS2>!$^S40Lg1^0IpiUF~ zEzT{!%B|Lzm(O@7)4|+3g4g-PdmSdFkG6B<*rXF*-p&UZfjA)5I|an51@O@yv7MSB z*s1=cRHR-|$}G9mS_Zhhi_qEC8C|^R_U=%;LY7^B`l_F0L72a!9+9_p{W+q|c1dC= zp7HMNUxj?i`Of7OJ<2WAlp(+g>ejdvLW|f#P9yXt1~MN(O$2FQxA~!Ktt>BSIy&3R z&%X07nQW7>d8*Feg>XCHLbxpis|=ud=VtviWIzky=1#~r%d|-b01ouxs!DVTM5n9s zf`JS83#g;(uiWvOdddwa^U!s8q2X$xKV~|N?mvDnYy2G{BzVk+mS&%yaG5%drfaPM zN!LegqT<`3Ww6W2X;OseyE+Lz>ukaN#1Q%&3ZDGw6z}l@T+c!i{g2X!;7B1Z~j5P3H5P*CL7c_UNDA4*3P+7WO1k~BJmW3CA-4xhpx49yuj$Z zO#3exJz(*>5N_vN2)Bh`W$}z~X;pYx257dZSEYX=RFfJHG_APetSTGWM`v-Nh;*@` z#h$5y#ZfH_iwh0Mm0X13O>cA3v&^E?c4j`v)+2!hOqemjN1c7+~`GId?7R(_5RGywxJU#o4GHGP5H&9Jc3X?_bZov74qpy7xAG7 z;+s$-IB=jlgA3>{(pv^Wy6-X{o)*$#`!@X#U8~<~KlRV9#VedIxf10Dzubjjo2W;~ zmxW+u?-OYQSLjPai(dvPdvDw!CsZ?g6N)x1UWvHDz2VoRQrrTt3iW)054=V1l$^q3 z8~W*{+ru+Mm7W|VtEXOLYBFLfD?w|!uJZcP*H5ESfs5886&(>bB^PjwlKW0C=M%S@ zevK!8I^cbGkUranaBxX^aZ}O0*p$a9$gPk4Dm1~~+fj{0p+540iW5#KXg3%|vh|VQ zl>`Am{+yEh&{dV2TK>#EVyvux?keM#yFhFc^^lbf#CAw6iAb-i%J?#)urjzx-OL-I znpv6lQO?RzX$@BPIOC&E69##XSK0f2E7Vbojrdl-jOjZipE}}3TjcAnu(CA~J?u|_ z-LF5KWbS;ECxr8Ue?Hm8_R$x;Bd4iK&`(N6UK{pr%9LxEdwZI@fnwuNB5wz1Pz*h>R=z6{8`exN9l^SYqcBJYGO z0oTf9Djr+h?~h6`Ip6wL(OaROc>UtObtQ?az>0ISCB9dBE&*We>AbY~TY+TZx*yaQ z{OD_bAgbMPtt%OcQ?vzW@wb$vi=;5|pm_}B$)66=A?MoK+jti8sH{d}ru*4!O8EQe z+`-U5nkhCe&2O!a6Zc+>Qz9h1goxp9!q*LXD@6qd5qU>sq~`-A`o)cLMy< zKlhpXE+pIK7LsitxH9$e0T0C$_EOEizbylVsd+8V)CI~$W%9z*24&lN3E@Y1SU}nE zg{~sl9{N_OgQ@W&IbKz1rdr>$=vFj#7}W#G>wfu?N87F515e$#iWG zY9)OWYLIjs3FmM*RpjuI$mq&=CW%(WK(bsJ7l@h%dw<%Z9q6K`rq9cFA=oar5Nr#< z%FEn8hwHDA7`hA)UOu@?`;Aad$~X<%yfCs70dVy1{HPSI%rKz{{w2CPXx^>lurj!n zoTqH!;l0ow@Cl=Tu7|hb7ncFS)n~xV1FklFS{NIR%1XLyx*ls?t;<_D!Z$eEA778}_6)#7L(l;my18ZEUNqdnV=tYAUTLuW@GsdlPfv$y%jk~c}LQTOPztnXrUIST=PM^2$!mV9y;no&{ zmA8+elGeg4jZj&|t#QbW&E722X8pZX!unH*u+H6K_ox)`wzSMFo;Js=k;y+O{UDQb z*Mrxhe^>YOzE}EF6d&d`zWy4CW-=n~dc5!bl&g>W0H7`ey<4WbY!loTb>WAFnLqnZ zG1XDAx;uUHg`9BM_1FDu+r*k1rjpw1FgfM@Zw3vx0X^ZAGv5C71$)?m59+7vM9l`wwEWCJb#E|lBl7RqfQSOr3^|FUht zU<9o*_a4QZ^2=V}Y13s-rlf26hH?YK=vuy`+&Bx}qw{vz%PfV$px>d&>5T7`h7LYa zzH3mcQRpM-vO-Kh2o2d2)Sm#DfBl(`^U;?HnB&|4t~wcsv4^UBefAAq=t$rJ`ls?{ zIM+pQ&+mFdzf%jk`?9~-ln0u6dS=O2p$T3_t~dkdr$#WZUg2^Eq9?g)jo;-td$<-% zL-&^dp{pubHD7erKgjpd_5afI(Y?mGXMX#afBx_P_^*Hd<+shhZt?5?-_8C%{_(H> z>;Jv^&wu{oKmY50{_-0K=eNK9+duy0zyI>vzt7|&>->dcW!$r%`RYynGD6gJ+UgqT zD%oV91B-J{yHUkuZX}OJf@sF{?b=mul^pH`xp45nSs{vw_I=OUd!;|%%)fDWe$Wwo z6PyM)1Hn_x%8DX^s^O8VD?b1s6PKJ#{n>`Vk^r{y)#-sW8vw)ce>^7c8>IH*X3*{N zi!Xf7{q27AuSzuC+JlnL-;_KQcQeWDy8(9F47yl-x1&Z#urwI86&4qq@4(e{{d@m< z-aOoeZJWu*jBe<*aEh*VUK+L1U*;4>=lvB%7wB5t(T9`Y9DnL$d{nLkyQGaI7=6?C z#KPzX(jxHbIWF%C5$(C%dk1$b*oqdF(%`D*n&}8qs_281qiH7HKiV=b;LPO_jY7(0 z<)lKJ@4Yg*yb%~3j7LuDv`_a`p23xGEc9%rAUcnELd z#q~-jD4TIGtt&m_r)cU$p~5{1VBz$FZaWYHF>UR8?{F^zvcM-L$-BDHkN}yEYyUz?*P#Tkd3iLI8&-?HhT&`}Qw60)EdbbFrjAOC zs@qnjUIs)a$%CYN>UV+dn=K5ue%PA0C~D?iM`wF97uq>V07dq;13w<{WHZX z0Rkg(ts~Ra`)e&YTAMg3_T|yZS&t9QsL=bZ8XOSiA^{W|bs%y&K)dUVXagIfvDLsg z{MrNt?H)%YfUE&j8cx6D`lN&r*`bE-p*}SrNTdw!n2(hN_Tyv;i4{^@_z- z<95gb!_=LL!<~}aP-THl4C-XSeaHfZ!0q?6n$fR_4WMtFE@dPB!v@ef@%l-#3G?qM zIUNY=t)J(kqVt9t=>f}$Z8vAzr7)I@9B8V{v>KRW~+3RR8_!~ z_j&tfFj2px)I8x|ep`hBX;5S-&i}MJD!q)zP5^SJ9C)W>v$wA%z8F=Q2Gt4RCFG>5 zRW%I?-i!u;eONuVffsV44}Cv*8o`k1Nf13qPn4P~ijt5=ME?5&@Pe*Bj$n`#!8SW8 zX09i3}tcN4urn+EoJ(|Z~)}Bla-2Ka(A|Yt%Q49tFJWXywY5*4Jfg|=x9;`l(+NB}s zyB`sQL2zWm>&aE6vEcP*A_WOyfIlLc?g~LHWTHr?B>FaDK*WL~_yWJ@FkxhYKRXy|9AS3=MCtZ{kPI%2_|ft%2?NRS2kN}?lXRT6Z68?I^3lR`5Puv;K< zGho7jc}gDMlG4FkNr3M0`7`vP@O@qY>L~T|`pL5Jy{y+QTug)fcXD`&ZXhr2)trA9 zT95W5H#vW%Sak#9DOX5OBm4bjN@0BNy2AJcum@nqk-9orB*l7R8N7xvQFsb+nMKni zV0;0a5fDbd928lE@eQ4ZS1hX%cJPn^nV!o8U#J3V>{TEG6v9V<{gm6E*?omLD0n$) zb+()aLPrw=O1{_NA%=pzk~6kbHbP7s!}v0QvrCPkKJX10pc}l9u!UlM{drQnZ}8y~ z_3ZQjArBM*$KB%IT(1611KhIIl=!=noOzOcQ11IY2|xsNpT3(JzL#}9I2vF7PPvo9 z_i}z-&G&a9`8E%?pnNYBE8p{hhrOF#Mr6j16IU2tKv2_-|KOfj90WoYjBf}?7+*-Z z@zF`Uwmgh4NH-Xty0F?9zxzYN%y0i`0|L| zgVs)KalWmJ?%bddO7}Z$}Ln$N(y_?kJU6+=aeZ>Q(ezqqqz7ol~-c*0_c9 z4f#gWTwh;D2xM~FI^kr|84&`Rgifp!jqdA$NQ6vI(Lb^@`T$5If~?nTs-g+xzqcep z{`>2_1b0FCFcfcU0yD)b5v1)?1G)b)r$~e%rQayo>?MGeiw$6SJ}i1AK_WPyPx5E% z8bcyv^2pDLM7DmVDkOpoi0oZ-2VDdbL5N5lIt5~Hb$Pd(h6;{DkG|wGJGM_009yxs zQG9jRUlSlCLniRfG1*6r67=QuAj?PBJZgbFgJdDhfn;+yBv9!Cm%zjm3h?@gkO%?`e7K;c6^W2Zfc|f(N>&VlOhd$x%%wq zgZ%ebi-FC3(7=g&7&%xdu3~@)36886%upkAxC{^}uyarC8?ZH#07`*_J#loG0!oEm zP#Un?MCb*ilXSgLZJo^KdE>EWC;@xn2Z>g0!qX=BsXZ7IQKypGU){z-L6OC zqb)pg;K^qZL1Rn*REF$G_08F6kk@c5LT22OtL`?k%opKBPQ7(@Oe{0 zkPU`{WHrZ@i$LjE3JF_Uf2SA<{&qqjWIthn6Z$iVS7xVZQ455gKrTw0zHr2WZZE`v zBgDTdQHTTX6W0V}BHWkQ|3wDmXW=D$h#mshUr`EVMr=|Xy6Gqs0D&F_kDnp~^3w~v z;g|mX^-95g;KI#KaN$C+Dh1R-!G2tRE!$ujk+lLsgC?ZW3JSPzk6*fI1ws`vz`o0) zO%y5pxeCvwOtFxc@E1ZL z@24dMU6jb4!EYczb3{?nt zOi2&|jw-~Zw|+*`QZWq-O-Gwj5TV_Zgb*-f-x-{4KT!U@(38cut-=zCpa?@G0$1or zA{eR<2dn79E#Y8GKT@{#2FBQ5PY8!xZ_MN$!a<&ag8-*}-;_W&=(dr-^lC1;fG#|O z1w|ex3Xi8|Z>TL3d63Bo@6(LFCRsRO$68gqs=JGyoMX6st5q{aR5)joe=v99C&SR9@db(m*g;5kTO=D;Q&?3YEZK zGtxjNs@dHL0E5th(vCar zGMNgqE9Q>x?g@Z!xO@Si9}N!AB$)m<6OxP7&Jr9hXNm8QOKg29#-l%!yf~4$@$+<* z!}DKXuk;7d=nf8_aY-W%pD9)j&rPZ}Eo{IhW1W`!o_nTnxFD=14yOe}x$lLl&EY~a zRm|>3iePs`!_oHPoHa);@MQy{pceV@Bg0>TaYtlVqtb^?8|;pEKTTV$eOY(Z(4tAf z4NstA?*5=%Tu!iQ!36y6ka`GqV&n;wX9OM*)Mi(ELI`AflB2P-y$B-)fG#{Dgbpvq z^w;-{fXe;qyh?zQN9EU=JtQ-pv1^FxtZ%q_0p#FO80vs++jANq4rINaRgE~vf3M;| zxa!pxe&CDl5C=0HX(SG2id7uYu_$4MT7E!*I8A;)iU5`b!%Zfe;)6JAt2hvsyu3Lc}Eacta9JI_5v=a)r*ouMdwx;|UZzdT~P{jq3A+P^FV z+z;99neSTKzptFrJ#4H=V8GrEiU5$y?jc9}mjR%L0Fe&Gfo>bcYqPB?fi65$0tb^I z4l+?BPZ6-!kBv%@X^_DOt=!d4{X>ONcLI@d<$>}(zPe%sm zCTs(E0%iQGr9hyK?vMgAIB6sWW{On`paQR*uNo~hHX8vvO;UiPR4RuE*(?Q!))%E9 zPc{`4sA|8EgcQg?e0wfZz|e8HatH>GJb^MGYV=c&cBB-@ddN%Vb*IHEfQ!3;YZ34)p8DhP4~AzH|{6n}v-&4Pfya}flYY!L+R07ei9RRou9A_Reu z1SBX|bFWvvV9rR^JCp3xS5*-N3R0u59;+yBKgD-ZeO}lFOP^$ntMFv-SdMa zkYyYrE%AsC&jc)PKcPbt-$`+es-z(a?3Lgc!FS*Fa3Kj~04?50?V(cqq6C{~u=rQTbI);+(B$RHo0KP93kWDCoCE!RJB;VRItHp4ZNV0pP> z-N7d_!txZsC9vLA;9+@#)m@@o$B5;HJ}QobAP~pA>3|>f5z)L4rfUJb^pPU&!+-pUaX-j7EdYHA)M-PoII8AEbQW-SO-jjF#Ei(`aHG_Ll`+sKO7N zr)zqDRr*Qg&tA#m_mk<#4yvttbVe3Hzrpkpi{DSrDF`SE6LoEbA38gP?)Q`FXc4A= zM;K^9!Rw#Z`rqD%_?yVb_y3GJT7>}x=C9ql8m+nz?B)9BWED4`kS(&nlYWr}2KWQs zqVMI=NT$5t!v6v$g&zH`$H5o{0m_6GQ0hD35AK=Y(b)SF%& zaUu7Saw6%JwJv>IdbHT9Li@9=9tl89Cc~MF(XZQu<oI-ch+c2$N8#{s)v$$PW%`8ZIX$!xe#;2a0QL=~UjR~5#GiUMMd=r+C=_Jh3a%HD zu=MRyy;YKEV-7}JIL9W1=?xoqA=|xV{3sCxd(b+Q64>(N%aG_9LZ~EZwsvZ`_H7;X znjm@j`fHT=w;@0SHkA3kq3O$0LKa<6TGlsI`CkS^Z2ZA>T9{t3e7McPG19%s z?lTP>x~dsW?-)P)jsvq_$WL^FsmVWjbRPo7TWSO7aYIxxQ}?Od^$P5>1OC%DQEYu( zQdjX0c+eeeKck68ivLWpvOQU2r}fux@NEU5G&%Ut#H+0@sHuhRtF13oVe8wAf$fC^ zwwKb;CG%IvL+{6()koMP02Fve?+@*V;I6(xA7#4JxoDaC`4Pwha>sBGTz^#ouq9DM zbkmCI=~5=<6mz%%`qv?0gMfDLM?9bG^+b(N$?~_ag)ETML6K!}yK2ZY6-pBe4|1q0$1%2s6o1L}G`p%tl+r^Az4 zEC7WA_LPA@3Ed$TW>C>cD$EqCR5)e0PrZ!(2B^YK05oo|E&oQz789Vt|75Z#4djX& zQbABy+anUZ56VO_Q2g0$hm`B*x*!z{LkAeOr2GgCo+G^eS{}qs0}cRPMbrW~XH2es zeUt)Q1?5L_N57_0AVcu~hm-t}xrTR3Bak6eBr?Na{rkZE_KXur0wb)S&_`hv5~1{)qXx*FrvtAa z*TnT#;{cgH)Bwmw7C|P*?b=cU^az7va=a1DpJ{MF(?6o0E{sJWQ`V5b{JoL-r{lZq z9|)m4*nb8Tjo5#tcwzq}4dZ(>?|uO(&Fo)bn6STq;b!*t{BPWULKSy^sSc(juz$(^ z=EBd@OgOp>-*FQk{#U?RG>db+1m~Y%wF1(Tllxn6e_O^>P^Is%;>NdSP*Q)}%I;A* z6i57cw-{Zf=9O^EXLMwZ^2vbU2s)W6=W@;KdyY2A**32?zD#-|QSg)%H>1b)&-B6R zAe*}O1PXs9r#P|A_je^A7l&f<{iqNupXcMAAqqZd>}#`M%%{M8uLr2q0g}IOj(|<% z*Hixk8+3;Vn4v@?5inD%A|Ud4u7!YF9R#)lPntzQDg2KJ5YW^l0uG)7i3kv?a0-}W zh59cfhya?YcaH_qK(T0CqmmM1rvz!BSTt-xyxzqVkcsFI3q+!Y`Lk!AsggTLtw99X zv#B%F2*7?aIz+&cerT-=UgitF$H$&tmR+t~@G<}|DzZHPiU_cm3mRacLvz{7{HMSy z543nhK&GFfegjZ9A|R9Fd?@vI5dwNz@j7^|H#k4j5Se_SC~Pz6Vmnj>ZuZP7> z9HBlL|7y+;fY2SBKjVo;oIg{noWG@d&lUUAg7|L*qcn3q`6c>(;Cw+&&7ALTU*UYA z3eGPOe(Z#9<%g*L2P7z44^aaS-P3p#fBQ7dnNscvPxZcK?cbD z$N(0TT%tAZm+3vSGH_ERn12WxfMZ)erG~h%&L<1}ot4P?9lfV8N5rSnz3JlV+)=l&`q_t$w&zuVE zodf?gst^e!FsKtnV9-e3>MR(!*o!pE>^W}UDSedvqW@rDPkgWR^%@iO+c^5^mZ ze6RaKnT{_u0P%p4(EnUtl|EDi8d&HJ^vyoeLn{-=4(`-aNT>O()8KZdvTR?O`k~|$ zvDWE++VQ?A6Lf{ImI?R41$Q^$f(yke6Sy7L7Go{A;4(sF0y*-oCJ5P*5A2?IWP;ZP z^n#I(MiNoO4D2pZLzyw$1%cr28WhL}M+@Fa4-)$GAB@ixc<>w)u%N9d%m(kth*0ol zkm`S@o65Yb9*Ck%Pu06buF{m35rf`GiD7jZH!8&1{_z9Qs-G}v ziaYUiXjv0VM}VKbw%iCxHwgh$g!Pr!r$Y$H0`^fXm{MBTUojzsJ~}=S0Z7vq{RS~0 zPe2SH$ofqX(Gmk|yYvWzOi!1*!!FF$mtT##AP{s#7!derx*`gJU{Jn57-j>s zWkA>va*i4~pBNBAHyIF6cEu~QK?dMnz$M!E&|pB=12$lEa(x#&0Eh+|5C+6HS0=6+ zf}r=pzmPpfC7D>JrxuQBZ-2MXN%P`{V91X=gmLEAAL@aeit162zqh1;jDNK>xDRBw zy9qK}C{}5J$uw*pV$4t&7>Ew{NxhPh0J@CGM$M%_3&vQNLFvE)sEEX+EeRm_JlKc^CPTS| zFpwd$Hz8yC?JFV-WB{jlFYQxJl|vZVnoxuqz0Z&VLN_@PvIw?$Q4bW07Zy$*m5jlV z$$>4>Dz+F5@*VLwfTSt@giM1NI2{J{UXTbfrPolypOF9Fk_a;X)e<3aO0SPI>}FWg z$Y7W$R*A6o9}C}Z%c-b9t-u(8$t!gQmPbotftq5hRMCR0V2qR`a*Q0lh8$3g8wx*b z8d(X3=Zi#pI}m6BnTWbK(Lkf+Kh$Sq50JClRmZvh%d>Op+NIT4NQXq64);PM!l-n! ze_)W83&+b^b|FQN89oPm!1@E5#E3zflN{HujP0GI9%0yEBOBm`!PRS0B+ z(Y}Q;+-2<4RA4xY=T?tlCR>Dn1FsPRLKQ-wK#Y_mdXP$;{o*lHARNi2T5ntF@faG? zFUqM7J%)m8qki9>+Tzz=Q#9y`qJ(F@Hmhi`6-7aa zfKXvG^IfuiqOTAkpc@1QOemwvu*d_M|3uIgfW=5D$n;aWp#f$Bc_3eiq=<%6pl6tZ z1P(MoDabU$t&rz-$PofEPs2-?|Bf^*UP2j<5V#U(X@Om!lI~#p8N@VV`qj+X)=&0s&IDo}(HZ z5Ww3eg4t3kMCa$~7kWP5N!roZvR|kHwj|ma1K3=}gFXUlz?MYC%VR}%eJV=?$z{W3MY4v&etu501$mjM+10`Isk-J5B`_O{mtDj<(?40Zk^f;_;it8Z1;gDsHf zpTGq8J+Hs&3(Vx)NQ5DZ8X#wQOFgMUS7;|d16k#B?-u^%VmW+7bo|+6rM1Wu1yJ5TMo6hn$=ir9uY~DiVGgK>{I} zqK19+KK=kj!Cfct4c0>md6dXn*UL!Q8Y7VT z50~hz(61SfT*&mug=pQ-6(gtvnVeg^(Rn~fFz~28QvHMa`3a(upLzY&_@Qt}Lm1@0 zw}gR=?+b(deK5m`dMWJ5sDMpc8c5g@CgB-@@+0t5Z~$({j%V1q6(a0=MADVh+4TGpSQj4w7n zh#vq7$qiYu4(;uS7&e-3I%iaUsZIes82ApU)!aQ8-*ra(1Nch&iL5`Ad{do-^7Mv( zKvy>q0QX@E_ct+x3&kn`NX!~H6kM0J3^VT&{{ELgOnNEb)p#sNgLoL-LKipy9&ww2=vakRK^pxOE*=E1K-51CQ1umi_Z_)I7 zY6SAZV1;7ad(ltiLna5XL5ETI7wCfbI1+Pd=n_E?a^wTG^NPn%r}T*U&vKUBNuc8g zLLm@GuXhscXMobcNw83?LLv1X!(eMT36>Ee6eyupghIwf&2|9^?nNySD%1k+`h`#s zlBh;PfzN?mAebz=%}@=od%92&1jz<65HomGmssdIyF`t1;!*=iAR`b8_yUJOLR}H{ zNzqx-25K1uGz8fMcMi#ZS?6tl3UmlkA%L^VEm+DG=m4O&q+2HXf;YBpA9%0XKr5w6 zZ4XhGHCKKua3Z0fQWibI5LuAl3m6Cz28t|@LJ4sf7r%fqg||(G|ML`$ zh=GnD@PFWo?%@9!jx^%`nPTOC>iCYEj_nn76;8r1mDOq80$%ezVWI#r1gzTz^CdP3%&t5$Pic zVo;X6N?%yrmO>l$n~nh)LU&L+Yxq{Kzmi&@&xpz-=Op9Yjf-92@9?r4pbO9dgg($c zI9f&)LZ+Yci?H>23Ikl9$&Y~88a@I!B>PKXp7f(*7GxT-lMkeXCKs%dV;0DiZYCoE z^50u#fsD^ui^dH1fik*70?eSKkp!42t`cC21)F{?4M4G1lLP?EQ6xYnno9W=VPHdW$u}yQelA;NY9{YWMLYvCaa4N7b<$-yB!JK( zy})wxjd&!0F8l}v-46n;0qU|_%mc(BTn4k`sPz&N5Y3ca3b9#;wgzd61Y%c?%$+L9btCqje z<7uZ9Oy2_o+v~QeEQcT(nf{r6LJSVvDTshfj*8p`xt zndksAyYmLpK#p|r0m$fqEC_Vb9kO5sBaLLiOtH#>j2Sw&vIX`Ew3=iAc0Ebx+L$O0jmWI_LWK`hAfumK|bYY-b0;2IKwxpNk&IXtlkWI!}WJqB{W ziiJ!!i3QAtbs^OVka;8iK)bG%ED*Yg4o1_Hb%QNgV0+wBrOfYK1b}UflBk2WiQM}_ z59*o-;xHtr8^o7GGLoJe9DbQ7)O}7)*E)dfGdYQ0jW$Gj9u9#_>}xY{eV%cuK{9Yq z!u4|6oD1N3IlMP(kmz&+F%Y<-JH)^YLmG*JnPL?KNaw@#*Ge4N3$$q}PIdtNRm4Ch zo5jH1ON1f@get^9=~bX4hyjmj!TPeKs5J(Bt!FEu90)irlmjl}Q6u7I!2Y+t{@efg z>+jJhl%k`5{%%SH+P|Xc=r%&BV0|&D0rGV8QTh+JAi+u(UVosqEhe|!P)2I+J(gf{ zp+b6PwB$p>ycc>d)vynBD_rfmmFP9*U1{O$Ocb0g{m2URPsmy2u@UN%&kbiw&FAXL z%j9r2@!gE_F511!?gtyrmLq$cvt_ygX9q~=4$hu2MI+9hDOS#=yX~;!*j`{tGh+jY zFN~eZX2!-XTr7N{ica69e26>`l?)H|2neXl!#V{5ThxBT!!aZAJEh_#&s4aF_f)S= z7j)}RR~TF{wnX3xkIOm$6@mM2brz77{2ul4ZZ;g$AvpFZtmxg`V0T#n?7qXG=_(=I z=|Ug2{HXXVoUa=MQV)utt8F0!GJPoZmze zU3U-HyUgzPB|<=s_iZ5{)AfbG;XVZ5L_S0SE)=T}h~|`61W>E*z%n97`q1x+pHIkU zDL^qFmjX%^QlMP#N`e%K_Al3;@$TD=E-$)~NlgzI2tp1%F7UBAmTVH_z0MyTNse7B z5lAKuEuVDf~mqeIKn@zwdFor4frQ;K?=?rGv<@=yJfb z(bGlbMD=!80QAw(F@~V?+s6%t`5a9MT4mOf8C6F`w#cQ04MUX6qwORs}xAx$oS8Ow||*aqyR$Obpaup zr2uv2iWCrxA5!38Z1+e>e!&4p_2_=q8B##tRM`3_*FfGW4XhtcWd?n-V11hu$9gLh zK*%P}KV{cqaq$a%^gYKh(W(rfbWy+OBC)52>aRmW7oY}W<$Oc@@fxyUzhM4-!5~6% z>+*u_QTiwgNloUF5XkG9uzy)~Fg5X;eil+dkAIZPK>Fnn0;HaH7w}zYL}r~x21+8< z4Fa9L>H>OpZ|VX%-K%B7eZauOO<>?cvC0I>@sck4@=r@9NEx)*J^d43f5pv~|oeDK+p9P;4-wPO$h^gW*Fs%Td(nt?2YKt7GB z`T>S7Mvr`;oI*cN@>S_)he5w^6!Jl^e}qqK06>ohk^d6+7Yg(O3`e6u8IS|kPzL0@ zDqH-r(XVFv`=Eb^o6x_7;>z|>Z*px!)`I>mBSaOz*eh&bpnvpnFL+>?47WHq9i0fa zSLh!BDOw-&oO!UlLjN!x?0P^G5TqO|`G9qsL`f+Zqytb!0QhH&e2eSzGbXLzlB)(6 zFQ7hxx?x0`Z}cYCArS!JP$g5Tr6AbOaTc5gHJGsyWI*^0L}e|2Kcxp=O$k9$C=dWL zX($M!g6U_6>od`94}p*aK5hg6^kfkBleX;DG{_5x-j`HyUXNV?|LW{D?5D@LNCP>w zua*V@7ka&q@Gzr`20p@tVwDDTPaGMWf1_1xQq<>)Avj(NAVA0_!{D^5HOG}I8VDIH zA`SBM!bwPBjFEdlmKW8ZvTM)2Ig-Ipa&UR%6kdN-GU)uYK+I___1gZRj|jnV09n_y zyFc2J$XP`D&cTe9W}whI*h_kaS7QUsz%~v7nmS}7%|Hf#1m5QoM_vpDCBfe;x{4nY zB!f_e3%CH#Krg=(>_jqTqDeAj5mxPmOb#Z4fKhQ8$X5gtgr1``7zvpskbuW5pY5qA z2QsCX(7{uX|K9Qx$oN+agg_GAArNNZ(MTZ76sthMG8%U&I|!I*vI-)dzNiP8Y!(Pq zgDai_p+X>dx-J4iNH{Wd=<1$Z)B{;w^z_RmeyTtaj2eB#kAw#Mx#3s^fTj zBqn3PzWJYcl{IC6TGGXC5RwQa$ey8p zxYf{1MuarL&E3Z?APr~fm7cc3_$)@=#7rL*M9|wu(jb#ZQco=?rXTTe;vf|XkY~a4*d4NG zxhe;8EGncxPj~G|qAPl}3ERAHqOtH!UX6}5r}0)Lt%08sp* z1qi4rBml*?YtTTb!e3w$VIBy{5l8qSInKoK*bkwPyrOjq6%kM^Ly{}2LK+RuU!Kl zlq^I5$@u*`XCIY5Di_iSp&D49j4tAJR5%<(a6a1*qVa@CGOnVCil;gR{k9MDd#pJe?bcAG=K-G4cOII z5D0QIJOYY95RSMB9<=FRD19W+~;iYPA2@?uAw}SQBDx8Ic$V;ZWPSp7r+2KftJR&5- zWs|M_l1D7`zhAH#z}c;jXq6DtA(R)m002#9d`m(o%908o z5JBEx!$gq9{TdN;xOWj@8?W&4l2^D$tcZ}>kI_lK;V)ROFq5J93q)*21dNe_2tpbR z;z02i=s;mIjUGyWK2zsR9z4+DiKIyD+5&+*`KU?)doP7qS|F2l>rP#U-l7l{=E$V^@7L#=iL%N2?NUQmf+LncQ}7(j@u z6ctk`;)7n)@v{vS&VzwB01QyJUsU)R#Ro+^dz?3Hd|?e<;02UGfqpr968RED?{$uN$_E@OOaUeR*&We) zB@G&)q4!1_d{gg(QM5ttX9Chl?`Mfs?_q(D8<1Uu)U?D3YV%PMoAv%sYmBQDdheag z(0id+O{E45`cZb7Q!r8Qrf@h0vP@_=u8C38agm=>d+L6QsjtIEkL+V+_7}Yu?p^da zlKRE1>_WN42X=W}y16e$3j*Y&!FI^ufqT>Hmw5$ASn8kJ`ev6Gd6FPFCvP+v&^5ww0KT|>956s9d8QoTD7#RH^rZ_1T3^Hq>C#ziQc2RuXuGCDwXd}EJFdXkx@?SkxjkF>g7G%J+nEVC> z8H{ZQT#gF{5<-wmu;$tPw9c`86G8~`O(Hk}don*qVGZg{B60`|uo(R|`3s@gYV>EJ zs1He(q^^|#24wL_9^kWdi3*qjS)8ar=GOezfBgHOU;M5jd+h(yV2=k5e7^MH>P`TY zK$e|%+XN1D3E3`&NubxidlU#l(FQn}`9~u-m?c&?SV=-rgQx`u!f9II;8f#?LXDc> zprojNqU^lVg-ILN#fC{BB4H97MnoYIXQw*SCb_%pfP*|b0R+;D2E~nV2guXoySwL| zv%W8E2E9WB5d0xIAY`HFeiE+Ik){F%a!Gh9S*{6jT84ts)ri_=#%}YhU^jtrk;9hN zP_XBq^5P)58$&^7M807j-joKA$l_rr(9Ev;aRG@e9!!hcom9W)6(Xv$*+7Oo4Au-& z?3*VbgDjDTlOX^7SV@C+eG*X8;3XJG8?ay|D2=dSmRPX>%H`UGtlE)Z!l|lBhV8?!e4NHoOB0@*O^Kc#SeFEeC}nea|Ds639bh31BwhiCT?iT{~C;LWiR%Oe9WA zOs?^~tG_67|l+kfWVqq05kR^;IK=FydK*lXvFZZ4d09iF9SRnGjAUdJL4OLe;)+@D6TM+_%RUE`&FpYP8ADN2A zgKWy5?WGXG-1F(X(D{qXqwAPleWT)66%Sp<+t3~THx&=pjcd9t1~mQZ{FXm_iu=P+ z3d`t_f2ugHK>hn)`uB#W>y&bh6w~x=1j6e~S{iKii^NmYGd&1DY~%dDT%nro#J{gt z0mdLCqrX-kR;h3YmWqQa5}ID?ii-?~etbv$6n8XTIel22k``KwevzlFICTND+21NV zYPX26;J{=4y-o?G-;u%mqMC@#inHxr<8qAyfWAu@*}L7aiP29sFAh8xNusyY3s7JL zLK>rAM@>gWJ}QnKq(ign5m2D~(8C3~>j(<6IOj=P8Wb=P-XBK*Ize^Mi{L^Q#{$4e z=(kh=7nD_KiyK_Xf5%$$pn+YVn2?4G+lYhLm&CzEV#Nh*Jl4Vm;Wy1k0IZ{80SIeq zvH*xasT3A~5eFM#0u_m_BVM`uYaIq$z@YD+7r_N(^6_bfj986UbSr~c-yi$}teqETflP+_1b6axySf4v*yQV8 zj47b65Z%j`hUp9XpT#G$KiL`XR>c&^;sp5{?S@5UkN#8rXy5{{`4T-Elxai%mGo!c zD)c}9y`}$peP92#(FCtAX@ZNys{c`Xb=ZMyxkBxNnqMSh^9caNE&6XXL3-$v6M%?B z=iwo7SneBffzx0=1`3QO7)1g{okv7|Qa~!7jJ*MNiwO(*eq~Gqq1pu&NU!BqW`WO& z^H!s4^p84p&;j<@!md#b>;jq0)F#}?o&bQ&82SSQyXVi02f#qtcm$ww#$W{l;|bEP zNEZnJkVUS9q-)JlUAF)L4$Bb$=p1pDWNL#=007A1c=XD_KpBJ80MP6E0I-cCczwwc zTqITifIq&Hg8u|MXPP1zCvF6WB;;dADkc*|6yRIKx=vqb51~fAVv*2t;}b?T~5ynh!&* zV}ZPFs$y_bODfQa3+PPZ|YUK$mV~PQA^B5kE0b#4f0f3#g9sooN0B{cHXB#Lu!r29AZxR3y z{#DeN9aPu`0J1m$uqT72zc1hz6pCCF#FNr=E$Xn{0_GA~`rWMu1Hf*<_9)4YEgxaN z?~fWCEU`iX1+CWxWvz69tB{)(Tfi%W00lx%o1lP=wwwh-3Mgot1wJcgK~$dTM5SXXw&}T zaf_upB6!I*pEd8(2h$V|2J&L*%0!{T6lQ5z2KsJMiX)ce01mP|mH`>cz67ugvJl-1 zFVrsUjtK_n+Xph-*x+Q4We8Vbz7@+rR`CTZ=hO!(;6U$6`NW*~U1|-gj(GkFaG=8# z0-;_%2pI(3XagL~?4%JK%n~acP={ySs_ZI^s09vA^=Keuuo(_2gIES*ONLfjTmv!? zZ~&dIyRQNVMsj24&S!(VOJW@u&yDBLeybWc0TS#I%BPC&y5%j*m&~;X28T@y1iuA3 z(u^F69DRBYw;XIBzH6@(AW#k*j_ZE;(;^Re_cBQe8}Rg@51Q+T}417g26-2foK8cu#X;^#6}RALyZ^c7$wO- zuO~NZUH1HIJ-}M@Ac#gAAY$evjUZx{SRsN=9&0PJY72i!Wt++*q-KBd9teYNhKNQ~ zQKZ@sAtFG;A&2n|tbqvO!Ci5EQ@OJ2e&B-0MYj-2)uf%OWh%%e5L4JVT0CMIdIp1> z=Tq7h`Bb7yM&2}Arv;k|Hj$J7Zn+NRJx0S23xSjj1D#hCO(Tz?91`q3QV~!^Q2#7K z;Q|_0Qr{j#2*Lw$M=jERMNLdE;Up=#srhy~&Z|FpKm3uyb-t3NyWc0v*13 z4(0DUxxek|w}BuCM;m}(<|mDSV3v3S0q4(^M-&CPYvW z$!8lVjDkTiv9DG91IJRDcI8e~4f{YQ!4*{Q&rM4j=7C%h=>QZH8P*d1PUkge9OMj2 z+sMAR=OD;{cYf%t_@P|FaUdr`w`j)p=s3%SXwz^d=%ZsPKqa!z`m@c1$U?*+y4&zC zDo@1^k|2^g6th9*5N5+A_lUGj`8OS%T<-?YSV!DbPQlmeA2v)dI~&ED{#3Dw3vwqr zZU3zozmE#r2#B|r1jI#RMFm+Et)s$ng`q;B8jILOGyoL}(Lkjl&S3BfDhQJiRM?l} zfry}_R7ICyK?@0#$rc9%txjw=`BvwF#}9JV^@V*P^O}%Ajyd)=Kmud$MKzxmufXL9 zjWn91|ES|(qwgXm{P}C87Wl|#e3X6P4eBp5j>-+ACPS!ymY*7e*mm-(icEB4|KRAk z3f%+T8kl?|nYng~&SwRgX5Edex4RUI? zb6-G$EKbZeoS?!aE1?9vqMQ$G8I<#Z{QZ4PU>oJ|_L6eANUT_}wk?Bv(@I5Hu6T(B z!W5i+1t(K}k2yraRrf0LG6=2kxU?AWs z(p_M)+X4;w2DN5+X@}#bxLu zMDLOW5QsM?sD*M4|q=a&@@B^v_T; zJbI{F^tSp3V`+nTU?wR*_LA|H&s z1Esab%tO6oYAdp98Uf1lgBdJ7jus^3D{zWN9mP_W%x4^Yz+CvK91V0D*V;ys^MHO! ziatY?e*OxdK*TR}kF1eH5uw*Wsb?Eg%q{)B^|%=ZG9;vBgVxsz4x8&3ffJ(BviT_rk^` zP#PDDaa_6!i@=Cqdn%kaI0Pa#wId^BHquR^g?Z#z{Rh30IRh9?j}%~OpwK!4$R!x< zXFgz&!!sZ+niPr`BXbqmM2IXJp{X|4H9JW;1EC$A0a^& z$2KY3FQJ1hf=4l2mfg*svw@8G5ocM>NvHqN@9Ro0YNaUMlv70^nKSkBt9>A(wyu33Qeht$zH&Mc5nv(0e9vF0T`1yF zWt+-pEu$chibMNw&gFBeGXbMO=yccUFb2>5`D?}wjIKteVs3;~s6~1U;$alkc|kYy zOVFA_D&Y@|H9WfsJL+L7R62oB-4;wJ<;`WVa2EmJ`*xp9e9 zE2|L@tOjA+aU79qV$CR{+=K_gm61~o>H$J7fP^JQkP@$cDm{(`k>x>X(6&@B5ScI= zr2a}hK**3pEBw@s*&r+Vcw@KeXGvi;=*q&SAbFcDfRHy`0V4moG7e;R?*hbq8w+s~ zAF>b^i4`EI6^3(t`L*~)%N4I&iX!$nOB8@msQ`#l*g!=BAS46VV+$hAahx2K+#9g2 z|8L;LCc@-&g6A|bMhm%wTo_Cabwrfdf?P3y1=dy~|LAjkZOo#iQvmRk%lAUr!tx2dFPEPel9}yXwcks9sZ*7l#61vxnR4#kY80itcHWM zSyl1hRQz(o=~xmPMQlNZ+7(fy0u@S5ql!eH!k|yV*Yq*SDo#nxD-rXNih~Nu zNW>D_=a6H=+o12@Ba2Sv)bKUR6>}}rOvWjv3Vn>orxI*y)j#nkj~1~*!}mvh@+dG# zUj#WFl_Vyg7Y0FQU6?#jVKhaW4P~S#63D&_%PO zF2dCm7Ie^sMbd-^#{X2Ad&zb7wOLFDxy9RCx6lRr*hRQY&nBcUjEBt*3O9Yutyv>)Mv6t{wYHg*g8)pbu7V1g)zr+tD6HeNvmx&Ccb*hXNy zza%g&5-Td?Qc1_=fkCRobtz2Cz={fm$*fYK!lg7)QIVGvCJ|pafG-RMc_6wIb6|LZ z3i*orM~!NskZ#V>?b8u~9^7?VKU zC-rEcAB)WK@NV>G02IjZwI-5%{6b>V>)(cgpgz4HnY_&ZgC#i^Ws>PH~KN!&b@LT$W3t3 zlYyB%i-qp2vkDVm>ep1yqH-HA=SU>zDIea0LUYWCE2ZADh+o4Ns6hx>1JIu^1-jl2 z4iNfbvX0xi6>K4>8#Q<$><9YG5r;ZZmTk3p_)+Hy6@6VZj8@_4N%gp3T! zfY{9thd>J@xPcZD`mQbpxdloVbs|~;Ns%J}4+SdsGi^bHyhf@5(iC!4hgOLMeUm^0 zgs1^QkpqRzo*GI6≺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%0Ad)~0Q<6_e3gU!{P$|= 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

Q5usTh)1aBjmM^DKXBbLFe^_ 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 - Task status management +- /tasks - Task pool management (NEW) +- /download/ - File download +- /reports - Report listing +- /health - Health monitoring +- /stats - System statistics +- /config - Configuration info +- / - Web interface + +Usage: + python server_waitress.py + +Or package with PyInstaller: + pyinstaller --onefile --name GasFluxAPI server_waitress.py +""" + +import os +import sys +from pathlib import Path + +# Add the project root and src directory to PYTHONPATH +project_root = Path(__file__).parent.absolute() +src_dir = project_root / "src" + +sys.path.insert(0, str(project_root)) +sys.path.insert(0, str(src_dir)) + +# Set environment variables for production +os.environ['FLASK_APP'] = 'src/gasflux/app.py' +os.environ['FLASK_ENV'] = 'production' + +def main(): + """Main entry point for the GasFlux Web API server.""" + print("Starting GasFlux Web API with Waitress...") + + try: + # Import the Flask app + import gasflux.app as gasflux_app + from src.gasflux.app import Config + + app = gasflux_app.app + print("✓ GasFlux app imported successfully") + print("✓ Task pool management enabled") + print("✓ All blueprints loaded: upload, tasks, task_pool, download, reports, health, stats, config, web") + + # Import waitress + from waitress import serve + print("✓ Waitress WSGI server imported") + + # Server configuration from environment variables + host = Config.HOST + port = Config.PORT + threads = Config.THREADS + connection_limit = Config.CONNECTION_LIMIT + channel_timeout = Config.CHANNEL_TIMEOUT + + print(f"Starting Waitress server on {host}:{port}") + print(f"- Threads: {threads}") + print(f"- Connection limit: {connection_limit}") + print(f"- Channel timeout: {channel_timeout}s") + print("Press Ctrl+C to stop the server") + print("=" * 50) + print("Available API endpoints:") + print(" POST /upload - Upload files for processing") + print(" GET /task/ - Get task status") + print(" PUT /task/ - Update task status") + print(" DEL /task/ - Delete task") + print(" GET /tasks - List tasks (paginated)") + print(" GET /tasks/stats - Task pool statistics") + print(" GET /tasks/active - Active tasks") + print(" GET /tasks/queue - Queued tasks") + print(" GET /download/ - Download files") + print(" GET /reports - List reports") + print(" GET /health - Health check") + print(" GET /stats - System stats") + print(" GET /config - Configuration") + print(" GET / - Web interface") + print("=" * 50) + print(f"Configuration: {Config.to_dict()}") + + # Start the server with valid Waitress parameters + serve( + app, + host=host, + port=port, + threads=threads, + connection_limit=connection_limit, + channel_timeout=channel_timeout, + ) + + except KeyboardInterrupt: + print("\nServer stopped by user") + except Exception as e: + print(f"✗ Error starting GasFlux app: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/src/gasflux/app.py b/src/gasflux/app.py new file mode 100644 index 0000000..a3c1ab8 --- /dev/null +++ b/src/gasflux/app.py @@ -0,0 +1,728 @@ +import os +import shutil +import sys +import uuid +import logging +import threading +import time +from functools import wraps +from pathlib import Path +from flask import Flask, request, jsonify, send_file, render_template_string, url_for, g +from flask_cors import CORS +from werkzeug.utils import secure_filename +import yaml + +# Shared utilities imported from shared.py +try: + # Try relative import (when run as part of package) + from .shared import task_status, TASK_STATUS_PENDING, TASK_STATUS_PROCESSING, TASK_STATUS_COMPLETED, TASK_STATUS_FAILED +except ImportError: + # Fallback to absolute import (when run directly) + from shared import task_status, TASK_STATUS_PENDING, TASK_STATUS_PROCESSING, TASK_STATUS_COMPLETED, TASK_STATUS_FAILED + +# Blueprints will be imported after app initialization to avoid circular imports + +# Environment-based configuration management +class Config: + """Configuration management using environment variables with defaults.""" + + # Server configuration + HOST = os.getenv('GASFLUX_HOST', '0.0.0.0') + PORT = int(os.getenv('GASFLUX_PORT', '5000')) + DEBUG = os.getenv('GASFLUX_DEBUG', 'false').lower() in ('true', '1', 'yes', 'on') + + # Directory configuration + BASE_DIR = None # Will be set dynamically + UPLOAD_FOLDER_NAME = os.getenv('GASFLUX_UPLOAD_FOLDER', 'web_api_data/uploads') + OUTPUT_FOLDER_NAME = os.getenv('GASFLUX_OUTPUT_FOLDER', 'web_api_data/outputs') + + # File size limits (in bytes) + MAX_CONTENT_LENGTH = int(os.getenv('GASFLUX_MAX_CONTENT_LENGTH', str(100 * 1024 * 1024))) # 100MB + + # Logging configuration + LOG_LEVEL = os.getenv('GASFLUX_LOG_LEVEL', 'INFO').upper() + LOG_FILE = os.getenv('GASFLUX_LOG_FILE', 'logs/gasflux_api.log') + + # CORS configuration + CORS_ORIGINS = os.getenv('GASFLUX_CORS_ORIGINS', '*').split(',') + + # Task management + TASK_CLEANUP_INTERVAL = int(os.getenv('GASFLUX_TASK_CLEANUP_INTERVAL', '3600')) # 1 hour in seconds + MAX_TASK_AGE = int(os.getenv('GASFLUX_MAX_TASK_AGE', str(24 * 3600))) # 24 hours in seconds + + # Performance tuning + THREADS = int(os.getenv('GASFLUX_THREADS', '8')) # Waitress threads + CONNECTION_LIMIT = int(os.getenv('GASFLUX_CONNECTION_LIMIT', '100')) + CHANNEL_TIMEOUT = int(os.getenv('GASFLUX_CHANNEL_TIMEOUT', '300')) # 5 minutes + + @classmethod + def init_base_dir(cls): + """Initialize base directory based on environment.""" + try: + if getattr(sys, 'frozen', False): + # Running in PyInstaller bundle + cls.BASE_DIR = Path(sys.executable).parent + else: + # Running in normal Python environment + cls.BASE_DIR = Path(__file__).resolve().parent.parent.parent + except: + # Fallback to current working directory + cls.BASE_DIR = Path.cwd() + + # Initialize directories based on config + cls.init_directories() + + @classmethod + def init_directories(cls, output_dir=None): + """Initialize upload and output directories.""" + if output_dir: + # Use config-based output directory + output_base = Path(output_dir) + if not output_base.is_absolute(): + output_base = cls.BASE_DIR / output_base + else: + # Use default relative paths + output_base = cls.BASE_DIR + + # Set upload and output directories relative to output_base + cls.UPLOAD_FOLDER = output_base / "uploads" + cls.OUTPUT_FOLDER = output_base / "outputs" + + # Create directories + cls.UPLOAD_FOLDER.mkdir(parents=True, exist_ok=True) + cls.OUTPUT_FOLDER.mkdir(parents=True, exist_ok=True) + logger.info(f"Directories initialized - Upload: {cls.UPLOAD_FOLDER}, Output: {cls.OUTPUT_FOLDER}") + + @classmethod + def update_directories_from_config(cls, config_path=None): + """Update directories based on config file.""" + if config_path and Path(config_path).exists(): + try: + with open(config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + output_dir = config.get('output_dir') + if output_dir: + cls.init_directories(output_dir) + logger.info(f"Directories updated from config: {config_path}") + except Exception as e: + logger.warning(f"Failed to update directories from config {config_path}: {e}") + else: + logger.info("Using default directory configuration") + + @classmethod + def get_log_level(cls): + """Get logging level from string.""" + levels = { + 'DEBUG': logging.DEBUG, + 'INFO': logging.INFO, + 'WARNING': logging.WARNING, + 'ERROR': logging.ERROR, + 'CRITICAL': logging.CRITICAL + } + return levels.get(cls.LOG_LEVEL, logging.INFO) + + @classmethod + def to_dict(cls): + """Return configuration as dictionary for debugging.""" + return { + 'host': cls.HOST, + 'port': cls.PORT, + 'debug': cls.DEBUG, + 'base_dir': str(cls.BASE_DIR) if cls.BASE_DIR else None, + 'upload_folder': str(cls.UPLOAD_FOLDER) if hasattr(cls, 'UPLOAD_FOLDER') else None, + 'output_folder': str(cls.OUTPUT_FOLDER) if hasattr(cls, 'OUTPUT_FOLDER') else None, + 'max_content_length': cls.MAX_CONTENT_LENGTH, + 'log_level': cls.LOG_LEVEL, + 'log_file': cls.LOG_FILE, + 'cors_origins': cls.CORS_ORIGINS, + 'task_cleanup_interval': cls.TASK_CLEANUP_INTERVAL, + 'max_task_age': cls.MAX_TASK_AGE, + 'threads': cls.THREADS, + 'connection_limit': cls.CONNECTION_LIMIT, + 'channel_timeout': cls.CHANNEL_TIMEOUT + } + +# Initialize logging with environment-based configuration +logging.basicConfig( + level=Config.get_log_level(), + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(), # Console output + ] +) +logger = logging.getLogger("gasflux_api") +logger.info("Basic logging initialized") + + +def log_performance(func): + """Decorator to log function performance.""" + @wraps(func) + def wrapper(*args, **kwargs): + start_time = time.time() + func_name = func.__name__ + logger.debug(f"PERF: Starting {func_name}") + try: + result = func(*args, **kwargs) + duration = time.time() - start_time + logger.info(f"PERF: {func_name} completed in {duration:.3f}s") + return result + except Exception as e: + duration = time.time() - start_time + logger.error(f"PERF: {func_name} failed after {duration:.3f}s - Error: {str(e)}") + raise + return wrapper + +# Task status management +# Task status constants and storage moved to shared.py + +def update_task_status(task_id, status, message=None, results=None, error=None): + """Update task status in the global dictionary.""" + timestamp = time.time() + old_status = task_status.get(task_id, {}).get("status", "unknown") + + task_status[task_id] = { + "status": status, + "message": message, + "results": results, + "error": error, + "updated_at": timestamp + } + + # Log detailed status change with context + log_msg = f"Task {task_id} status changed: {old_status} -> {status}" + if message: + log_msg += f" | Message: {message}" + if results: + log_msg += f" | Results count: {len(results) if isinstance(results, list) else 'N/A'}" + if error: + log_msg += f" | Error: {error}" + + log_level = logging.ERROR if status == TASK_STATUS_FAILED else logging.INFO + logger.log(log_level, log_msg) + + # Update task statistics + stats_collector.record_task_status_change(old_status, status) + + +# Statistics and Monitoring +class APIStatsCollector: + """Collect and manage API statistics.""" + + def __init__(self): + self.start_time = time.time() + self.reset_stats() + + def reset_stats(self): + """Reset all statistics.""" + self.stats = { + 'requests': { + 'total': 0, + 'by_method': {}, + 'by_endpoint': {}, + 'by_status': {}, + 'response_times': [], + 'errors': 0 + }, + 'tasks': { + 'total_created': 0, + 'total_completed': 0, + 'total_failed': 0, + 'by_status': { + 'pending': 0, + 'processing': 0, + 'completed': 0, + 'failed': 0 + }, + 'processing_times': [] + }, + 'performance': { + 'avg_response_time': 0, + 'max_response_time': 0, + 'min_response_time': float('inf'), + 'uptime_seconds': time.time() - self.start_time + } + } + + def record_request(self, method, endpoint, status_code, response_time): + """Record an API request.""" + self.stats['requests']['total'] += 1 + + # Method stats + if method not in self.stats['requests']['by_method']: + self.stats['requests']['by_method'][method] = 0 + self.stats['requests']['by_method'][method] += 1 + + # Endpoint stats + if endpoint not in self.stats['requests']['by_endpoint']: + self.stats['requests']['by_endpoint'][endpoint] = 0 + self.stats['requests']['by_endpoint'][endpoint] += 1 + + # Status stats + status_category = str(status_code // 100 * 100) # 200, 400, 500, etc. + if status_category not in self.stats['requests']['by_status']: + self.stats['requests']['by_status'][status_category] = 0 + self.stats['requests']['by_status'][status_category] += 1 + + # Response time stats + self.stats['requests']['response_times'].append(response_time) + + # Keep only last 1000 response times for memory efficiency + if len(self.stats['requests']['response_times']) > 1000: + self.stats['requests']['response_times'] = self.stats['requests']['response_times'][-1000:] + + # Error tracking + if status_code >= 400: + self.stats['requests']['errors'] += 1 + + # Update performance stats + self._update_performance_stats() + + def record_task_status_change(self, old_status, new_status): + """Record task status changes.""" + if old_status == "unknown": # New task + self.stats['tasks']['total_created'] += 1 + + if new_status == TASK_STATUS_COMPLETED: + self.stats['tasks']['total_completed'] += 1 + elif new_status == TASK_STATUS_FAILED: + self.stats['tasks']['total_failed'] += 1 + + # Update status counts + for status in [old_status, new_status]: + if status in self.stats['tasks']['by_status']: + if status == old_status and old_status != "unknown": + self.stats['tasks']['by_status'][old_status] -= 1 + elif status == new_status: + self.stats['tasks']['by_status'][new_status] += 1 + + def record_task_completion_time(self, completion_time): + """Record task completion time.""" + self.stats['tasks']['processing_times'].append(completion_time) + + # Keep only last 100 processing times + if len(self.stats['tasks']['processing_times']) > 100: + self.stats['tasks']['processing_times'] = self.stats['tasks']['processing_times'][-100:] + + def _update_performance_stats(self): + """Update performance statistics.""" + response_times = self.stats['requests']['response_times'] + if response_times: + self.stats['performance']['avg_response_time'] = sum(response_times) / len(response_times) + self.stats['performance']['max_response_time'] = max(response_times) + self.stats['performance']['min_response_time'] = min(response_times) + + self.stats['performance']['uptime_seconds'] = time.time() - self.start_time + + def get_summary(self): + """Get a summary of current statistics.""" + current_time = time.time() + uptime = current_time - self.start_time + + # Calculate rates + requests_per_second = self.stats['requests']['total'] / max(uptime, 1) + error_rate = (self.stats['requests']['errors'] / max(self.stats['requests']['total'], 1)) * 100 + + # Task completion rate + total_tasks_processed = self.stats['tasks']['total_completed'] + self.stats['tasks']['total_failed'] + task_success_rate = (self.stats['tasks']['total_completed'] / max(total_tasks_processed, 1)) * 100 + + return { + 'summary': { + 'uptime_seconds': uptime, + 'uptime_formatted': self._format_uptime(uptime), + 'requests_total': self.stats['requests']['total'], + 'requests_per_second': round(requests_per_second, 2), + 'error_rate_percent': round(error_rate, 2), + 'active_tasks': len([t for t in task_status.values() + if t.get('status') in [TASK_STATUS_PENDING, TASK_STATUS_PROCESSING]]) + }, + 'requests': { + 'by_method': self.stats['requests']['by_method'], + 'by_status': self.stats['requests']['by_status'], + 'top_endpoints': dict(sorted(self.stats['requests']['by_endpoint'].items(), + key=lambda x: x[1], reverse=True)[:10]) + }, + 'tasks': { + 'total_created': self.stats['tasks']['total_created'], + 'total_completed': self.stats['tasks']['total_completed'], + 'total_failed': self.stats['tasks']['total_failed'], + 'success_rate_percent': round(task_success_rate, 2), + 'by_status': self.stats['tasks']['by_status'] + }, + 'performance': { + 'avg_response_time_ms': round(self.stats['performance']['avg_response_time'] * 1000, 2), + 'max_response_time_ms': round(self.stats['performance']['max_response_time'] * 1000, 2), + 'min_response_time_ms': round(self.stats['performance']['min_response_time'] * 1000, 2) if self.stats['performance']['min_response_time'] != float('inf') else 0 + } + } + + def _format_uptime(self, seconds): + """Format uptime in human readable format.""" + days, remainder = divmod(int(seconds), 86400) + hours, remainder = divmod(remainder, 3600) + minutes, seconds = divmod(remainder, 60) + + parts = [] + if days > 0: + parts.append(f"{days}d") + if hours > 0: + parts.append(f"{hours}h") + if minutes > 0: + parts.append(f"{minutes}m") + parts.append(f"{seconds}s") + + return " ".join(parts) + + +# Global statistics collector +stats_collector = APIStatsCollector() + +# get_task_status moved to shared.py + +# cleanup_old_tasks moved to shared.py + +def process_data_async(task_id, data_path, config_path, job_output_dir): + """Background task to process data asynchronously.""" + logger.info(f"Job {task_id}: Background processing started for task {task_id}") + start_time = time.time() + + try: + update_task_status(task_id, TASK_STATUS_PROCESSING, "Starting data processing...") + + # 1. Load and override config FIRST + logger.info(f"Job {task_id}: Loading configuration from {config_path}") + config_start = time.time() + + try: + with open(config_path, 'r') as f: + config = yaml.safe_load(f) + logger.info(f"Job {task_id}: Configuration loaded successfully with {len(config)} keys") + except Exception as e: + logger.error(f"Job {task_id}: Failed to load config from {config_path}: {str(e)}") + raise + + # Update directories based on config output_dir + Config.update_directories_from_config(config_path) + + # Sync app.config with updated directories + app.config['UPLOAD_FOLDER'] = Config.UPLOAD_FOLDER + app.config['OUTPUT_FOLDER'] = Config.OUTPUT_FOLDER + + # Update task status file path to new output directory + from .shared import set_task_status_file_path, load_task_status_from_file + set_task_status_file_path(Config.OUTPUT_FOLDER / "task_status.json") + # 立即从新路径加载现有状态,避免后续保存清空文件 + load_task_status_from_file() + + # Update job directories to be under the correct config-based paths + from pathlib import Path + job_upload_dir = Path(Config.UPLOAD_FOLDER) / task_id + job_output_dir = Path(Config.OUTPUT_FOLDER) / task_id + job_upload_dir.mkdir(parents=True, exist_ok=True) + job_output_dir.mkdir(parents=True, exist_ok=True) + + # Move uploaded files to the correct config-based directories + try: + import shutil + + # Move data file to correct uploads directory + if data_path.parent != job_upload_dir: + new_data_path = job_upload_dir / data_path.name + if data_path != new_data_path: + shutil.move(str(data_path), str(new_data_path)) + data_path = new_data_path + logger.info(f"Job {task_id}: Moved data file to {data_path}") + + # Move config file to correct uploads directory (if it's a custom config) + if config_path.parent != job_upload_dir and config_path.parent != Config.BASE_DIR: + new_config_path = job_upload_dir / config_path.name + if config_path != new_config_path: + shutil.move(str(config_path), str(new_config_path)) + config_path = new_config_path + logger.info(f"Job {task_id}: Moved config file to {config_path}") + + except Exception as e: + logger.warning(f"Job {task_id}: Failed to move uploaded files to configured directories: {str(e)}") + + logger.debug(f"Job {task_id}: Keeping original output directory: {config.get('output_dir', 'not set')}") + logger.debug(f"Job {task_id}: Updated directories - Upload: {Config.UPLOAD_FOLDER}, Output: {Config.OUTPUT_FOLDER}, Job output: {job_output_dir}") + + config_duration = time.time() - config_start + logger.info(f"Job {task_id}: Configuration processing completed in {config_duration:.3f}s") + + update_task_status(task_id, TASK_STATUS_PROCESSING, "Configuration loaded, starting preprocessing...") + + # 2. Data Preprocessing (files are already in correct directories) + logger.info(f"Job {task_id}: Starting preprocessing phase...") + preprocess_start = time.time() + + processed_csv = data_path.parent / f"{data_path.stem}.processed.csv" + logger.debug(f"Job {task_id}: Input file: {data_path}, Output file: {processed_csv}") + + process_file(str(data_path), str(processed_csv), str(config_path)) + + preprocess_duration = time.time() - preprocess_start + logger.info(f"Job {task_id}: Preprocessing completed in {preprocess_duration:.3f}s") + + update_task_status(task_id, TASK_STATUS_PROCESSING, "Preprocessing completed, starting GasFlux analysis...") + + # Write modified config to a temp file + final_config_path = data_path.parent / "final_config.yaml" + try: + with open(final_config_path, 'w') as f: + yaml.safe_dump(config, f) + logger.info(f"Job {task_id}: Final config written to {final_config_path}") + except Exception as e: + logger.error(f"Job {task_id}: Failed to write final config: {str(e)}") + raise + + config_duration = time.time() - config_start + logger.info(f"Job {task_id}: Configuration processing completed in {config_duration:.3f}s") + + update_task_status(task_id, TASK_STATUS_PROCESSING, "Configuration loaded, starting GasFlux analysis...") + + # 3. GasFlux Processing + logger.info(f"Job {task_id}: Starting GasFlux analysis...") + analysis_start = time.time() + + process_main(processed_csv, final_config_path, task_id) + + analysis_duration = time.time() - analysis_start + logger.info(f"Job {task_id}: GasFlux analysis completed in {analysis_duration:.3f}s") + + update_task_status(task_id, TASK_STATUS_PROCESSING, "GasFlux analysis completed, generating reports...") + + # Collect results and generate full URLs + logger.info(f"Job {task_id}: Collecting generated files from {job_output_dir}") + results_start = time.time() + results = [] + + try: + for f in job_output_dir.rglob("*"): + if f.is_file(): + rel_path = f.relative_to(app.config['OUTPUT_FOLDER']).as_posix() + file_size = f.stat().st_size + results.append({ + "name": f.name, + "rel_path": rel_path, + "download_url": f"/download/{rel_path}", # Relative URL that client can use + "size": file_size + }) + logger.debug(f"Job {task_id}: Found output file: {f.name} ({file_size} bytes)") + + results_duration = time.time() - results_start + logger.info(f"Job {task_id}: Results collection completed in {results_duration:.3f}s - {len(results)} files generated") + + total_size = sum(r.get('size', 0) for r in results) + logger.info(f"Job {task_id}: Total output size: {total_size} bytes across {len(results)} files") + + except Exception as e: + logger.error(f"Job {task_id}: Failed to collect results: {str(e)}") + raise + + total_duration = time.time() - start_time + logger.info(f"Job {task_id}: Processing complete. Total duration: {total_duration:.3f}s, {len(results)} files generated.") + + # Record task completion time for statistics + stats_collector.record_task_completion_time(total_duration) + + update_task_status(task_id, TASK_STATUS_COMPLETED, "Processing completed successfully", results=results) + + except Exception as e: + total_duration = time.time() - start_time + logger.error(f"Job {task_id}: Processing failed after {total_duration:.3f}s - Error: {str(e)}", exc_info=True) + + # Record failed task processing time for statistics + stats_collector.record_task_completion_time(total_duration) + logger.error(f"Job {task_id}: Failed task details - Data: {data_path}, Config: {config_path}, Output: {job_output_dir}") + + # Try to capture any partial results + partial_results = [] + try: + for f in job_output_dir.rglob("*"): + if f.is_file(): + rel_path = f.relative_to(app.config['OUTPUT_FOLDER']).as_posix() + partial_results.append({ + "name": f.name, + "rel_path": rel_path, + "download_url": f"/download/{rel_path}", # Relative URL that client can use + "size": f.stat().st_size, + "note": "partial_result" + }) + except Exception as collect_error: + logger.warning(f"Job {task_id}: Failed to collect partial results: {str(collect_error)}") + + error_msg = f"Processing failed: {str(e)}" + if partial_results: + error_msg += f" (partial results available: {len(partial_results)} files)" + + update_task_status(task_id, TASK_STATUS_FAILED, error=error_msg, results=partial_results if partial_results else None) + +# Import GasFlux modules +logger.info("Importing GasFlux modules...") +import_start = time.time() + +try: + # Try absolute imports first (more reliable) + from src.gasflux.processing_pipelines import process_main + from src.gasflux.data_processor import process_file + from src.gasflux.reporting import generate_reports + import_duration = time.time() - import_start + logger.info(f"GasFlux modules imported successfully in {import_duration:.3f}s (absolute import)") +except ImportError as e1: + logger.warning(f"Absolute import failed, trying relative import: {e1}") + try: + from .processing_pipelines import process_main + from .data_processor import process_file + from .reporting import generate_reports + import_duration = time.time() - import_start + logger.info(f"GasFlux modules imported successfully in {import_duration:.3f}s (relative import)") + except ImportError as e2: + import_duration = time.time() - import_start + logger.error(f"Failed to import GasFlux modules after {import_duration:.3f}s - Absolute error: {e1}, Relative error: {e2}") + raise ImportError(f"Cannot import GasFlux modules: {e2}") + +app = Flask(__name__) +CORS(app) # Initialize CORS + +# Enhanced logging configuration after app initialization +try: + log_file_path = Path(Config.LOG_FILE) + log_file_path.parent.mkdir(parents=True, exist_ok=True) + + # Create file handler + file_handler = logging.FileHandler(log_file_path, encoding='utf-8') + file_handler.setLevel(Config.get_log_level()) + formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') + file_handler.setFormatter(formatter) + + # Add file handler to logger + logger.addHandler(file_handler) + logger.info(f"File logging initialized. Log file: {log_file_path.absolute()}") + print(f"Log file: {log_file_path.absolute()}") # Also print to console +except Exception as e: + print(f"Warning: Failed to initialize file logging: {e}") + logger.warning(f"Failed to initialize file logging: {e}") + +logger.info("Flask application initialized") + +# Request logging middleware +@app.before_request +def log_request_info(): + """Log incoming request details.""" + g.start_time = time.time() + logger.info(f"REQUEST: {request.method} {request.url} - IP: {request.remote_addr} - User-Agent: {request.headers.get('User-Agent', 'Unknown')}") + +@app.after_request +def log_response_info(response): + """Log response details.""" + duration = time.time() - g.start_time + logger.info(f"RESPONSE: {request.method} {request.url} - Status: {response.status_code} - Duration: {duration:.3f}s") + + # Record statistics + endpoint = request.url_rule.rule if request.url_rule else request.path + stats_collector.record_request(request.method, endpoint, response.status_code, duration) + + return response + +# Initialize configuration from environment variables +Config.init_base_dir() + +# Apply configuration to app (directories will be created dynamically based on config) +# ALLOWED_DATA_EXTENSIONS and ALLOWED_CONFIG_EXTENSIONS moved to shared.py + +app.config['MAX_CONTENT_LENGTH'] = Config.MAX_CONTENT_LENGTH +# Don't set UPLOAD_FOLDER and OUTPUT_FOLDER here - they will be set dynamically per request +# Set defaults to avoid KeyError if any handler reads before config is applied +app.config.setdefault('UPLOAD_FOLDER', None) +app.config.setdefault('OUTPUT_FOLDER', None) + +# Log current configuration +logger.info(f"Upload folder: {Config.UPLOAD_FOLDER}") +logger.info(f"Output folder: {Config.OUTPUT_FOLDER}") +logger.info(f"Configuration: {Config.to_dict()}") + +# Ensure directories exist at startup +def setup_directories(): + logger.info("Initializing application directories...") + start_time = time.time() + + try: + # Check if directories already exist + upload_exists = Config.UPLOAD_FOLDER.exists() + output_exists = Config.OUTPUT_FOLDER.exists() + + Config.UPLOAD_FOLDER.mkdir(parents=True, exist_ok=True) + Config.OUTPUT_FOLDER.mkdir(parents=True, exist_ok=True) + + duration = time.time() - start_time + logger.info(f"Directories initialized in {duration:.3f}s: {Config.UPLOAD_FOLDER} ({'existing' if upload_exists else 'created'}), {Config.OUTPUT_FOLDER} ({'existing' if output_exists else 'created'})") + + # Log directory permissions + upload_writable = os.access(Config.UPLOAD_FOLDER, os.W_OK) + output_writable = os.access(Config.OUTPUT_FOLDER, os.W_OK) + logger.info(f"Directory permissions - Upload writable: {upload_writable}, Output writable: {output_writable}") + + except Exception as e: + duration = time.time() - start_time + logger.error(f"Failed to create directories after {duration:.3f}s: {e}") + raise + +# setup_directories() - commented out to avoid creating directories at startup +# Directories will be created dynamically based on config when processing tasks + +# allowed_file moved to shared.py + +# Import blueprints after app initialization to avoid circular imports +from .blueprints.health import health_bp +from .blueprints.upload import upload_bp +from .blueprints.tasks import tasks_bp +from .blueprints.task_pool import task_pool_bp +from .blueprints.stats import stats_bp +from .blueprints.config import config_bp +from .blueprints.reports import reports_bp +from .blueprints.download import download_bp +from .blueprints.web import web_bp + +# Register blueprints +app.register_blueprint(health_bp) +app.register_blueprint(upload_bp) +app.register_blueprint(tasks_bp) +app.register_blueprint(task_pool_bp) +app.register_blueprint(stats_bp) +app.register_blueprint(config_bp) +app.register_blueprint(reports_bp) +app.register_blueprint(download_bp) +app.register_blueprint(web_bp) + +# Load persisted task status after app initialization +try: + from .shared import ( + load_task_status_from_file, + save_task_status_to_file, + set_task_status_file_path, + ) + + # 只有在 OUTPUT_FOLDER 有效时才启用持久化 + if hasattr(Config, 'OUTPUT_FOLDER') and Config.OUTPUT_FOLDER: + task_status_path = Config.OUTPUT_FOLDER / "task_status.json" + set_task_status_file_path(task_status_path) + logger.info(f"Task status persistence path set to: {task_status_path}") + with app.app_context(): + load_task_status_from_file() + + import atexit + def _save_on_exit(): + with app.app_context(): + save_task_status_to_file() + atexit.register(_save_on_exit) + else: + logger.info("Task status persistence will be configured after config is loaded") +except Exception as e: + print(f"⚠ Failed to setup task persistence: {e}") + +# _get_file_type and _format_response moved to shared.py + + +if __name__ == '__main__': + app.run(host='0.0.0.0', port=5000, debug=False) diff --git a/src/gasflux/blueprints/__init__.py b/src/gasflux/blueprints/__init__.py new file mode 100644 index 0000000..c60db7b --- /dev/null +++ b/src/gasflux/blueprints/__init__.py @@ -0,0 +1 @@ +# GasFlux API Blueprints \ No newline at end of file diff --git a/src/gasflux/blueprints/config.py b/src/gasflux/blueprints/config.py new file mode 100644 index 0000000..3305639 --- /dev/null +++ b/src/gasflux/blueprints/config.py @@ -0,0 +1,67 @@ +""" +Configuration Blueprint +Provides configuration information and environment variables endpoints. +""" + +import os +from flask import Blueprint + +from ..app import Config +from ..shared import _format_response, log_performance, logger + +# Create blueprint +config_bp = Blueprint('config', __name__, url_prefix='/config') + + +@config_bp.route('', methods=['GET']) +@log_performance +def get_config(): + """Get current configuration (without sensitive information).""" + logger.debug("Configuration requested") + + try: + config_info = Config.to_dict() + + # Remove potentially sensitive information + safe_config = config_info.copy() + + data = { + "configuration": safe_config, + "environment_variables": { + "supported": [ + "GASFLUX_HOST", + "GASFLUX_PORT", + "GASFLUX_DEBUG", + "GASFLUX_UPLOAD_FOLDER", + "GASFLUX_OUTPUT_FOLDER", + "GASFLUX_MAX_CONTENT_LENGTH", + "GASFLUX_LOG_LEVEL", + "GASFLUX_LOG_FILE", + "GASFLUX_CORS_ORIGINS", + "GASFLUX_TASK_CLEANUP_INTERVAL", + "GASFLUX_MAX_TASK_AGE", + "GASFLUX_THREADS", + "GASFLUX_CONNECTION_LIMIT", + "GASFLUX_CHANNEL_TIMEOUT" + ], + "current_values": { + key: os.getenv(key, "not set") if key.startswith("GASFLUX_") else "internal" + for key in [ + "GASFLUX_HOST", "GASFLUX_PORT", "GASFLUX_DEBUG", + "GASFLUX_UPLOAD_FOLDER", "GASFLUX_OUTPUT_FOLDER", + "GASFLUX_MAX_CONTENT_LENGTH", "GASFLUX_LOG_LEVEL", + "GASFLUX_LOG_FILE", "GASFLUX_CORS_ORIGINS", + "GASFLUX_TASK_CLEANUP_INTERVAL", "GASFLUX_MAX_TASK_AGE", + "GASFLUX_THREADS", "GASFLUX_CONNECTION_LIMIT", + "GASFLUX_CHANNEL_TIMEOUT" + ] + } + } + } + return _format_response(200, "配置信息获取成功", data) + + except Exception as e: + logger.error(f"Failed to retrieve configuration: {str(e)}", exc_info=True) + return _format_response(500, "获取配置信息失败", { + "error_details": str(e) + }) \ No newline at end of file diff --git a/src/gasflux/blueprints/download.py b/src/gasflux/blueprints/download.py new file mode 100644 index 0000000..47dc3f3 --- /dev/null +++ b/src/gasflux/blueprints/download.py @@ -0,0 +1,68 @@ +""" +Download Blueprint +Handles file download endpoints. +""" + +from pathlib import Path +from flask import Blueprint, send_file, current_app + +from ..shared import _format_response, log_performance, logger + +# Create blueprint +download_bp = Blueprint('download', __name__, url_prefix='/download') + + +@download_bp.route('/') +@log_performance +def download_file(filename): + """Download a processed file.""" + from flask import request + + logger.info(f"Download request for file: {filename} from IP {request.remote_addr}") + + try: + # 支持两种路径格式: + # 1. 绝对路径(以 / 开头,如 /full/path/to/file) + # 2. 相对路径(task_id/filename) + if filename.startswith('/'): + # 绝对路径 - 直接使用 + file_path = Path(filename) + else: + # 相对路径 - 相对于 OUTPUT_FOLDER + output_folder = Path(current_app.config.get('OUTPUT_FOLDER') or '') + if not output_folder: + logger.error("OUTPUT_FOLDER not configured") + return _format_response(500, "服务器配置错误") + + # 解析 task_id/filename 格式 + parts = filename.split('/', 1) + if len(parts) != 2: + logger.warning(f"Invalid relative path format: {filename}") + return _format_response(400, "无效的文件路径") + + task_id, filename_part = parts + file_path = output_folder / task_id / filename_part + + # Security check - ensure file is within output folder + file_path = file_path.resolve() + output_folder = Path(current_app.config.get('OUTPUT_FOLDER') or '').resolve() + if output_folder and not str(file_path).startswith(str(output_folder)): + logger.warning(f"Security violation: Attempted to access file outside output folder: {filename}") + return _format_response(403, "访问被拒绝") + + if not file_path.exists(): + logger.warning(f"File not found: {filename}") + return _format_response(404, "文件未找到") + + if not file_path.is_file(): + logger.warning(f"Path is not a file: {filename}") + return _format_response(400, "不是文件") + + file_size = file_path.stat().st_size + logger.info(f"Serving file: {filename} ({file_size} bytes)") + + return send_file(file_path) + + except Exception as e: + logger.error(f"Error serving file {filename}: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") \ No newline at end of file diff --git a/src/gasflux/blueprints/health.py b/src/gasflux/blueprints/health.py new file mode 100644 index 0000000..d0213aa --- /dev/null +++ b/src/gasflux/blueprints/health.py @@ -0,0 +1,94 @@ +""" +Health Check Blueprint +Provides API health monitoring and system status endpoints. +""" + +import os +import time +import logging +from flask import Blueprint, request + +from ..app import Config, stats_collector +from ..shared import task_status, TASK_STATUS_PENDING, TASK_STATUS_PROCESSING +from ..shared import _format_response, log_performance, logger + +# Create blueprint +health_bp = Blueprint('health', __name__, url_prefix='/health') + + +@health_bp.route('', methods=['GET']) +@log_performance +def health_check(): + """API Health Check""" + logger.debug("Health check requested") + + try: + # Check storage accessibility + uploads_writable = os.access(Config.UPLOAD_FOLDER, os.W_OK) + outputs_writable = os.access(Config.OUTPUT_FOLDER, os.W_OK) + + # Check active tasks + active_tasks = len([t for t in task_status.values() if t.get("status") in [TASK_STATUS_PENDING, TASK_STATUS_PROCESSING]]) + + # Get basic stats for health check + stats_summary = stats_collector.get_summary() + + health_data = { + "status": "healthy", + "version": "1.0.0", + "timestamp": time.time(), + "uptime": stats_summary['summary']['uptime_formatted'], + "storage": { + "uploads_writable": uploads_writable, + "outputs_writable": outputs_writable + }, + "tasks": { + "active_count": active_tasks, + "total_tracked": len(task_status), + "total_processed": stats_summary['tasks']['total_completed'] + stats_summary['tasks']['total_failed'], + "success_rate_percent": stats_summary['tasks']['success_rate_percent'] + }, + "performance": { + "requests_per_second": stats_summary['summary']['requests_per_second'], + "avg_response_time_ms": stats_summary['performance']['avg_response_time_ms'], + "error_rate_percent": stats_summary['summary']['error_rate_percent'] + } + } + + # Determine health status based on metrics + is_healthy = True + issues = [] + + if not uploads_writable: + issues.append("上传文件夹不可写") + is_healthy = False + if not outputs_writable: + issues.append("输出文件夹不可写") + is_healthy = False + if active_tasks > 20: # High load threshold + issues.append(f"活跃任务数量过多 ({active_tasks})") + if stats_summary['summary']['error_rate_percent'] > 10: # High error rate + issues.append(f"错误率过高 ({stats_summary['summary']['error_rate_percent']:.1f}%)") + is_healthy = False + + health_data["status"] = "healthy" if is_healthy else "degraded" + if issues: + health_data["issues"] = issues + + # Log warnings for potential issues + for issue in issues: + logger.warning(f"Health check issue: {issue}") + + status_level = logging.DEBUG if is_healthy else logging.WARNING + logger.log(status_level, f"Health check: {health_data['status']} (active tasks: {active_tasks})") + + status_code = 200 if is_healthy else 503 # 503 Service Unavailable for degraded + return _format_response(status_code, "健康检查完成" if is_healthy else "服务不可用", health_data) + + except Exception as e: + logger.error(f"Health check failed: {str(e)}", exc_info=True) + return _format_response(500, "健康检查失败", { + "status": "unhealthy", + "error": str(e), + "timestamp": time.time() + }) \ No newline at end of file diff --git a/src/gasflux/blueprints/reports.py b/src/gasflux/blueprints/reports.py new file mode 100644 index 0000000..e80201e --- /dev/null +++ b/src/gasflux/blueprints/reports.py @@ -0,0 +1,192 @@ +""" +Reports Blueprint +Provides report listing and management endpoints. +""" + +import time +from pathlib import Path +from flask import Blueprint, request, current_app + + +from ..shared import _get_file_type, _format_response, log_performance, logger, task_status +from ..app import Config + +# Create blueprint +reports_bp = Blueprint('reports', __name__, url_prefix='/reports') + + +@reports_bp.route('', methods=['GET']) +@log_performance +def list_reports(): + """List all generated reports with pagination and filtering.""" + logger.debug("Reports list requested") + + try: + # Parse query parameters + try: + page = int(request.args.get('page', 1)) + if page < 1: + return _format_response(400, "Invalid parameter: page must be >= 1") + except (ValueError, TypeError): + return _format_response(400, "Invalid parameter: page must be a valid integer") + + try: + per_page = int(request.args.get('per_page', 20)) + if per_page < 1 or per_page > 100: + return _format_response(400, "Invalid parameter: per_page must be between 1 and 100") + except (ValueError, TypeError): + return _format_response(400, "Invalid parameter: per_page must be a valid integer") + + sort_by = request.args.get('sort_by', 'created_at') + sort_order = request.args.get('sort_order', 'desc') + status_filter = request.args.get('status', None) # 'completed', 'failed', or None for all + + # Validate sort parameters + valid_sort_fields = ['created_at', 'task_id', 'file_size', 'processing_time'] + if sort_by not in valid_sort_fields: + return _format_response(400, f"Invalid parameter: sort_by must be one of {valid_sort_fields}") + if sort_order not in ['asc', 'desc']: + return _format_response(400, "Invalid parameter: sort_order must be 'asc' or 'desc'") + + # Validate status filter + valid_statuses = ['completed', 'failed', None] + if status_filter is not None and status_filter not in ['completed', 'failed']: + return _format_response(400, "Invalid parameter: status must be 'completed', 'failed', or not specified") + + # 兼容缺省:优先 app.config,其次 Config.OUTPUT_FOLDER + output_root = current_app.config.get('OUTPUT_FOLDER') or getattr(Config, 'OUTPUT_FOLDER', None) + if not output_root: + return _format_response(200, "报告列表获取成功", { + 'reports': [], + 'pagination': {'page': page, 'per_page': per_page, 'total_reports': 0, 'total_pages': 0, 'has_next': False, 'has_prev': False}, + 'filters': {'sort_by': sort_by, 'sort_order': sort_order, 'status': status_filter} + }) + + output_folder = Path(output_root) + reports = [] + + # Scan all task directories + if output_folder.exists(): + for task_dir in output_folder.iterdir(): + if not task_dir.is_dir(): + continue + task_id = task_dir.name + + # Get task information from global task_status + task_info = task_status.get(task_id, {}) + task_status_value = task_info.get('status') + + # Log task status for debugging + logger.debug(f"Task {task_id}: status from memory={task_status_value}, info={task_info}") + + # 直接扫描平铺文件 + files = [p for p in task_dir.iterdir() if p.is_file()] + if not files: + # 按需应用状态过滤 + if status_filter: + continue + reports.append({ + 'task_id': task_id, + 'report_name': "N/A", + 'status': 'failed', + 'created_at': task_dir.stat().st_mtime, + 'file_count': 0, + 'total_size': 0, + 'processing_time_seconds': None, + 'main_report': None, + 'all_files': [], + 'run_directory': f'{task_id}' + }) + continue + + # 识别主报告与统计 + total_size = sum(f.stat().st_size for f in files) + created_at = max(f.stat().st_mtime for f in files) if files else task_dir.stat().st_mtime + + def file_entry(p): + return { + 'name': p.name, + 'size': p.stat().st_size, + 'type': _get_file_type(p.name), + # 使用相对路径下载,清晰且安全 + 'download_url': f"/download/{task_id}/{p.name}" + } + + all_files = [file_entry(f) for f in files] + # 优先 CO2_report,其次任意 *_report_*.html + report_html = None + for f in files: + if f.name.endswith('_report_') and f.suffix == '.html': + report_html = file_entry(f) + break + if not report_html: + for f in files: + if f.name.endswith('.html'): + report_html = file_entry(f) + break + + # 任务状态:若有报告或关键产物则视为 completed + has_outputs = any(f.name.startswith(('config_', 'output_vars_', 'processed_data_', 'CO2_report_')) for f in files) + task_status_value = 'completed' if has_outputs else 'unknown' + if status_filter and task_status_value != status_filter: + continue + + # Create report entry + report_entry = { + 'task_id': task_id, + 'report_name': task_id, + 'status': task_status_value, + 'created_at': created_at, + 'file_count': len(files), + 'total_size': total_size, + 'processing_time_seconds': None, + 'main_report': report_html, + 'all_files': all_files, + 'run_directory': f'{task_id}' + } + + reports.append(report_entry) + + # Sort reports + reverse_order = sort_order == 'desc' + if sort_by == 'created_at': + reports.sort(key=lambda x: x['created_at'], reverse=reverse_order) + elif sort_by == 'task_id': + reports.sort(key=lambda x: x['task_id'], reverse=reverse_order) + elif sort_by == 'file_size': + reports.sort(key=lambda x: x['total_size'], reverse=reverse_order) + elif sort_by == 'processing_time': + reports.sort(key=lambda x: x['processing_time_seconds'] or 0, reverse=reverse_order) + + # Paginate results + total_reports = len(reports) + start_idx = (page - 1) * per_page + end_idx = start_idx + per_page + paginated_reports = reports[start_idx:end_idx] + + # Calculate pagination metadata + total_pages = (total_reports + per_page - 1) // per_page + + response_data = { + 'reports': paginated_reports, + 'pagination': { + 'page': page, + 'per_page': per_page, + 'total_reports': total_reports, + 'total_pages': total_pages, + 'has_next': page < total_pages, + 'has_prev': page > 1 + }, + 'filters': { + 'sort_by': sort_by, + 'sort_order': sort_order, + 'status': status_filter + } + } + + logger.info(f"Returning {len(paginated_reports)} reports (page {page}/{total_pages})") + return _format_response(200, "报告列表获取成功", response_data) + + except Exception as e: + logger.error(f"Error listing reports: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") \ No newline at end of file diff --git a/src/gasflux/blueprints/stats.py b/src/gasflux/blueprints/stats.py new file mode 100644 index 0000000..fc0d1a2 --- /dev/null +++ b/src/gasflux/blueprints/stats.py @@ -0,0 +1,93 @@ +""" +Statistics Blueprint +Provides API statistics and monitoring endpoints. +""" + +import time +from flask import Blueprint, current_app + + +from ..shared import _format_response, log_performance, logger,stats_collector, task_status + +# Create blueprint +stats_bp = Blueprint('stats', __name__, url_prefix='/stats') + + +@stats_bp.route('', methods=['GET']) +@log_performance +def get_stats(): + """Get detailed API statistics and monitoring data.""" + logger.debug("Statistics requested") + + try: + # Get detailed statistics + stats_data = stats_collector.get_summary() + + # Add current system information + try: + import psutil + memory = psutil.virtual_memory() + disk = psutil.disk_usage(str(current_app.config['OUTPUT_FOLDER'])) + + stats_data['system'] = { + 'memory_usage_percent': memory.percent, + 'memory_used_gb': round(memory.used / (1024**3), 2), + 'memory_total_gb': round(memory.total / (1024**3), 2), + 'disk_usage_percent': disk.percent, + 'disk_used_gb': round(disk.used / (1024**3), 2), + 'disk_total_gb': round(disk.total / (1024**3), 2) + } + except ImportError: + # psutil not available + stats_data['system'] = { + 'note': 'System metrics unavailable - install psutil for detailed monitoring' + } + except Exception as e: + logger.warning(f"Failed to collect system metrics: {e}") + stats_data['system'] = {'error': str(e)} + + # Add recent task information + recent_tasks = [] + current_time = time.time() + for task_id, task_info in list(task_status.items())[-20:]: # Last 20 tasks + age = current_time - task_info.get('updated_at', 0) + recent_tasks.append({ + 'task_id': task_id, + 'status': task_info.get('status'), + 'age_seconds': round(age, 1), + 'message': task_info.get('message', '')[:100] # Truncate long messages + }) + + stats_data['recent_tasks'] = recent_tasks + + return _format_response(200, "统计信息获取成功", stats_data) + + except Exception as e: + logger.error(f"Failed to retrieve statistics: {str(e)}", exc_info=True) + return _format_response(500, "获取统计信息失败", { + "error_details": str(e) + }) + + +@stats_bp.route('/reset', methods=['POST']) +@log_performance +def reset_stats(): + """Reset API statistics (admin function).""" + logger.warning("Statistics reset requested") + + try: + # Reset statistics + stats_collector.reset_stats() + + # Log the reset + logger.info("API statistics have been reset") + + return _format_response(200, "统计信息重置成功", { + "timestamp": time.time() + }) + + except Exception as e: + logger.error(f"Failed to reset statistics: {str(e)}", exc_info=True) + return _format_response(500, "重置统计信息失败", { + "error_details": str(e) + }) \ No newline at end of file diff --git a/src/gasflux/blueprints/task_pool.py b/src/gasflux/blueprints/task_pool.py new file mode 100644 index 0000000..7c39e4b --- /dev/null +++ b/src/gasflux/blueprints/task_pool.py @@ -0,0 +1,228 @@ +""" +Task Pool Blueprint +Handles task pool management endpoints: listing tasks with pagination, pool statistics. +""" + +from flask import Blueprint, request +from pathlib import Path + +from ..shared import ( + get_task_list, + get_task_pool_stats, + _format_response, + log_performance, + logger, + task_status, + TASK_STATUS_PENDING, + TASK_STATUS_PROCESSING, + TASK_STATUS_COMPLETED, + TASK_STATUS_FAILED +) + +# Create blueprint +task_pool_bp = Blueprint('task_pool', __name__, url_prefix='/tasks') + + +def _build_simple_downloads_from_results(results: list[dict]) -> dict: + """ + Build direct download shortcuts for common files, based on task results. + This is intentionally minimal and frontend-friendly. + """ + downloads: dict = {} + + def set_once(key: str, url: str): + if key not in downloads and url: + downloads[key] = url + + for item in results or []: + if not isinstance(item, dict): + continue + rel_path = item.get('rel_path') + if not rel_path: + continue + + name_l = (item.get('name') or '').lower() + url = f"/download/{rel_path}" + + if name_l.endswith('.xlsx'): + set_once('data_xlsx', url) + elif name_l.endswith('.xls'): + set_once('data_xls', url) + elif name_l.endswith('ch4_report.html'): + set_once('report_ch4', url) + elif name_l.endswith('co2_report.html'): + set_once('report_co2', url) + elif name_l.endswith(('.yaml', '.yml')): + set_once('config', url) + elif name_l.endswith('.json') and 'output_vars' in name_l: + set_once('metadata', url) + elif name_l.endswith('.html'): + # fallback: any html report + set_once('report_html', url) + + return downloads + + +def _lean_task_summary(task_summary: dict) -> dict: + """Return a minimal task representation for frontend consumption.""" + task_id = task_summary.get('task_id') + status = task_summary.get('status') + + lean = { + 'task_id': task_id, + 'status': status, + 'message': task_summary.get('message'), + 'updated_at': task_summary.get('updated_at'), + } + + if status == TASK_STATUS_COMPLETED and task_id: + full_task_info = task_status.get(task_id, {}) + results = full_task_info.get('results', []) or [] + downloads = _build_simple_downloads_from_results(results) + if downloads: + lean['downloads'] = downloads + + return lean + + +@task_pool_bp.route('', methods=['GET']) +@log_performance +def list_tasks(): + """Get paginated list of tasks with optional filtering.""" + logger.debug(f"Task list request from IP {request.remote_addr}") + + try: + # Parse query parameters + status_filter = request.args.get('status') + if status_filter: + # Support comma-separated status values + status_filter = status_filter.split(',') + + page = int(request.args.get('page', 1)) + page_size = int(request.args.get('page_size', 20)) + sort_by = request.args.get('sort_by', 'updated_at') + sort_order = request.args.get('sort_order', 'desc') + + # Validate parameters + if page < 1: + return _format_response(400, "页码必须大于0") + + if page_size < 1 or page_size > 100: + return _format_response(400, "每页数量必须在1-100之间") + + valid_sort_fields = ['created_at', 'updated_at', 'status'] + if sort_by not in valid_sort_fields: + return _format_response(400, f"排序字段必须是以下之一: {', '.join(valid_sort_fields)}") + + if sort_order.lower() not in ['asc', 'desc']: + return _format_response(400, "排序顺序必须是 'asc' 或 'desc'") + + # Get task list + result = get_task_list( + status_filter=status_filter, + page=page, + page_size=page_size, + sort_by=sort_by, + sort_order=sort_order, + cleanup=False + ) + + # Slim response: only task status + downloads (completed only) + result['tasks'] = [_lean_task_summary(t) for t in result.get('tasks', [])] + + logger.debug(f"Returning {len(result['tasks'])} tasks (page {page} of {result['total_pages']})") + + return _format_response(200, "任务列表查询成功", result) + + except ValueError as e: + logger.warning(f"Invalid parameter in task list request: {str(e)}") + return _format_response(400, "参数格式错误") + except Exception as e: + logger.error(f"Error listing tasks: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") + + +@task_pool_bp.route('/stats', methods=['GET']) +@log_performance +def get_pool_stats(): + """Get task pool statistics.""" + logger.debug(f"Task pool stats request from IP {request.remote_addr}") + + try: + stats = get_task_pool_stats() + + logger.debug(f"Pool stats: {stats['total_tasks']} total tasks, " + f"{stats['active_tasks']} active, {stats['queued_tasks']} queued") + + return _format_response(200, "任务池统计信息查询成功", stats) + + except Exception as e: + logger.error(f"Error getting pool stats: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") + + +@task_pool_bp.route('/active', methods=['GET']) +@log_performance +def get_active_tasks(): + """Get list of currently active (processing) tasks.""" + logger.debug(f"Active tasks request from IP {request.remote_addr}") + + try: + # Get all processing tasks, no pagination needed for active tasks + result = get_task_list( + status_filter=TASK_STATUS_PROCESSING, + page=1, + page_size=1000, # Large page size to get all active tasks + sort_by='updated_at', + sort_order='asc', # Oldest first + cleanup=False + ) + + active_tasks = result['tasks'] + + active_tasks = [_lean_task_summary(t) for t in active_tasks] + + logger.debug(f"Returning {len(active_tasks)} active tasks") + + return _format_response(200, "活跃任务查询成功", { + 'active_tasks': active_tasks, + 'count': len(active_tasks) + }) + + except Exception as e: + logger.error(f"Error getting active tasks: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") + + +@task_pool_bp.route('/queue', methods=['GET']) +@log_performance +def get_queued_tasks(): + """Get list of queued (pending) tasks.""" + logger.debug(f"Queued tasks request from IP {request.remote_addr}") + + try: + # Get all pending tasks, sorted by creation time + result = get_task_list( + status_filter=TASK_STATUS_PENDING, + page=1, + page_size=1000, # Large page size to get all queued tasks + sort_by='created_at', + sort_order='asc', # Oldest first (FIFO) + cleanup=False + ) + + queued_tasks = result['tasks'] + + queued_tasks = [_lean_task_summary(t) for t in queued_tasks] + + logger.debug(f"Returning {len(queued_tasks)} queued tasks") + + return _format_response(200, "队列任务查询成功", { + 'queued_tasks': queued_tasks, + 'count': len(queued_tasks), + 'queue_position_info': "任务按创建时间排序,较早的任务优先处理" + }) + + except Exception as e: + logger.error(f"Error getting queued tasks: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") \ No newline at end of file diff --git a/src/gasflux/blueprints/tasks.py b/src/gasflux/blueprints/tasks.py new file mode 100644 index 0000000..946a6be --- /dev/null +++ b/src/gasflux/blueprints/tasks.py @@ -0,0 +1,220 @@ +""" +Tasks Blueprint +Handles task management endpoints: status query, update, and deletion. +""" + +from flask import Blueprint, request + +from ..shared import ( + get_task_status, + update_task_status, + cleanup_old_tasks, + _format_response, + log_performance, + logger, + task_status, + _build_simple_downloads_from_results, + TASK_STATUS_COMPLETED, + TASK_STATUS_FAILED, + TASK_STATUS_PROCESSING, + TASK_STATUS_PENDING, +) + +# Create blueprint +tasks_bp = Blueprint('tasks', __name__, url_prefix='/task') + + +@tasks_bp.route('/', methods=['GET']) +@log_performance +def get_task_status_endpoint(task_id): + """Get the status of a processing task.""" + logger.debug(f"Status request for task {task_id}") + + try: + # Note: cleanup_old_tasks() is disabled for individual task queries + # to preserve historical task data for task pool management + # cleanup_old_tasks() + + task_info = get_task_status(task_id) + if task_info.get("status") == "not_found": + logger.warning(f"Status request for non-existent task {task_id} from IP {request.remote_addr}") + return _format_response(404, "任务未找到") + + data = { + "task_id": task_id, + "status": task_info["status"], + "message": task_info.get("message", ""), + "updated_at": task_info.get("updated_at", 0) + } + + if task_info["status"] == TASK_STATUS_COMPLETED: + results = task_info.get("results", []) + data["results"] = results + # Add direct download shortcuts for frontend (if available or can be derived) + downloads = task_info.get("downloads") or _build_simple_downloads_from_results(results) + if downloads: + data["downloads"] = downloads + logger.debug(f"Task {task_id}: Returning {len(results)} completed results") + return _format_response(200, "任务查询成功", data) + elif task_info["status"] == TASK_STATUS_FAILED: + error_msg = task_info.get("error", "未知错误") + data["error"] = error_msg + logger.warning(f"Task {task_id}: Returning failure status - {error_msg}") + # Return 200 for failed tasks since this is expected behavior, not an HTTP error + return _format_response(200, "任务处理失败", data) + else: + # Processing or pending status + return _format_response(200, "任务查询成功", data) + + except Exception as e: + logger.error(f"Error retrieving status for task {task_id}: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") + + +@tasks_bp.route('/', methods=['PUT']) +@log_performance +def update_task(task_id): + """Update task status and information.""" + logger.info(f"Task update request for {task_id} from IP {request.remote_addr}") + + try: + # Validate task exists + task_info = get_task_status(task_id) + if task_info.get("status") == "not_found": + logger.warning(f"Update request for non-existent task {task_id}") + return _format_response(404, "任务未找到") + + # Parse request data + data = request.get_json() + if not data: + return _format_response(400, "请求体必须是 JSON 格式") + + # Validate allowed fields + allowed_fields = ['status', 'message', 'priority'] + valid_statuses = [TASK_STATUS_PENDING, TASK_STATUS_PROCESSING, + TASK_STATUS_COMPLETED, TASK_STATUS_FAILED] + + updates = {} + for field in allowed_fields: + if field in data: + if field == 'status' and data[field] not in valid_statuses: + return _format_response(400, f"无效状态。必须是以下之一: {', '.join(valid_statuses)}") + updates[field] = data[field] + + if not updates: + return _format_response(400, "没有有效的字段可更新") + + # Update task status + current_status = task_info.get('status') + new_status = updates.get('status', current_status) + message = updates.get('message', task_info.get('message')) + + # Special handling for status changes + if 'status' in updates: + if new_status == TASK_STATUS_COMPLETED: + # For completed tasks, we might want to add fake results if none exist + if not task_info.get('results'): + logger.warning(f"Marking task {task_id} as completed but no results found") + elif new_status == TASK_STATUS_FAILED: + # For failed tasks, error message is required + error_msg = updates.get('message', 'Task manually marked as failed') + update_task_status(task_id, new_status, error_msg) + else: + update_task_status(task_id, new_status, message) + else: + # Only update message + update_task_status(task_id, current_status, message) + + # Update priority if provided + if 'priority' in updates: + task_status[task_id]['priority'] = updates['priority'] + + # Get updated task info + updated_task = get_task_status(task_id) + + data = { + "task_id": task_id, + "status": "updated", + "task_info": { + "status": updated_task.get("status"), + "message": updated_task.get("message"), + "updated_at": updated_task.get("updated_at", 0), + "priority": updated_task.get("priority", "normal") + } + } + + logger.info(f"Task {task_id} updated: {updates}") + return _format_response(200, "任务更新成功", data) + + except Exception as e: + logger.error(f"Error updating task {task_id}: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") + + +@tasks_bp.route('/', methods=['DELETE']) +@log_performance +def delete_task(task_id): + """Delete a task and its associated files.""" + logger.info(f"Task deletion request for {task_id} from IP {request.remote_addr}") + + try: + # Validate task exists + task_info = get_task_status(task_id) + if task_info.get("status") == "not_found": + logger.warning(f"Delete request for non-existent task {task_id}") + return _format_response(404, "任务未找到") + + # Check if task is currently processing + if task_info.get("status") in [TASK_STATUS_PROCESSING, TASK_STATUS_PENDING]: + return _format_response(409, "无法删除当前正在处理或等待处理的任务", { + "task_status": task_info.get("status") + }) + + # Delete associated files + from pathlib import Path + from flask import current_app + import shutil + + output_folder = Path(current_app.config['OUTPUT_FOLDER']) + task_folder = output_folder / task_id + + files_deleted = 0 + total_size_deleted = 0 + + if task_folder.exists(): + try: + # Calculate total size before deletion + for file_path in task_folder.rglob('*'): + if file_path.is_file(): + total_size_deleted += file_path.stat().st_size + + # Delete the entire task folder + shutil.rmtree(task_folder) + files_deleted = 1 # Count as one folder deleted + logger.info(f"Deleted task folder: {task_folder}") + + except Exception as e: + logger.error(f"Error deleting task folder {task_folder}: {str(e)}") + return _format_response(500, f"删除任务文件失败: {str(e)}") + + # Remove from task status tracking + if task_id in task_status: + del task_status[task_id] + logger.info(f"Removed task {task_id} from status tracking") + + data = { + "task_id": task_id, + "status": "deleted", + "details": { + "folders_deleted": files_deleted, + "total_size_deleted": total_size_deleted, + "task_status": task_info.get("status") + } + } + + logger.info(f"Task {task_id} deleted successfully") + return _format_response(200, "任务及相关文件删除成功", data) + + except Exception as e: + logger.error(f"Error deleting task {task_id}: {str(e)}", exc_info=True) + return _format_response(500, "内部服务器错误") \ No newline at end of file diff --git a/src/gasflux/blueprints/upload.py b/src/gasflux/blueprints/upload.py new file mode 100644 index 0000000..5749c8b --- /dev/null +++ b/src/gasflux/blueprints/upload.py @@ -0,0 +1,134 @@ +""" +Upload Blueprint +Handles file upload and processing initiation endpoints. +""" + +import uuid +import threading +from pathlib import Path +from flask import Blueprint, request, current_app +from werkzeug.utils import secure_filename +import yaml +from io import BytesIO + + +from ..app import process_data_async +from ..shared import _format_response, log_performance, logger, ALLOWED_DATA_EXTENSIONS, ALLOWED_CONFIG_EXTENSIONS, allowed_file,update_task_status, TASK_STATUS_PENDING, TASK_STATUS_FAILED + +# Create blueprint +upload_bp = Blueprint('upload', __name__, url_prefix='/upload') + + +@upload_bp.route('', methods=['POST']) +@log_performance +def upload_file(): + logger.info("Received upload request") + logger.info(f"Request content length: {request.content_length} bytes") + + # Check if data file is present + if 'file' not in request.files: + logger.warning("Upload failed: No data file part in request") + return _format_response(400, "未找到数据文件部分") + + data_file = request.files['file'] + config_file = request.files.get('config') + + # Log file details + logger.info(f"Data file: {data_file.filename} (size: {getattr(data_file, 'content_length', 'unknown')} bytes)") + if config_file: + logger.info(f"Config file: {config_file.filename} (size: {getattr(config_file, 'content_length', 'unknown')} bytes)") + else: + logger.info("No custom config file provided, will use default") + + if data_file.filename == '': + logger.warning("Upload failed: No data file selected (empty filename)") + return _format_response(400, "未选择数据文件") + + if not allowed_file(data_file.filename, ALLOWED_DATA_EXTENSIONS): + logger.warning(f"Upload failed: Invalid data file type {data_file.filename} - allowed: {ALLOWED_DATA_EXTENSIONS}") + return _format_response(400, "无效的数据文件类型。只允许 .xlsx 和 .xls 格式。") + + # Generate unique job ID + job_id = str(uuid.uuid4()) + logger.info(f"Generated job ID: {job_id}") + + # 1) Parse config content (parse in memory without saving first) + if config_file and config_file.filename != '': + if not allowed_file(config_file.filename, ALLOWED_CONFIG_EXTENSIONS): + return _format_response(400, "无效的配置文件类型。只允许 .yaml 和 .yml 格式。") + config_file.stream.seek(0) + config_text = config_file.read().decode('utf-8', errors='ignore') + try: + active_config = yaml.safe_load(config_text) + except Exception: + return _format_response(400, "配置文件解析失败") + # Reset stream for saving + config_file.stream = BytesIO(config_text.encode('utf-8')) + else: + default_config_path = Path(__file__).parent.parent / "gasflux_config.yaml" + with open(default_config_path, 'r', encoding='utf-8') as f: + active_config = yaml.safe_load(f) + + # 2) Create job directories based on config['output_dir'] + output_base = Path(active_config['output_dir']).expanduser() + job_upload_dir = output_base / "uploads" / job_id + job_output_dir = output_base / "outputs" / job_id + job_upload_dir.mkdir(parents=True, exist_ok=True) + job_output_dir.mkdir(parents=True, exist_ok=True) + logger.info(f"Job {job_id}: Created directories - Upload: {job_upload_dir}, Output: {job_output_dir}") + + # 3) Save data file to job_upload_dir + data_filename = secure_filename(data_file.filename) + data_path = job_upload_dir / data_filename + try: + data_file.seek(0) + data_file.save(str(data_path)) + logger.info(f"Job {job_id}: Data file saved successfully - Path: {data_path}") + except Exception as e: + logger.error(f"Job {job_id}: Failed to save data file {data_filename}: {str(e)}") + return _format_response(500, "保存数据文件失败") + + # 4) Save config file to job_upload_dir + if config_file and config_file.filename != '': + config_filename = secure_filename(config_file.filename) + config_path = job_upload_dir / config_filename + try: + config_file.seek(0) + config_file.save(str(config_path)) + active_config_path = config_path + logger.info(f"Job {job_id}: Custom config saved successfully - Path: {config_path}") + except Exception as e: + logger.error(f"Job {job_id}: Failed to save config file {config_filename}: {str(e)}") + return _format_response(500, "保存配置文件失败") + else: + # Copy default config for record keeping + config_path = job_upload_dir / "config.yaml" + with open(config_path, 'w', encoding='utf-8') as f: + yaml.safe_dump(active_config, f, allow_unicode=True) + active_config_path = config_path + logger.info(f"Job {job_id}: Default config saved for record - Path: {config_path}") + + # Initialize task status + update_task_status(job_id, TASK_STATUS_PENDING, "Task queued for processing") + logger.info(f"Job {job_id}: Task status initialized as PENDING") + + # Start background processing + try: + thread = threading.Thread( + target=process_data_async, + args=(job_id, data_path, active_config_path, job_output_dir) + ) + thread.daemon = True + thread.start() + logger.info(f"Job {job_id}: Background processing thread started successfully") + except Exception as e: + logger.error(f"Job {job_id}: Failed to start background processing thread: {str(e)}") + update_task_status(job_id, TASK_STATUS_FAILED, error=str(e)) + return _format_response(500, "启动处理失败") + + logger.info(f"Job {job_id}: Upload process completed successfully, returning job ID to client") + return _format_response(202, "任务已接受并加入处理队列", { + "status": "accepted", + "job_id": job_id, + "task_status_url": f"/task/{job_id}" + }) \ No newline at end of file diff --git a/src/gasflux/blueprints/web.py b/src/gasflux/blueprints/web.py new file mode 100644 index 0000000..18dcc6d --- /dev/null +++ b/src/gasflux/blueprints/web.py @@ -0,0 +1,233 @@ +""" +Web Blueprint +Provides web interface for the GasFlux API. +""" + +import time +from pathlib import Path +from flask import Blueprint, render_template_string, current_app + +from ..shared import log_performance, logger +from ..app import Config + +# Create blueprint +web_bp = Blueprint('web', __name__) + + +@web_bp.route('/') +@log_performance +def index(): + logger.debug("Index page requested") + + # 递归查找所有生成的 HTML 报告 + start_time = time.time() + all_reports = [] + # 优先用 app.config 中的目录,其次回退到 Config.OUTPUT_FOLDER;都不存在则不列出文件 + output_root = current_app.config.get('OUTPUT_FOLDER') or getattr(Config, 'OUTPUT_FOLDER', None) + if not output_root: + output_path = None + else: + output_path = Path(output_root) + + if output_path and output_path.exists(): + try: + for file in output_path.rglob("*.html"): + # 获取相对于 OUTPUT_FOLDER 的相对路径,用于下载链接 + rel_path = file.relative_to(output_path).as_posix() + all_reports.append(rel_path) + + scan_duration = time.time() - start_time + logger.debug(f"Report scan completed in {scan_duration:.3f}s - found {len(all_reports)} HTML reports") + + except Exception as e: + logger.error(f"Error scanning for reports: {str(e)}") + all_reports = [] + else: + logger.debug("No output directory configured yet, skipping report scan") + all_reports = [] + + return render_template_string(''' + + + + GasFlux Web API + + + +

+ + + ''', reports=all_reports) \ No newline at end of file diff --git a/src/gasflux/data_processor.py b/src/gasflux/data_processor.py index 15e8c63..528b52f 100644 --- a/src/gasflux/data_processor.py +++ b/src/gasflux/data_processor.py @@ -29,13 +29,14 @@ from datetime import datetime import sys import os from collections import Counter +import yaml try: from tqdm import tqdm HAS_TQDM = True except ImportError: HAS_TQDM = False - print("⚠️ 未安装tqdm库,将不显示进度条。如需进度条,请运行: pip install tqdm") + print("WARNING: 未安装tqdm库,将不显示进度条。如需进度条,请运行: pip install tqdm") def create_height_bins(heights, bin_size=2.0): @@ -82,7 +83,7 @@ def create_height_bins(heights, bin_size=2.0): # 导入qiya模块 try: from .qiya import get_pressure_at_location - print("✅ 成功导入qiya模块") + except ImportError as e: print(f"❌ 导入qiya模块失败: {e}") print("请确保GasFlux包结构完整") @@ -104,13 +105,13 @@ def load_excel_data(file_path): # 读取Excel文件 df = pd.read_excel(file_path) - print(f"✅ 成功读取数据:{len(df)} 行,{len(df.columns)} 列") + print(f"SUCCESS: Successfully loaded data: {len(df)} rows, {len(df.columns)} columns") print(f"列名:{list(df.columns)}") return df except Exception as e: - print(f"❌ 读取文件失败: {e}") + print(f"ERROR: Failed to read file: {e}") sys.exit(1) @@ -132,11 +133,11 @@ def remove_columns(df, columns_to_remove): missing_columns = [col for col in columns_to_remove if col not in df.columns] if missing_columns: - print(f"⚠️ 以下列不存在(跳过): {missing_columns}") + print(f"以下列不存在(跳过): {missing_columns}") if existing_columns: df = df.drop(columns=existing_columns) - print(f"✅ 已删除 {len(existing_columns)} 列") + print(f"已删除 {len(existing_columns)} 列") return df @@ -158,7 +159,7 @@ def extract_hour_from_filename(filename): if match: return match.group(1) else: - print(f"⚠️ 无法从文件名 '{filename}' 中提取小时信息,使用默认值 '00'") + print(f"无法从文件名 '{filename}' 中提取小时信息,使用默认值 '00'") return "00" @@ -181,7 +182,7 @@ def fix_time_column(df, filename): # 检查时间列是否存在 if '时间' not in df.columns: - print("❌ 未找到 '时间' 列") + print("未找到 '时间' 列") return df # 修正时间格式 @@ -231,18 +232,18 @@ def convert_coordinates(df): """ print("转换经纬度坐标...") - if '经度' in df.columns: - original_lon = df['经度'].head(3).tolist() - df['经度'] = df['经度'] / 1e7 - converted_lon = df['经度'].head(3).tolist() + if 'stGPSPositionX' in df.columns: + original_lon = df['stGPSPositionX'].head(3).tolist() + df['stGPSPositionX'] = df['stGPSPositionX'] / 1e7 + converted_lon = df['stGPSPositionX'].head(3).tolist() print("经度转换示例:") for orig, conv in zip(original_lon, converted_lon): print(".6f") - if '纬度' in df.columns: - original_lat = df['纬度'].head(3).tolist() - df['纬度'] = df['纬度'] / 1e7 - converted_lat = df['纬度'].head(3).tolist() + if 'stGPSPositionY' in df.columns: + original_lat = df['stGPSPositionY'].head(3).tolist() + df['stGPSPositionY'] = df['stGPSPositionY'] / 1e7 + converted_lat = df['stGPSPositionY'].head(3).tolist() print("纬度转换示例:") for orig, conv in zip(original_lat, converted_lat): print(".6f") @@ -265,42 +266,54 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s """ print("计算气压数据...") - # 检查必要列是否存在 - required_cols = ['日期', '时间', '经度', '纬度', '融合高程'] + # 检查必要列是否存在(支持原始列名和新列名) + # 原始数据中的列名 + original_cols = ['qStrDate', 'qStrTime', 'stGPSPositionX', 'stGPSPositionY', 'fAltitudeFused'] + # 坐标转换后的列名(经纬度已被除以1e7) + converted_cols = ['qStrDate', 'qStrTime', 'stGPSPositionX', 'stGPSPositionY', 'fAltitudeFused'] + + # 优先使用转换后的列名(如果存在),否则使用原始列名 + date_col = 'qStrDate' if 'qStrDate' in df.columns else '日期' + time_col = 'qStrTime' if 'qStrTime' in df.columns else '时间' + lon_col = 'stGPSPositionX' if 'stGPSPositionX' in df.columns else '经度' + lat_col = 'stGPSPositionY' if 'stGPSPositionY' in df.columns else '纬度' + height_col = 'fAltitudeFused' if 'fAltitudeFused' in df.columns else '融合高程' + + required_cols = [date_col, time_col, lon_col, lat_col, height_col] missing_cols = [col for col in required_cols if col not in df.columns] if missing_cols: - print(f"❌ 缺少必要列: {missing_cols}") + print(f"缺少必要列: {missing_cols}") return df # 检查高度变化范围 - height_min = df['融合高程'].min() - height_max = df['融合高程'].max() + height_min = df[height_col].min() + height_max = df[height_col].max() height_range = height_max - height_min - print(f"🏔️ 高度范围: {height_min:.1f} - {height_max:.1f} 米 (变化: {height_range:.1f} 米)") + print(f"高度范围: {height_min:.1f} - {height_max:.1f} 米 (变化: {height_range:.1f} 米)") # 创建高度分档 - height_bins = create_height_bins(df['融合高程'], height_bin_size) - print(f"📏 高度分档: {len(height_bins)} 个档位 (间隔: {height_bin_size:.1f} 米)") + height_bins = create_height_bins(df[height_col], height_bin_size) + print(f"高度分档: {len(height_bins)} 个档位 (间隔: {height_bin_size:.1f} 米)") for i, (bin_min, bin_max, bin_center, count) in enumerate(height_bins): print(f" 档位{i+1}: {bin_min:.1f}-{bin_max:.1f}m (中心: {bin_center:.1f}m, 数据: {count}行)") # 决定计算策略 if height_range <= height_tolerance: # 高度变化小,只计算一次气压 - print("🎯 高度变化小,将使用平均高度计算一次气压") + print("高度变化小,将使用平均高度计算一次气压") use_single_calculation = True - mean_height = df['融合高程'].mean() - print(f"📍 使用平均高度: {mean_height:.1f} 米") + mean_height = df[height_col].mean() + print(f"使用平均高度: {mean_height:.1f} 米") elif len(height_bins) == 1: # 只有一个高度档位,使用档位中心高度 - print("📦 只有一个高度档位,使用档位中心高度") + print("只有一个高度档位,使用档位中心高度") use_single_calculation = True mean_height = height_bins[0][2] # bin_center - print(f"📍 使用档位中心高度: {mean_height:.1f} 米") + print(f"使用档位中心高度: {mean_height:.1f} 米") else: # 高度变化大,使用分档计算 - print("🏗️ 使用高度分档策略,减少API调用") + print("使用高度分档策略,减少API调用") use_single_calculation = False # 确定要处理的行数 @@ -309,10 +322,10 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s sample_df = df.copy() actual_samples = len(df) if not use_single_calculation: - print(f"📊 将计算所有 {len(df)} 行的气压数据") + print(f"将计算所有 {len(df)} 行的气压数据") else: # 限制采样数量 - print(f"⚠️ 数据量较大 ({len(df)} 行),只对前 {max_samples} 行计算气压") + print(f"数据量较大 ({len(df)} 行),只对前 {max_samples} 行计算气压") sample_df = df.head(max_samples).copy() actual_samples = max_samples @@ -325,11 +338,11 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s first_row = sample_df.iloc[0] # 转换日期格式 - 只提取日期部分,移除任何时间信息 - date_str = str(first_row['日期']) + date_str = str(first_row[date_col]) if ' ' in date_str: - date_str = date_str.split(' ')[0] + date_str = date_str.split(' ')[0] # 处理 "2026-01-15 00:00:00" 格式 elif 'T' in date_str: - date_str = date_str.split('T')[0] + date_str = date_str.split('T')[0] # 处理ISO格式 if '/' in date_str: date_str = date_str.replace('/', '-') @@ -343,8 +356,15 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s # 使用数据的代表性时间(整点小时,众数) time_strings = [] - for time_val in sample_df['时间']: + for time_val in sample_df[time_col]: time_str = str(time_val).strip() + + # 处理时间字符串,提取正确的部分 + # 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个 + if ' ' in time_str: + time_parts = time_str.split() + time_str = time_parts[-1] # 取最后一个有效的时间部分 + if ':' in time_str: # 确保是有效的 HH:MM 格式,然后取整点小时 parts = time_str.split(':') @@ -368,8 +388,8 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s print("正在计算平均气压...") pressure = get_pressure_at_location( - lat=sample_df['纬度'].mean(), - lon=sample_df['经度'].mean(), + lat=sample_df[lat_col].mean(), + lon=sample_df[lon_col].mean(), altitude=mean_height, date=formatted_date, time=formatted_time @@ -377,18 +397,18 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s if pressure is not None: pressures = [pressure] * len(sample_df) - print("✅ 平均气压计算成功,将应用到所有行") + print("平均气压计算成功,将应用到所有行") else: - print("❌ 平均气压计算失败") + print("平均气压计算失败") pressures = [None] * len(sample_df) except Exception as e: - print(f"❌ 平均气压计算失败: {e}") + print(f"平均气压计算失败: {e}") pressures = [None] * len(sample_df) else: # 使用高度分档策略 - print("🏗️ 开始分档计算气压...") + print("开始分档计算气压...") # 为每个高度档位计算气压 bin_pressures = {} # bin_center -> pressure @@ -402,19 +422,19 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s try: # 使用第一行数据作为代表来获取日期和时间 # 找到这个档位中的一行数据 - bin_rows = sample_df[(sample_df['融合高程'] >= bin_min) & - (sample_df['融合高程'] <= bin_max)] + bin_rows = sample_df[(sample_df[height_col] >= bin_min) & + (sample_df[height_col] <= bin_max)] if len(bin_rows) == 0: continue first_row = bin_rows.iloc[0] # 转换日期格式 - 只提取日期部分,移除任何时间信息 - date_str = str(first_row['日期']) + date_str = str(first_row[date_col]) if ' ' in date_str: - date_str = date_str.split(' ')[0] + date_str = date_str.split(' ')[0] # 处理 "2026-01-15 00:00:00" 格式 elif 'T' in date_str: - date_str = date_str.split('T')[0] + date_str = date_str.split('T')[0] # 处理ISO格式 if '/' in date_str: date_str = date_str.replace('/', '-') @@ -424,14 +444,21 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s year, month, day = date_parts formatted_date = f"{year}-{month.zfill(2)}-{day.zfill(2)}" else: - print(f"⚠️ 档位高度 {bin_center:.1f}m 日期格式异常: {date_str}") + print(f"档位高度 {bin_center:.1f}m 日期格式异常: {date_str}") bin_pressures[bin_center] = None continue # 使用该档位数据的代表性时间(整点小时) time_strings = [] - for time_val in bin_rows['时间']: + for time_val in bin_rows[time_col]: time_str = str(time_val).strip() + + # 处理时间字符串,提取正确的部分 + # 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个 + if ' ' in time_str: + time_parts = time_str.split() + time_str = time_parts[-1] # 取最后一个有效的时间部分 + if ':' in time_str: # 确保是有效的 HH:MM 格式,然后取整点小时 parts = time_str.split(':') @@ -456,8 +483,8 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s print(" 无有效时间数据,使用默认中午12:00") # 计算这个档位的气压(使用平均位置和档位中心高度) - avg_lat = bin_rows['纬度'].mean() - avg_lon = bin_rows['经度'].mean() + avg_lat = bin_rows[lat_col].mean() + avg_lon = bin_rows[lon_col].mean() pressure = get_pressure_at_location( lat=avg_lat, @@ -475,14 +502,14 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s iterator.set_description(f"计算档位 (成功: {success_count}/{len(bin_pressures)})") except Exception as e: - print(f"❌ 计算高度档位 {bin_center:.1f}m 气压失败: {e}") + print(f"计算高度档位 {bin_center:.1f}m 气压失败: {e}") bin_pressures[bin_center] = None # 为每一行分配对应档位的气压 pressures = [] for idx, row in sample_df.iterrows(): # 找到这个高度对应的档位 - height = row['融合高程'] + height = row[height_col] assigned_pressure = None for bin_min, bin_max, bin_center, count in height_bins: @@ -492,7 +519,7 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s pressures.append(assigned_pressure) - print(f"✅ 完成分档气压计算,共 {len(bin_pressures)} 个档位,{len([p for p in bin_pressures.values() if p is not None])} 个成功") + print(f"完成分档气压计算,共 {len(bin_pressures)} 个档位,{len([p for p in bin_pressures.values() if p is not None])} 个成功") # 添加气压列 df['pressure'] = None # 初始化 @@ -513,7 +540,7 @@ def calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_s avg_pressure = sum(valid_pressures) / len(valid_pressures) print(f"成功计算 {len(valid_pressures)}/{actual_samples} 个气压值,平均值: {avg_pressure:.1f} hPa") else: - print("⚠️ 未能计算出任何气压值") + print("未能计算出任何气压值") return df @@ -529,13 +556,13 @@ def adjust_altitude(df): """ print("调整融合高程...") - if '融合高程' in df.columns: - min_altitude = df['融合高程'].min() + if 'fAltitudeFused' in df.columns: + min_altitude = df['fAltitudeFused'].min() print(".2f") - original_alt = df['融合高程'].head(3).tolist() - df['融合高程'] = df['融合高程'] - min_altitude - adjusted_alt = df['融合高程'].head(3).tolist() + original_alt = df['fAltitudeFused'].head(3).tolist() + df['fAltitudeFused'] = df['fAltitudeFused'] - min_altitude + adjusted_alt = df['fAltitudeFused'].head(3).tolist() print("高度调整示例:") for orig, adj in zip(original_alt, adjusted_alt): @@ -546,7 +573,7 @@ def adjust_altitude(df): def merge_timestamp(df): """ - 融合日期和时间列为时间戳 + 融合日期和时间列为时间戳(修正时间格式) Args: df: 输入DataFrame @@ -554,57 +581,48 @@ def merge_timestamp(df): Returns: pd.DataFrame: 融合后的DataFrame """ - print("融合日期和时间...") + print("融合日期和时间(修正时间格式)...") - if '日期' in df.columns and '时间' in df.columns: + if 'qStrDate' in df.columns and 'qStrTime' in df.columns: timestamps = [] for idx, row in df.iterrows(): try: - date_str = str(row['日期']) - time_str = str(row['时间']) + date_str = str(row['qStrDate']).strip() + time_str = str(row['qStrTime']).strip() - # 清理日期字符串 - 移除任何时间部分 - date_str = date_str.strip() + # 处理日期字符串,提取纯日期部分 + # 如果日期字符串包含时间部分(如 "2026-01-15 00:00:00"),取日期部分 if ' ' in date_str: - date_str = date_str.split(' ')[0] # 只取日期部分 - if 'T' in date_str: - date_str = date_str.split('T')[0] # 处理ISO格式 + date_parts = date_str.split() + date_str = date_parts[0] # 取第一个部分作为日期 - # 标准化日期格式 - if '/' in date_str: - date_str = date_str.replace('/', '-') + # 处理时间字符串,提取正确的部分 + # 如果时间字符串包含多个时间部分(如 "00:00:00 08:37:12"),取最后一个 + if ' ' in time_str: + time_parts = time_str.split() + # 取最后一个有效的时间部分 + time_str = time_parts[-1] - # 确保日期格式正确 - date_parts = date_str.split('-') - if len(date_parts) == 3: - year, month, day = date_parts - date_formatted = f"{year.zfill(4)}-{month.zfill(2)}-{day.zfill(2)}" - else: - print(f"⚠️ 日期格式异常: '{date_str}',使用当前日期") - date_formatted = datetime.now().strftime("%Y-%m-%d") - - # 时间字符串已经是修正后的格式(如 "08:34:01"),直接使用 - time_str = time_str.strip() + # 确保时间格式正确 if ':' in time_str and len(time_str.split(':')) >= 2: - time_formatted = time_str + # 组合日期和修正后的时间 + timestamp = f"{date_str} {time_str}" else: - print(f"⚠️ 时间格式异常: '{time_str}',使用默认时间") - time_formatted = "12:00:00" + timestamp = f"{date_str} 12:00:00" # 默认中午时间 + print(f" 时间格式异常 '{row['qStrTime']}',使用默认时间") - # 组合时间戳 - 直接连接日期和时间 - timestamp = f"{date_formatted} {time_formatted}" timestamps.append(timestamp) except Exception as e: - print(f"❌ 处理第 {idx+1} 行时间戳失败: {e}") + print(f"处理第 {idx+1} 行时间戳失败: {e}") timestamps.append(f"{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}") df['timestamp'] = timestamps print("时间戳融合示例:") for i in range(min(3, len(timestamps))): - print(f" {df.loc[i, '日期']} + {df.loc[i, '时间']} → {timestamps[i]}") + print(f" {df.loc[i, 'qStrDate']} + {df.loc[i, 'qStrTime']} → {timestamps[i]}") return df @@ -623,83 +641,104 @@ def rename_columns(df): # 定义字段映射 column_mapping = { - 'timestamp': 'timestamp', # 时间戳(已创建) - '经度': 'longitude', # 经度 → latitude - '纬度': 'latitude', # 纬度 → longitude - '融合高程': 'height_ato', # 融合高程 → height_ato - '修正风向': 'winddir', # 修正风向 → winddir - '修正风速': 'windspeed', # 修正风速 → windspeed - '风温': 'temperature', # 风温 → temperature - 'pressure': 'pressure', # 气压(已计算) - 'CH4': 'ch4', # CH4保持不变 - 'pitch': 'course_elevation', # pitch → course_elevation - 'yaw': 'course_azimuth' # yaw → course_azimuth + 'timestamp': 'timestamp', # 时间戳(已创建) + 'stGPSPositionX': 'longitude', # 经度 → longitude + 'stGPSPositionY': 'latitude', # 纬度 → latitude + 'fAltitudeFused': 'height_ato', # 融合高程 → height_ato + 'fFixedWindDirection': 'winddir', # 修正风向 → winddir + 'fFixedWindSpeed': 'windspeed', # 修正风速 → windspeed + 'fWindTemperature': 'temperature', # 风温 → temperature + 'CO2': 'CO2', # CO2浓度 → co2 + 'pitch': 'course_elevation', # pitch → course_elevation + 'yaw': 'course_azimuth' # yaw → course_azimuth } - # 重命名存在的列 - columns_to_rename = {} + # 先复制字段,再对复制的字段重命名 + columns_renamed = [] for old_name, new_name in column_mapping.items(): if old_name in df.columns: - columns_to_rename[old_name] = new_name + # 复制字段到新名称 + df[new_name] = df[old_name].copy() + columns_renamed.append((old_name, new_name)) + print(f" 复制并重命名: {old_name} → {new_name}") - if columns_to_rename: - df = df.rename(columns=columns_to_rename) - print("字段重命名:") - for old, new in columns_to_rename.items(): - print(f" {old} → {new}") - - # 只保留GasFlux需要的列 - required_columns = ['timestamp', 'latitude', 'longitude', 'height_ato', 'windspeed', 'winddir', 'temperature', 'pressure', 'ch4', 'course_elevation', 'course_azimuth'] - existing_required_columns = [col for col in required_columns if col in df.columns] - - if len(existing_required_columns) != len(required_columns): - missing = [col for col in required_columns if col not in df.columns] - print(f"⚠️ 缺少必需列: {missing}") - - # 移除不需要的列,只保留必需的列 - df = df[existing_required_columns] - print(f"最终保留列: {existing_required_columns}") + if columns_renamed: + print(f"共处理了 {len(columns_renamed)} 个字段") return df -def process_excel_file(file_path): +def ensure_float64_types(df, config=None): + """ + 确保数值字段为float64类型,以满足GasFlux处理要求 + + Args: + df: 输入DataFrame + config: 配置字典,包含gases字段 + + Returns: + pd.DataFrame: 数据类型转换后的DataFrame + """ + print("确保数值字段类型为float64...") + + # 定义基础需要转换为float64的字段 + float64_columns = [ + 'longitude', 'latitude', 'height_ato', # 位置和高度 + 'winddir', 'windspeed', 'temperature', # 风和温度 + 'pressure', # 气压 + 'course_elevation', 'course_azimuth' # 姿态角 + ] + + # 如果提供了配置,添加gases中的气体列 + if config and 'gases' in config: + gas_columns = list(config['gases'].keys()) + float64_columns.extend(gas_columns) + print(f"从配置中添加气体列: {gas_columns}") + + converted_count = 0 + for col in float64_columns: + if col in df.columns: + try: + original_dtype = df[col].dtype + df[col] = df[col].astype('float64') + new_dtype = df[col].dtype + if original_dtype != new_dtype: + print(f" 转换: {col} ({original_dtype} → {new_dtype})") + converted_count += 1 + except Exception as e: + print(f" 转换失败: {col} - {e}") + + if converted_count > 0: + print(f"共转换了 {converted_count} 个字段的数据类型为float64") + else: + print("所有数值字段已经是float64类型") + + return df + + +def process_excel_file(file_path, config_path=None): """ 处理单个Excel文件的主函数 Args: file_path: Excel文件路径 + config_path: 配置文件路径,用于读取gases配置 """ print(f"=== 开始处理文件: {file_path} ===\n") - # 获取文件名(用于时间修正) - filename = Path(file_path).name - # 1. 读取数据 df = load_excel_data(file_path) - # 2. 删除不需要的列 - columns_to_remove = [ - '高程', '速度x', '速度y', '速度z', - '四元数_q0', '四元数_q1', '四元数_q2', '四元数_q3', - 'roll', 'H2O', # 保留pitch和yaw,将重命名为course_elevation和course_azimuth - '原始风向', '原始风速' - ] - df = remove_columns(df, columns_to_remove) - - # 3. 修正时间格式 - df = fix_time_column(df, filename) - - # 4. 坐标转换 + # 2. 坐标转换 df = convert_coordinates(df) - # 5. 计算气压 + # 4. 计算气压数据 df = calculate_pressure(df, max_samples=None, height_tolerance=10.0, height_bin_size=2.0) # 计算所有行,高度容差10米,分档2米 - # 6. 高度调整 + # 5. 高度调整 df = adjust_altitude(df) - # 7. 时间戳融合 + # 6. 时间戳融合(保持原有时间格式) df = merge_timestamp(df) # 调试:检查当前列 @@ -707,28 +746,41 @@ def process_excel_file(file_path): if 'timestamp' in df.columns: print(f"timestamp列示例: {df['timestamp'].head(3).tolist()}") - # 8. 字段重命名 + # 7. 字段重命名 df = rename_columns(df) + # 8. 确保数值字段类型为float64 + # 读取配置以获取gases字段 + config = None + if config_path: + try: + with open(config_path, 'r', encoding='utf-8') as f: + config = yaml.safe_load(f) + except Exception as e: + print(f"读取配置文件失败: {e}") + + df = ensure_float64_types(df, config) + # 保存处理结果 output_path = Path(file_path).with_suffix('.processed.csv') - df.to_csv(output_path, index=False) + df.to_csv(output_path, index=False, encoding='utf-8-sig') - print(f"\n✅ 处理完成!") - print(f"📁 输出文件: {output_path}") - print(f"📊 最终数据形状: {df.shape[0]} 行 × {df.shape[1]} 列") - print(f"📋 最终列名: {list(df.columns)}") + print(f"\n处理完成!") + print(f"输出文件: {output_path}") + print(f"最终数据形状: {df.shape[0]} 行 × {df.shape[1]} 列") + print(f"最终列名: {list(df.columns)}") return df -def process_file(input_file, output_file=None): +def process_file(input_file, output_file=None, config_file=None): """ 直接处理Excel文件的函数(不使用命令行参数) Args: input_file: 输入Excel文件路径(字符串或Path对象) output_file: 输出CSV文件路径(可选,字符串或Path对象) + config_file: 配置文件路径(可选,用于读取gases配置) Returns: pd.DataFrame: 处理后的DataFrame @@ -744,13 +796,13 @@ def process_file(input_file, output_file=None): raise ValueError(f"输入文件必须是Excel格式 (.xlsx 或 .xls),当前文件: {input_path}") # 处理文件 - df = process_excel_file(str(input_path)) + df = process_excel_file(str(input_path), config_file) # 如果指定了输出路径,额外保存一份 if output_file: output_path = Path(output_file) - df.to_csv(output_path, index=False) - print(f"📁 额外保存到: {output_path}") + df.to_csv(output_path, index=False, encoding='utf-8-sig') + print(f"额外保存到: {output_path}") return df @@ -769,17 +821,17 @@ def interactive_input(): while True: input_file = input("请输入Excel文件路径 (例如: data.xlsx): ").strip() if not input_file: - print("❌ 文件路径不能为空,请重新输入") + print("文件路径不能为空,请重新输入") continue input_path = Path(input_file) if not input_path.exists(): - print(f"❌ 文件不存在: {input_path}") + print(f"文件不存在: {input_path}") print("提示: 请确保文件路径正确,或者将文件放在当前目录下") continue if input_path.suffix.lower() not in ['.xlsx', '.xls']: - print(f"❌ 文件格式错误: {input_path.suffix}") + print(f"文件格式错误: {input_path.suffix}") print("只支持 .xlsx 和 .xls 格式的Excel文件") continue @@ -791,13 +843,13 @@ def interactive_input(): output_file = None print("使用默认输出文件名") - print(f"\n✅ 输入确认:") + print(f"\n输入确认:") print(f" 输入文件: {input_file}") print(f" 输出文件: {output_file or '自动生成'}") confirm = input("\n确认开始处理? (y/N): ").strip().lower() if confirm not in ['y', 'yes', '是', '确认']: - print("❌ 用户取消操作") + print("用户取消操作") return None, None return input_file, output_file @@ -823,7 +875,7 @@ def main(input_file=None, output_file=None, interactive=False): try: return process_file(input_file, output_file) except Exception as e: - print(f"❌ 处理失败: {e}") + print(f"处理失败: {e}") raise # 否则使用命令行参数 @@ -881,7 +933,7 @@ def main(input_file=None, output_file=None, interactive=False): if not input_file: parser.print_help() print("\n" + "="*60) - print("📖 使用示例:") + print("使用示例:") print("="*60) print("1. 命令行模式:") print(" python data_processor.py your_file.xlsx") @@ -900,11 +952,11 @@ def main(input_file=None, output_file=None, interactive=False): # 检查输入文件 input_path = Path(input_file) if not input_path.exists(): - print(f"❌ 错误:输入文件不存在: {input_path}") + print(f"错误:输入文件不存在: {input_path}") sys.exit(1) if input_path.suffix.lower() not in ['.xlsx', '.xls']: - print(f"❌ 错误:输入文件必须是Excel格式 (.xlsx 或 .xls)") + print(f"错误:输入文件必须是Excel格式 (.xlsx 或 .xls)") sys.exit(1) # 处理文件 @@ -914,16 +966,16 @@ def main(input_file=None, output_file=None, interactive=False): # 如果指定了输出路径,额外保存一份 if output_file: output_path = Path(output_file) - df.to_csv(output_path, index=False) - print(f"📁 额外保存到: {output_path}") + df.to_csv(output_path, index=False, encoding='utf-8-sig') + print(f"额外保存到: {output_path}") return df except KeyboardInterrupt: - print("\n⚠️ 用户中断处理") + print("\n用户中断处理") sys.exit(1) except Exception as e: - print(f"\n❌ 处理失败: {e}") + print(f"\n处理失败: {e}") import traceback traceback.print_exc() sys.exit(1) diff --git a/src/gasflux/processing_pipelines.py b/src/gasflux/processing_pipelines.py index f3abb1e..1c2795b 100644 --- a/src/gasflux/processing_pipelines.py +++ b/src/gasflux/processing_pipelines.py @@ -192,8 +192,13 @@ class SpiralSpatialProcessingStrategy(SpatialProcessingStrategy): self.data_processor.circle_center_x, self.data_processor.circle_center_y, ) = processing.circle_deviation(self.data_processor.df, x_col="utm_easting", y_col="utm_northing") + + # 使用配置中第一个气体的归一化列名 + primary_gas = self.data_processor.gases[0] + y_col = f"{primary_gas}_normalised" + self.data_processor.df = processing.recentre_azimuth( - self.data_processor.df, r=self.data_processor.circle_radius + self.data_processor.df, r=self.data_processor.circle_radius, y=y_col ) self.data_processor.df["x"] = self.data_processor.df["circumference_distance"] for gas_name in self.data_processor.gases: @@ -304,10 +309,11 @@ class DataProcessor: ].std() -def process_main(data_file: Path, config_file: Path) -> None: +def process_main(data_file: Path, config_file: Path, task_id: str | None = None) -> None: """Main function to run the pipeline.""" config = load_config(config_file) - name = data_file.stem + # 优先使用 task_id,否则退回文件 stem + name = task_id if task_id else data_file.stem df = read_csv(data_file) processor = DataProcessor(config, df) diff --git a/src/gasflux/reporting.py b/src/gasflux/reporting.py index 4de5cee..15cea2c 100644 --- a/src/gasflux/reporting.py +++ b/src/gasflux/reporting.py @@ -66,47 +66,52 @@ def generate_reports(name: str, processor, config: dict): Generates reports, configuration files, and processed output variables for gasflux processing runs. Parameters: - name (str): The name identifier for the current processing run. + name (str): The name identifier for the current processing run (task_id). processor (object): The processing object containing report data and output variables. config (dict): Configuration dictionary used for processing. """ output_dir = Path(config["output_dir"]).expanduser() processing_time = datetime.now() - output_path = output_dir / name / processing_time.strftime("%Y-%m-%d_%H-%M-%S-%f_processing_run") + # Save directly to outputs/{task_id} directory + output_path = output_dir / "outputs" / name output_path.mkdir(parents=True, exist_ok=True) # Save reports for gas, report in processor.reports.items(): - report_path = output_path / f"{name}_{gas}_report.html" + timestamp_str = processing_time.strftime("%Y%m%d_%H%M%S") + report_path = output_path / f"{gas}_report_{timestamp_str}.html" with open(report_path, "w", encoding="utf-8") as file: file.write(report) # Save config - header = f"# Gasflux output config for file {name} from processing run at {processing_time}\n" - config_path = output_path / f"{name}_config.yaml" + header = f"# Gasflux output config for task {name} from processing run at {processing_time}\n" + timestamp_str = processing_time.strftime("%Y%m%d_%H%M%S") + config_path = output_path / f"config_{timestamp_str}.yaml" with open(config_path, "w") as file: file.write(header) yaml.safe_dump(config, file) - # Save DataFrame to CSV + # Save DataFrame to Excel if hasattr(processor, 'df') and processor.df is not None: - csv_path = output_path / f"{name}_data.csv" - processor.df.to_csv(csv_path, index=False) - logger.info(f"DataFrame saved to {csv_path}") + timestamp_str = processing_time.strftime("%Y%m%d_%H%M%S") + excel_path = output_path / f"processed_data_{timestamp_str}.xlsx" + processor.df.to_excel(excel_path, index=False, engine='openpyxl') + logger.info(f"DataFrame saved to {excel_path}") # Save output variables output_vars = processor.output_vars # output_vars = delete_large_arrays(output_vars, threshold_size=50) header = ( - f"# Gasflux output variables for file {name} from processing run at {processing_time}\n" + f"# Gasflux output variables for task {name} from processing run at {processing_time}\n" ) - filename = output_path / f"{name}_output_vars.json" + timestamp_str = processing_time.strftime("%Y%m%d_%H%M%S") + filename = output_path / f"output_vars_{timestamp_str}.json" with open(filename, "w") as file: file.write(header) json.dump( output_vars, file, default=lambda item: item.tolist() if isinstance(item, np.ndarray) else item, indent=4 ) - logger.info(f"Processing run saved to {output_path}") + logger.info(f"Task {name} results saved to {output_path}") def delete_large_arrays(output_vars: dict, threshold_size: int) -> dict: diff --git a/src/gasflux/run_example.py b/src/gasflux/run_example.py index 52509b9..8c41de4 100644 --- a/src/gasflux/run_example.py +++ b/src/gasflux/run_example.py @@ -81,7 +81,7 @@ def main(): if args.output: processed_csv = Path(args.output) else: - processed_csv = input_path.with_suffix('.processed.csv') + processed_csv = input_path.with_suffix('_processed.csv') # 确定配置文件 if args.config: @@ -98,7 +98,7 @@ def main(): try: # 第一步:数据预处理 print("🔄 第一步:数据预处理...") - processed_df = process_file(str(input_path), str(processed_csv)) + processed_df = process_file(str(input_path), str(processed_csv), str(config_file)) print(f"✅ 数据预处理完成,输出文件: {processed_csv}") print() diff --git a/src/gasflux/shared.py b/src/gasflux/shared.py new file mode 100644 index 0000000..8073d65 --- /dev/null +++ b/src/gasflux/shared.py @@ -0,0 +1,704 @@ +""" +Shared utilities and constants for GasFlux API. +This module contains shared functions and variables to avoid circular imports. +""" + +import time +import logging +from functools import wraps +from flask import request, current_app, g +from pathlib import Path +import threading + +# Task status constants +TASK_STATUS_PENDING = "pending" +TASK_STATUS_PROCESSING = "processing" +TASK_STATUS_COMPLETED = "completed" +TASK_STATUS_FAILED = "failed" + +# Global task status storage +task_status = {} + +# Task status persistence file override (set by app on startup) +_TASK_STATUS_FILE_PATH: Path | None = None +_TASK_STATUS_FILE_LOCK = threading.Lock() + + +def set_task_status_file_path(path: str | Path): + """Set the task status persistence file path (used across threads without Flask app context).""" + global _TASK_STATUS_FILE_PATH + _TASK_STATUS_FILE_PATH = Path(path) + + +def _build_simple_downloads_from_results(results: list[dict]) -> dict: + """Build direct download shortcuts for common files (minimal, frontend-friendly).""" + downloads: dict = {} + + def set_once(key: str, url: str): + if key not in downloads and url: + downloads[key] = url + + for item in results or []: + if not isinstance(item, dict): + continue + rel_path = item.get('rel_path') + if not rel_path: + continue + + name_l = (item.get('name') or '').lower() + url = f"/download/{rel_path}" + + if name_l.endswith('.xlsx'): + set_once('data_xlsx', url) + elif name_l.endswith('.xls'): + set_once('data_xls', url) + elif name_l.endswith('ch4_report.html'): + set_once('report_ch4', url) + elif name_l.endswith('co2_report.html'): + set_once('report_co2', url) + elif name_l.endswith(('.yaml', '.yml')): + set_once('config', url) + elif name_l.endswith('.json') and 'output_vars' in name_l: + set_once('metadata', url) + elif name_l.endswith('.html'): + set_once('report_html', url) + + return downloads + +# File extension constants +ALLOWED_DATA_EXTENSIONS = {'xlsx', 'xls'} +ALLOWED_CONFIG_EXTENSIONS = {'yaml', 'yml'} + +# Statistics collector +class StatisticsCollector: + """Collects and manages API statistics.""" + + def __init__(self): + self.start_time = time.time() + self.stats = { + 'requests': { + 'total': 0, + 'by_method': {}, + 'by_endpoint': {}, + 'by_status': {}, + 'response_times': [], + 'errors': 0 + }, + 'tasks': { + 'total_created': 0, + 'total_completed': 0, + 'total_failed': 0, + 'by_status': { + 'pending': 0, + 'processing': 0, + 'completed': 0, + 'failed': 0 + }, + 'processing_times': [] + }, + 'performance': { + 'avg_response_time': 0, + 'max_response_time': 0, + 'min_response_time': float('inf'), + 'uptime_seconds': time.time() - self.start_time + } + } + + def record_request(self, method, endpoint, status_code, response_time): + """Record an API request.""" + self.stats['requests']['total'] += 1 + + # Method stats + if method not in self.stats['requests']['by_method']: + self.stats['requests']['by_method'][method] = 0 + self.stats['requests']['by_method'][method] += 1 + + # Endpoint stats + if endpoint not in self.stats['requests']['by_endpoint']: + self.stats['requests']['by_endpoint'][endpoint] = 0 + self.stats['requests']['by_endpoint'][endpoint] += 1 + + # Status stats + status_category = str(status_code // 100 * 100) # 200, 400, 500, etc. + if status_category not in self.stats['requests']['by_status']: + self.stats['requests']['by_status'][status_category] = 0 + self.stats['requests']['by_status'][status_category] += 1 + + # Response time stats + self.stats['requests']['response_times'].append(response_time) + + # Keep only last 1000 response times for memory efficiency + if len(self.stats['requests']['response_times']) > 1000: + self.stats['requests']['response_times'] = self.stats['requests']['response_times'][-1000:] + + # Error tracking + if status_code >= 400: + self.stats['requests']['errors'] += 1 + + # Update performance stats + self._update_performance_stats() + + def record_task_status_change(self, old_status, new_status): + """Record task status changes.""" + if old_status == "unknown": # New task + self.stats['tasks']['total_created'] += 1 + + if new_status == TASK_STATUS_COMPLETED: + self.stats['tasks']['total_completed'] += 1 + self.stats['tasks']['by_status']['completed'] += 1 + elif new_status == TASK_STATUS_FAILED: + self.stats['tasks']['total_failed'] += 1 + self.stats['tasks']['by_status']['failed'] += 1 + elif new_status == TASK_STATUS_PROCESSING: + self.stats['tasks']['by_status']['processing'] += 1 + elif new_status == TASK_STATUS_PENDING: + self.stats['tasks']['by_status']['pending'] += 1 + + def record_task_completion_time(self, completion_time): + """Record task completion time.""" + self.stats['tasks']['processing_times'].append(completion_time) + + def reset_stats(self): + """Reset all statistics.""" + current_time = time.time() + self.start_time = current_time + self.stats = { + 'requests': { + 'total': 0, + 'by_method': {}, + 'by_endpoint': {}, + 'by_status': {}, + 'response_times': [], + 'errors': 0 + }, + 'tasks': { + 'total_created': 0, + 'total_completed': 0, + 'total_failed': 0, + 'by_status': { + 'pending': 0, + 'processing': 0, + 'completed': 0, + 'failed': 0 + }, + 'processing_times': [] + }, + 'performance': { + 'avg_response_time': 0, + 'max_response_time': 0, + 'min_response_time': float('inf'), + 'uptime_seconds': current_time - self.start_time + } + } + + def _update_performance_stats(self): + """Update performance statistics.""" + response_times = self.stats['requests']['response_times'] + if response_times: + self.stats['performance']['avg_response_time'] = sum(response_times) / len(response_times) + self.stats['performance']['max_response_time'] = max(response_times) + self.stats['performance']['min_response_time'] = min(response_times) + + self.stats['performance']['uptime_seconds'] = time.time() - self.start_time + + def get_summary(self): + """Get a summary of current statistics.""" + current_time = time.time() + uptime = current_time - self.start_time + + # Calculate rates + requests_per_second = self.stats['requests']['total'] / max(uptime, 1) + error_rate = (self.stats['requests']['errors'] / max(self.stats['requests']['total'], 1)) * 100 + + # Task completion rate + total_tasks_processed = self.stats['tasks']['total_completed'] + self.stats['tasks']['total_failed'] + task_success_rate = (self.stats['tasks']['total_completed'] / max(total_tasks_processed, 1)) * 100 + + return { + 'summary': { + 'uptime_seconds': uptime, + 'uptime_formatted': self._format_uptime(uptime), + 'requests_total': self.stats['requests']['total'], + 'requests_per_second': round(requests_per_second, 2), + 'error_rate_percent': round(error_rate, 2), + 'active_tasks': len([t for t in task_status.values() + if t.get('status') in [TASK_STATUS_PENDING, TASK_STATUS_PROCESSING]]) + }, + 'requests': { + 'by_method': self.stats['requests']['by_method'], + 'by_status': self.stats['requests']['by_status'], + 'top_endpoints': dict(sorted(self.stats['requests']['by_endpoint'].items(), + key=lambda x: x[1], reverse=True)[:10]) + }, + 'tasks': { + 'total_created': self.stats['tasks']['total_created'], + 'total_completed': self.stats['tasks']['total_completed'], + 'total_failed': self.stats['tasks']['total_failed'], + 'success_rate_percent': round(task_success_rate, 2), + 'by_status': self.stats['tasks']['by_status'] + }, + 'performance': { + 'avg_response_time_ms': round(self.stats['performance']['avg_response_time'] * 1000, 2), + 'max_response_time_ms': round(self.stats['performance']['max_response_time'] * 1000, 2), + 'min_response_time_ms': round(self.stats['performance']['min_response_time'] * 1000, 2) + if self.stats['performance']['min_response_time'] != float('inf') else 0 + } + } + + def _format_uptime(self, seconds): + """Format uptime in human readable format.""" + days, remainder = divmod(int(seconds), 86400) + hours, remainder = divmod(remainder, 3600) + minutes, seconds = divmod(remainder, 60) + + parts = [] + if days > 0: + parts.append(f"{days}d") + if hours > 0: + parts.append(f"{hours}h") + if minutes > 0: + parts.append(f"{minutes}m") + parts.append(f"{seconds}s") + + return " ".join(parts) + + +# Create global statistics collector instance +stats_collector = StatisticsCollector() + + +# Shared utility functions +def log_performance(func): + """Decorator to log function performance.""" + @wraps(func) + def wrapper(*args, **kwargs): + start_time = time.time() + try: + result = func(*args, **kwargs) + duration = time.time() - start_time + logger.debug(f"PERF: {func.__name__} completed in {duration:.3f}s") + return result + except Exception as e: + duration = time.time() - start_time + logger.error(f"PERF: {func.__name__} failed after {duration:.3f}s - Error: {str(e)}") + raise + return wrapper + + +def log_request_info(): + """Log incoming request information.""" + if hasattr(request, 'remote_addr'): + logger.info(f"REQUEST: {request.method} {request.url} - IP: {request.remote_addr} - User-Agent: {request.headers.get('User-Agent', 'Unknown')}") + + +def log_response_info(response): + """Log outgoing response information.""" + if hasattr(request, 'url_rule') and request.url_rule: + endpoint = request.url_rule.rule + else: + endpoint = request.path + + start_time = getattr(g, 'start_time', None) + if start_time: + duration = time.time() - start_time + + logger.info(f"RESPONSE: {request.method} {endpoint} - Status: {response.status_code} - Duration: {duration:.3f}s" if duration else f"RESPONSE: {request.method} {endpoint} - Status: {response.status_code}") + + +def update_task_status(task_id, status, message=None, results=None, error=None): + """Update task status in the global dictionary.""" + timestamp = time.time() + old_status = task_status.get(task_id, {}).get("status", "unknown") + + task_status[task_id] = { + "status": status, + "message": message, + "results": results, + "error": error, + "updated_at": timestamp, + "created_at": task_status.get(task_id, {}).get("created_at", timestamp) + } + + # Record status change in statistics + stats_collector.record_task_status_change(old_status, status) + + # Log detailed status change with context + log_msg = f"Task {task_id} status changed: {old_status} -> {status}" + if message: + log_msg += f" | Message: {message}" + if results: + log_msg += f" | Results count: {len(results) if isinstance(results, list) else 'N/A'}" + if error: + log_msg += f" | Error: {error}" + + log_level = logging.ERROR if status == TASK_STATUS_FAILED else logging.INFO + logger.log(log_level, log_msg) + + # Save task status to file for persistence + logger.debug(f"Saving task {task_id} status '{status}' to persistent storage") + save_task_status_to_file() + + +def get_task_status(task_id): + """Get task status from global dictionary.""" + if task_id in task_status: + return task_status[task_id] + return {"status": "not_found"} + + +def cleanup_old_tasks(): + """Clean up old completed tasks to prevent memory leak.""" + current_time = time.time() + max_age = 24 * 3600 # 24 hours + to_remove = [] + + for task_id, task_info in task_status.items(): + task_age = current_time - task_info.get("updated_at", 0) + if task_age > max_age: + to_remove.append(task_id) + logger.info(f"Task {task_id} scheduled for cleanup (age: {task_age:.1f}s, status: {task_info.get('status')})") + + initial_count = len(task_status) + for task_id in to_remove: + del task_status[task_id] + + if to_remove: + logger.info(f"Cleanup completed: removed {len(to_remove)} tasks, {len(task_status)} tasks remaining") + else: + logger.debug(f"Cleanup check: no old tasks to remove ({len(task_status)} active tasks)") + + +def get_task_list(status_filter=None, page=1, page_size=20, sort_by='updated_at', sort_order='desc', cleanup=True): + """ + Get paginated list of tasks with optional filtering and sorting. + + Args: + status_filter (str or list): Filter by task status. Can be single status or list of statuses. + page (int): Page number (1-based). + page_size (int): Number of tasks per page. + sort_by (str): Sort field ('created_at', 'updated_at', 'status'). + sort_order (str): Sort order ('asc' or 'desc'). + cleanup (bool): Whether to cleanup old tasks before returning list. + + Returns: + dict: { + 'tasks': list of task summaries, + 'total': total number of tasks, + 'page': current page, + 'page_size': page size, + 'total_pages': total pages, + 'has_next': has next page, + 'has_prev': has previous page + } + """ + if cleanup: + cleanup_old_tasks() # Clean up old tasks before returning list + + # Filter tasks + filtered_tasks = [] + for task_id, task_info in task_status.items(): + if status_filter: + if isinstance(status_filter, str): + if task_info.get('status') != status_filter: + continue + elif isinstance(status_filter, list): + if task_info.get('status') not in status_filter: + continue + filtered_tasks.append((task_id, task_info)) + + # Sort tasks + def safe_numeric_sort(value): + """Safely convert value to numeric for sorting.""" + try: + if isinstance(value, (int, float)): + return value + elif isinstance(value, str): + # Try to convert string timestamp to float + return float(value) + else: + return 0 + except (ValueError, TypeError): + return 0 + + reverse_order = sort_order.lower() == 'desc' + if sort_by == 'created_at': + filtered_tasks.sort(key=lambda x: safe_numeric_sort(x[1].get('created_at', 0)), reverse=reverse_order) + elif sort_by == 'updated_at': + filtered_tasks.sort(key=lambda x: safe_numeric_sort(x[1].get('updated_at', 0)), reverse=reverse_order) + elif sort_by == 'status': + filtered_tasks.sort(key=lambda x: x[1].get('status', ''), reverse=reverse_order) + else: + # Default sort by updated_at desc + filtered_tasks.sort(key=lambda x: safe_numeric_sort(x[1].get('updated_at', 0)), reverse=True) + + # Paginate + total_tasks = len(filtered_tasks) + total_pages = (total_tasks + page_size - 1) // page_size + start_idx = (page - 1) * page_size + end_idx = start_idx + page_size + + paginated_tasks = filtered_tasks[start_idx:end_idx] + + # Format task summaries + task_summaries = [] + for task_id, task_info in paginated_tasks: + summary = { + 'task_id': task_id, + 'status': task_info.get('status'), + 'message': task_info.get('message'), + 'created_at': task_info.get('created_at'), + 'updated_at': task_info.get('updated_at'), + 'has_results': bool(task_info.get('results')), + 'has_error': bool(task_info.get('error')) + } + task_summaries.append(summary) + + return { + 'tasks': task_summaries, + 'total': total_tasks, + 'page': page, + 'page_size': page_size, + 'total_pages': total_pages, + 'has_next': page < total_pages, + 'has_prev': page > 1 + } + + +def get_task_pool_stats(): + """ + Get task pool statistics. + + Returns: + dict: Task pool statistics including counts by status. + """ + # Note: cleanup_old_tasks() is disabled for task pool stats + # to preserve historical task data for management purposes + # cleanup_old_tasks() # Clean up old tasks before calculating stats + + stats = { + 'total_tasks': len(task_status), + 'status_counts': {}, + 'active_tasks': 0, + 'queued_tasks': 0, + 'completed_tasks': 0, + 'failed_tasks': 0 + } + + for task_info in task_status.values(): + status = task_info.get('status') + if status: + stats['status_counts'][status] = stats['status_counts'].get(status, 0) + 1 + + if status == TASK_STATUS_PROCESSING: + stats['active_tasks'] += 1 + elif status == TASK_STATUS_PENDING: + stats['queued_tasks'] += 1 + elif status == TASK_STATUS_COMPLETED: + stats['completed_tasks'] += 1 + elif status == TASK_STATUS_FAILED: + stats['failed_tasks'] += 1 + + return stats + + +def _get_status_file(): + """Get the task status file path, preferring OUTPUT_FOLDER for reliability.""" + if _TASK_STATUS_FILE_PATH is not None: + return _TASK_STATUS_FILE_PATH + try: + # Prefer OUTPUT_FOLDER which is more reliable and writable + from flask import current_app + output_dir = current_app.config.get('OUTPUT_FOLDER') + if output_dir: + return Path(output_dir) / "task_status.json" + except Exception: + pass + # Fall back to module directory (for development) + return Path(__file__).parent / "task_status.json" + + +def _to_json_safe(obj): + """Convert numpy types and other non-JSON-serializable objects to JSON-safe types.""" + # Preserve JSON-native types + if obj is None or isinstance(obj, (str, int, float, bool)): + return obj + # Recursively handle containers + if isinstance(obj, list): + return [_to_json_safe(v) for v in obj] + if isinstance(obj, tuple): + return [_to_json_safe(v) for v in obj] + if isinstance(obj, dict): + return {str(k): _to_json_safe(v) for k, v in obj.items()} + + try: + import numpy as np + if isinstance(obj, (np.integer,)): + return int(obj) + if isinstance(obj, (np.floating,)): + return float(obj) + if isinstance(obj, (np.bool_,)): + return bool(obj) + if isinstance(obj, (np.ndarray,)): + return obj.tolist() + except Exception: + pass + # Non-builtin/unknown types, fall back to string representation + try: + return str(obj) + except Exception: + return None + + +def save_task_status_to_file(): + """Save current task status to JSON file for persistence.""" + try: + import json + import os + from pathlib import Path + + status_file = _get_status_file() + logger.debug(f"Attempting to save {len(task_status)} task statuses to {status_file}") + + # Guard: 避免用空内存覆盖已有文件 + if not task_status and status_file.exists(): + logger.info("Skipping task status save: empty in-memory status and file already exists") + return + + # Ensure only one thread writes the status file at a time (prevents Windows replace/lock issues) + with _TASK_STATUS_FILE_LOCK: + + status_to_save = {} + for task_id, task_info in task_status.items(): + try: + clean_info = {} + for k, v in task_info.items(): + if k == 'results' and isinstance(v, list): + # Keep only metadata for results, remove potentially large data fields + cleaned_results = [] + for item in v: + if isinstance(item, dict): + cleaned_item = {kk: vv for kk, vv in item.items() if kk not in ['data', 'arrays']} + + # Normalize result metadata for persistence + name = cleaned_item.get('name') or '' + rel_path = cleaned_item.get('rel_path') or '' + + # Normalize size to int + size = cleaned_item.get('size', 0) + try: + size = int(size) + except Exception: + size = 0 + cleaned_item['size'] = size + + # Infer/normalize type if missing or unknown + t = cleaned_item.get('type') + if (not t) or (t == 'unknown'): + t = _get_file_type(name or rel_path) + cleaned_item['type'] = t + + # Convert numpy types / other objects to JSON-safe + for kk, vv in list(cleaned_item.items()): + cleaned_item[kk] = _to_json_safe(vv) + + cleaned_results.append(cleaned_item) + + clean_info['results'] = cleaned_results + + # Slim persistence: store only direct downloads for frontend + clean_info['downloads'] = _build_simple_downloads_from_results(cleaned_results) + + # Ensure each result has a download_url (optional but handy) + for r in clean_info['results']: + if isinstance(r, dict) and r.get('rel_path') and not r.get('download_url'): + r['download_url'] = f"/download/{r['rel_path']}" + else: + clean_info[k] = _to_json_safe(v) + + status_to_save[task_id] = clean_info + logger.debug(f"Processed task {task_id} with status {clean_info.get('status')}") + + except Exception as task_e: + logger.warning(f"Failed to process task {task_id}: {task_e}") + # Skip this task but continue with others + continue + + # Ensure the directory exists + status_file.parent.mkdir(parents=True, exist_ok=True) + + # Write to a unique temporary file first, then rename for atomicity with fsync + temp_file = status_file.with_suffix(f'.{os.getpid()}.tmp') + with open(temp_file, 'w', encoding='utf-8') as f: + json.dump(status_to_save, f, indent=2, ensure_ascii=False) + f.flush() + os.fsync(f.fileno()) # Force write to disk + + # Atomic rename + temp_file.replace(status_file) + + logger.info(f"Successfully saved {len(status_to_save)} task statuses to {status_file}") + + except Exception as e: + logger.error(f"Failed to save task status to file: {e}", exc_info=True) + + +def load_task_status_from_file(): + """Load task status from JSON file on startup.""" + try: + import json + + status_file = _get_status_file() + + if not status_file.exists(): + logger.info("No task status file found, starting with empty task pool") + return + + # Ensure no writer thread is updating the file while we read it + with _TASK_STATUS_FILE_LOCK: + with open(status_file, 'r', encoding='utf-8') as f: + loaded_status = json.load(f) + + # Restore task status + global task_status + task_status.clear() + task_status.update(loaded_status) + + logger.info(f"Loaded {len(loaded_status)} task statuses from {status_file}") + + except Exception as e: + logger.warning(f"Failed to load task status from file: {e}") + + +def allowed_file(filename, allowed_extensions): + """Check if file extension is allowed.""" + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in allowed_extensions + + +def _format_response(code, message, data=None): + """Format API response in unified format.""" + response = { + "code": code, + "message": message, + "data": data if data is not None else {} + } + return response, code + + +def _get_file_type(filename): + """Determine file type from filename.""" + fn = (filename or '').lower() + if fn.endswith('.html'): + return 'report' + elif fn.endswith(('.csv', '.xlsx', '.xls')): + return 'data' + elif fn.endswith('.yaml') or fn.endswith('.yml'): + return 'config' + elif fn.endswith('.json'): + return 'metadata' + else: + return 'other' + + +# Initialize logger +logger = logging.getLogger('gasflux_api') \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/test_processing.py b/tests/test_processing.py deleted file mode 100644 index 31de69b..0000000 --- a/tests/test_processing.py +++ /dev/null @@ -1,205 +0,0 @@ -import pandas as pd -from src.gasflux import processing -import yaml -from pathlib import Path -import numpy as np -from src.gasflux.processing import min_angular_displacement -import pytest - - -testdf = pd.read_csv(Path(__file__).parents[1] / "src" / "gasflux" / "testdata" / "testdata.csv") -testconfig = yaml.safe_load(open(Path(__file__).parents[1] / "src" / "gasflux" / "testdata" / "testconfig.yaml")) - - -def load_cols(cols): - return testdf[cols] - - -def test_min_angular_diff_def(): - def test_min_angular_displacement(): - assert min_angular_displacement(10, 350) == 20 - assert min_angular_displacement(0, 180) == 180 - x = np.array([10, 0]) - y = np.array([350, 180]) - expected = np.array([20, 180]) - result = min_angular_displacement(x, y) - assert np.all(result == expected), "Vectorized function failed" - - -def test_circ_median(): - x = np.array([0, 1, 2, 359, 4, 3]) - median = processing.circ_median(x) - assert median == 1.5, "Circular median not calculated correctly" - - -@pytest.mark.parametrize( - "plane_angle,expected_winddir_rel,expected_windspeed_normal", - [ - ( - 90, - [0, 90, 0, 90, 0], - [5, 0, 5, 0, 5], - ), - ( - 30, - [60, 30, 60, 30, 60], - np.array([1 / 2, np.sqrt(3) / 2, 1 / 2, np.sqrt(3) / 2, 1 / 2]) * 5, - ), - ( - 60, - [30, 60, 30, 60, 30], - np.array([np.sqrt(3) / 2, 1 / 2, np.sqrt(3) / 2, 1 / 2, np.sqrt(3) / 2]) * 5, - ), - ], -) -def test_wind_offset_correction_parametrized(plane_angle, expected_winddir_rel, expected_windspeed_normal): - data = {"winddir": [0, 90, 180, 270, 360], "windspeed": [5, 5, 5, 5, 5]} - df = pd.DataFrame(data) - corrected_df = processing.wind_offset_correction(df, plane_angle) - assert "winddir_rel" in corrected_df.columns, f"Relative wind direction column not added for angle {plane_angle}" - assert "windspeed" in corrected_df.columns, f"Normalised wind speed column not added for angle {plane_angle}" - assert np.allclose(corrected_df["winddir_rel"], expected_winddir_rel, rtol=1e-5, atol=1e-10), ( - f"Relative wind directions not calculated correctly for angle {plane_angle}" - ) - assert np.allclose(corrected_df["windspeed"], expected_windspeed_normal, rtol=1e-5, atol=1e-10), ( - f"Normalised wind speeds not calculated correctly for angle {plane_angle}" - ) - - -def test_bimodal_azimuth(): - input_mode = testconfig["transect_azimuth"] - input_reciprocal_mode = (input_mode + 180) % 360 - df = load_cols(["course_azimuth", "height_ato"]) - mode1, mode2 = processing.bimodal_azimuth(df) - assert ( - min_angular_displacement(mode1, input_mode) < 3 or min_angular_displacement(mode1, input_reciprocal_mode) < 3 - ), "Mode1 does not match expected azimuth or its reciprocal within 3 degrees" - - if min_angular_displacement(mode1, input_mode) < 3: - assert min_angular_displacement(mode2, input_reciprocal_mode) < 3, ( - "Mode2 does not match expected reciprocal azimuth within 3 degrees" - ) - else: - assert min_angular_displacement(mode2, input_mode) < 3, "Mode2 does not match expected azimuth within 3 degrees" - - -def test_bimodal_elevation(): - df = load_cols(["course_elevation", "height_ato"]) - input_mode = 0 - input_reciprocal_mode = 0 - input_mode - mode1, mode2 = processing.bimodal_elevation(df) - assert ( - min_angular_displacement(mode1, input_mode) < 3 or min_angular_displacement(mode1, input_reciprocal_mode) < 3 - ), "Mode1 does not match expected elevation or its reciprocal within 3 degrees" - if min_angular_displacement(mode1, input_mode) < 3: - assert min_angular_displacement(mode2, input_reciprocal_mode) < 3, ( - "Mode2 does not match expected reciprocal elevation within 3 degrees" - ) - else: - assert min_angular_displacement(mode2, input_mode) < 3, ( - "Mode2 does not match expected elevation within 3 degrees" - ) - - -def test_height_transect_splitter(): - df = load_cols(["height_ato"]) - df, fig = processing.height_transect_splitter(df) - assert "transect_num" in df.columns, "Transect number column not added to dataframe" - assert df["transect_num"].nunique() == testconfig["number_of_transects"], ( - "Dataframe was not split into the right number of transects" - ) - - -def test_add_transect_azimuth_switches(): - df = load_cols(["course_azimuth"]) - df = processing.add_transect_azimuth_switches(df) - assert df["transect_num"].nunique() == testconfig["number_of_transects"], ( - "Transect azimuth switches not added to dataframe" - ) - - -def test_course_filter(): - df = load_cols(["course_azimuth", "course_elevation", "height_ato"]) - azimuth_filter = testconfig["filters"]["course_filter"]["azimuth_filter"] - azimuth_window = testconfig["filters"]["course_filter"]["azimuth_window"] - elevation_filter = testconfig["filters"]["course_filter"]["elevation_filter"] - df_filtered, df_unfiltered = processing.course_filter( - df, azimuth_filter=azimuth_filter, azimuth_window=azimuth_window, elevation_filter=elevation_filter - ) - input_mode = testconfig["transect_azimuth"] - input_reciprocal_mode = (input_mode + 180) % 360 - # assert that the filtered dataframe contains the expected azimuth or its reciprocal within the window - df_filtered["near_mode1"] = df_filtered["rolling_course_azimuth"].apply( - lambda x: min_angular_displacement(x, input_mode) < azimuth_window - ) - df_filtered["near_mode2"] = df_filtered["rolling_course_azimuth"].apply( - lambda x: min_angular_displacement(x, input_reciprocal_mode) < azimuth_window - ) - assert df_filtered["near_mode1"].any() or df_filtered["near_mode2"].any(), ( - "Filtered dataframe does not contain expected azimuth or its reciprocal within the window" - ) - - -def test_mCount_max(): - data_dict = {1: -5.4, 2: 0.6, 3: 5.6, 4: 3.2, 5: 10.4, 6: 18.4, 7: 20.8, 8: 19.4} - start, end = processing.mCount_max(data_dict) - assert start == 4, "Start index of max count not calculated correctly" - assert end == 7, "End index of max count not calculated correctly" - - -def test_largest_monotonic_transect_series(): - df = load_cols( - ["timestamp", "height_ato", "course_azimuth", "longitude", "latitude", "utm_easting", "utm_northing"] - ) - df, starttransect, endtransect = processing.largest_monotonic_transect_series(df) - starttransect = 1 - endtransect = testconfig["number_of_transects"] - assert starttransect == starttransect, "Start index of largest monotonic transect not calculated correctly" - assert endtransect == endtransect, "End index of largest monotonic transect not calculated correctly" - - -def test_remove_non_transects(): - df = load_cols( - ["height_ato", "course_azimuth", "course_elevation", "longitude", "latitude", "utm_easting", "utm_northing"] - ) - retained_df, removed_df = processing.remove_non_transects(df) - assert retained_df is not None, "Retained dataframe is None" - assert removed_df is not None, "Removed dataframe is None" - - -def test_flatten_linear_plane(): - df = load_cols(["height_ato", "utm_easting", "utm_northing"]) - df, plane_angle = processing.flatten_linear_plane(df) - input_plane_angle = testconfig["transect_azimuth"] - reciprocal_plane_angle = (input_plane_angle + 180) % 360 - assert ( - min_angular_displacement(plane_angle, input_plane_angle) < 3 - or min_angular_displacement(plane_angle, reciprocal_plane_angle) < 3 - ), "Plane angle not calculated correctly" - - -def test_drone_anemo_to_point_wind(): - data = { - "yaw": [0, 90, 0, -90, 180], - "anemo_u": [0, 0, 10, 10, 10], - "anemo_v": [0, 0, 0, 0, 0], - "easting": [0, 10, 0, 10, 0], - "northing": [0, 0, 0, 0, 10], - } - df_test = pd.DataFrame(data) - yaw_col = "yaw" - anemo_u_col = "anemo_u" - anemo_v_col = "anemo_v" - easting_col = "easting" - northing_col = "northing" - result_df = processing.drone_anemo_to_point_wind( - df_test, yaw_col, anemo_u_col, anemo_v_col, easting_col, northing_col - ) - expected_windspeed = np.array([0, 10, 10, np.sqrt(200), np.sqrt(200)]) - expected_winddir = np.array( - [180, 270, 270, 225, 135] - ) # 180 not zero because of the way IEEE 754 handles floating point numbers - windspeed_diff = np.abs(result_df["windspeed"].values - expected_windspeed) - winddir_diff = processing.min_angular_displacement(result_df["winddir"].to_numpy(), expected_winddir) - assert np.all(windspeed_diff < 1e-10), "Wind speed not calculated correctly" - assert np.all(np.array(winddir_diff) < 3), "Wind direction not calculated correctly" diff --git a/tests/test_processing_pipelines.py b/tests/test_processing_pipelines.py deleted file mode 100644 index 54bbd7b..0000000 --- a/tests/test_processing_pipelines.py +++ /dev/null @@ -1,58 +0,0 @@ -from src.gasflux import processing_pipelines -import yaml -from pathlib import Path -import pytest - - -@pytest.fixture -def setup_test_environment(tmp_path): - """Prepare actual test data and configuration with a modified output directory.""" - df_path = Path(__file__).parents[1] / "src" / "gasflux" / "testdata" / "testdata.csv" - config_path = Path(__file__).parents[1] / "src" / "gasflux" / "testdata" / "testconfig.yaml" - - with open(config_path) as f: - config = yaml.safe_load(f) - config["output_dir"] = str(tmp_path) - - temp_config_path = tmp_path / "temp_testconfig.yaml" - with open(temp_config_path, "w") as f: - yaml.safe_dump(config, f) - - return df_path, temp_config_path - - -def test_process_main_config_output(setup_test_environment): - df_path, temp_config_path = setup_test_environment - processing_pipelines.process_main(df_path, temp_config_path) - with open(temp_config_path) as f: - temp_config = yaml.safe_load(f) - output_dir = Path(temp_config["output_dir"]) / df_path.stem - assert output_dir.exists(), "Output directory does not exist." - processing_run_dirs = [d for d in output_dir.iterdir() if d.is_dir()] - assert len(processing_run_dirs) > 0, "No processing run directory found." - processing_run_dir = processing_run_dirs[0] - - with open(temp_config_path) as f: - original_config = yaml.safe_load(f) - - for gas in original_config.get("gases", []): - report_path = processing_run_dir / f"{df_path.stem}_{gas}_report.html" - assert report_path.exists(), f"Report for {gas} does not exist." - - config_dump_path = processing_run_dir / f"{df_path.stem}_config.yaml" - assert config_dump_path.exists(), "Config dump file does not exist." - - def load_and_redump(yaml_file): - with open(yaml_file) as file: - data = yaml.safe_load(file) - redumped_data = yaml.dump(data, sort_keys=True, default_flow_style=False) - return redumped_data - - assert load_and_redump(temp_config_path) == load_and_redump( - config_dump_path - ), "The dumped config does not match the input config." - - -def test_process_main_deterministic_output(setup_test_environment): - df_path, temp_config_path = setup_test_environment - processing_pipelines.process_main(df_path, temp_config_path)
+

GasFlux Web API 控制台

+ +
+

新建处理任务

+
+
+ + +
+
+ + +
+ +
+ +
+ +
+

已生成的报告

+
+ {% for report in reports %} +
+
+ {{ report.split('/')[-1] }} + 任务 ID: {{ report.split('/')[0] }} +
+ 下载 +
+ {% else %} +

暂无已生成的报告。

+ {% endfor %} +
+
+ +
+

API 调用指南 (开发者)

+

健康检查: GET /health

+

上传分析: POST /upload

+

查询任务状态: GET /task/<task_id>

+

参数: file (Excel), config (YAML, 可选)

+

示例: curl -X POST -F "file=@data.xlsx" http://localhost:5000/upload

+

状态查询: curl http://localhost:5000/task/your-task-id

+
+ + +

^0!|>e{$a%q6#KFFt==c|?yQiDMSi z00U&@d7z2iU!SM${vTuq9)nl(0tR#%(h38*#D)PJ+Nlct`8UDT%0a>i= z{`7^jjpi%(x9OM=7;rV4lHFehYFR*YcM}*O)1#QkagpA^VZfMeI14FZIQ9Gh2l;|K z=VL17rp;kM4w5Uv5FjQsH#I;5nbL)|N6OqbqfsqDy(9|W<{;*TrfJv&a)bRYiX+Tb4QR;n*aTF0pC2B8hAfUvKz)Z|6UdUe z@KH@aF$rXy;&NqwT(}2y5f0!W4>uqS>-CGnz-t7-JP|L6F888fCIR5$!(_J7C4aUMIX2n%l23^a3Bmd2s20Obvq0VFGw#zH6mwndz&i7`z87 zZxYBLOBQD)?L^!=sEcp_2YHNvcTlfiz(J6QUa$^2*Jx!ObcqcPNU7XdL8Bbuqwtry zb#T7eM*|LoRTU=$itXB`z&glM0SC2wkPZ}epBBC4fdiQyx<}TBel0M-IBk0(VvZCj zewHF0h!SfzE_)hI1^J55dwTaIy|1SNnMd5>Ed9?$5FnSp-9a_qg8;iE@^>@ywLm@) zAeV4Npr_b;3kCx0g_2QEv~_AtV-na6@VC1A0XrZIg>T@xC_|3k%lG2N59|P09M=<} zV_h3)yR1k;nNTQ_X;u_pM>JsISdjk?Vi(tdT)$BKpcK8J_?>~YQv5ElDSm1U1_fkf z2MDL?q4?hO8^sqAUsL>x$KO!=EES6H1*ka|$UrDQLQ{(j#aGf5zBo#DZLGS20rE6a zVpr16%kzT=!fIpf#Bpbv&@f1lF9|@1qr@~0fgxbGpoF$i56*tIdkGnAUBCJyZ2jmvSD~`O><=H^Lau@>h>=7^_i$i)j z4KP6#!jp7$k+lGSaQ>?9a});qeL0{JWs{~1C%$%>ToX-ra+bnQ-A};bPlL}76(t@ zDV9TktPIi!=$KG!0a=ec35}nM0ifel13;gyWD4pEUFdr7jb1PSIz4G+0Cb6`uJ3o0 zTKh~C8a4(y3hnM;0Qgx410ahvU56-B$iO1i90B%f(RCRp3Wt`nzs!iUKO1Jz=`6zz z!%5tI;wZ9E*dPEfqvQh{YO;?_?WkVOaD3C_0p_PjMWG^ae z-461Z(PSGLGa*?DUXBG#wi$DNlm2A-rcq@1UZiRg6PmsdbUKU2u^y`TN~g0pXH*g= zicZUNxJK}C?#N_U&*JAeNO4tmfF>fPByEA{RYn|fo1 ztowi+g`xCt``6aLBG%RW;4Q-0%7tAOiNH1Vmqj}1cOHagE0^g7{cdp@*K$a($JP63!;vKR6?G6AyrjL6YJOCiy;R_<29B9*eA3FT(5)ANM=dzyCzyf)z zWZvv>SU!K%BmfrVCR7WhT-uy61T2VrqyR+K=X8&P3$hj>-Uw0o`7@OQ7qWQ61;QgU zarXKweuX$*I|S&`;n-j5|LAgkJCN9#$idaXm$f)>_vOEj+QeEYNb7(h&TOTlh}||jsT@u_*YkvYJA{g7wAC5|2UaVcNY*q$$&WgFXap%GQo-^J+V{< z7a`W2(rs!e1g+1v?pmKq-@kzJXnd_xXq5U|@>`t#^`#<@=|MNfp^)Ct!yyOOG;%mk4JfuzW zNxI(}gw@^D9A(;~)JYin^ihS+4PV#0OF0`yw9)i716L(tNo;{2%&fkIH-KtAe&J#lEVA zg1Xztlrvf2D)zj+iHp?2@m0XiuBPQ*|n)34-m-WxIqWeI}xFB zH>+or1YGBv$zq0+GWOl>DC0Yas9%=kN3`=kFo0fj}<&a~RtE z1I`~sGW7$o{X6Fm8jWZ7VDmfY&$D&d{+07b9@?Tq3?Puj57crbdv`P2N=mSZ0|I+G zoRo}U5kgrpvvVP;QCkoj7oHylLZ{0gOF)=%6rzsm*=gP*XfQG!)k~ze$;c>>cNxjI zxw=oqHBdrsf}u?1!8zp;l(6T3JW(5i>Q{fmAF`R*DMc$EK$qC? zfcTH8vk*Lxfm(@xd3FeRpiHqH2mCpC zo8v$hkJtdw@{$jzkIb2YN;j*Qgn6LD0ta3x2?)qCpbY8jlBF_&0J$VeiPBa(`4&eI zAg>RSAa(K*E{z<2c9T|9WqL*j0_-`?N0PGL@vk2mw*uu!76C!~iUb4~g#$r@JOvt! zm&Ch2Y}sNRoNE@aO_Er(w0|is0MP_5We)`8zvHFV>;JJSeb^)jM$iio&r;!U3U*vh|bPbp@@HinTb&in5q8KY?Ko)PgKb|j!;%6BuzEPB< zm;tg_<5;kmli=4v_ap9|BPxLRJS@k0V2O>`?9ymg)+#iw03rAYWyi5UbpdU9%_OH4Di8wAg%>RN6 z@@Y9BPy(}@3S?zQv!w-33NL{S>Cq=lg8X+vIK?E;>pyxb2xid>aL|cJD>byrA$ zPQO7$z)8qWkHTTKO^}FnI3U@ez=5!_fP;N$9iRiXQ^7JaDAR-ddvSXlILI<_6rcr= zeIVc<3*m{TI*I1oluRHS_J27$vm9xX+z4MqnD_owxn}~JdFg%MyAPlP2+)IceSM6G zs-PL&S(VCZ41S>K9v%zL>7+5n(-4;HZn01lL!DF7feG5EK&cZg0D06_BY+t0o(OaoaSWOovY zr(YLXkj23Q7T{H$zyh7~LY`4gs>c_gb72gSR^Pb>g;W8!O1-K?Axqh6sm(ERNN} zd9iH$^6KyxPza*)4a)cUiOs|P)@=PUrJsh;`~3IO*019~sQH)YXn~7(8ztN%cFhmZ z^eZdqIw}&ljnE>2)gdTitL7h0rACNI1@r!3X4_Y6%b92pCxIrr?`stgEB{pbmVTo` z5uH0xSAR#7qJ7`$2)5}{q{8%0L0R~c0!WOk0RoZ#jBGTr#kAH}U(VF$JH5!lEu_rRejCN1 zAd3oUIv=C};J}v9At(fNg31U1Ec6L+V9p%tQ9w~=CM_m5&*@eRRtPZ?k0|8kiVs0dLAZfV zq5~gnIzL3{_z&a5b4`UfA3@JHY9Q?wBoroJ^E8B2aO zXD?O+BxI3DMfB`FU_hyBzyLLan!^GxkVh=tDGqh71O=RYNVmea&L_5k^APWZ%6H|d zwt-y&!##IJANd({RdG}n&i*L&s1FerCN@qw=nO~)ei00u?9VC1$Nf=7S@$`+=NElW zVKI|8cfKg&el=9+h)@BKnqh+}Cz^dSIZd01(mwF;1_Nw`%o`^_=G2kx(&3ED< zt){2@5GWw@GJNhd2P_{ps;^`=(&lef$xwac(a{$4GDTbPiBMVWjVN5qiUTG681+6T&*n=Z(76D}|4e>J4`hB`-Wf zFFA*j3(qhrikil87LX5t{T*^f<4d&fCP)x-$2Iz{vzG!wK3&mw8Qa$_m$h! z>H8}2rtdVJ=g{*?EUMr3=d^Fk3A|ef!8dWNe_^z(-T^Nv{iRARB|PjHfx^o~vDN3` zu67T{J^p}KZ z(|e#Hi${FKOZou~@;Wbg1`EMJMIgI(J(z(5nbF&=VEO9@WcDAfL-~6H2f<5v0uELh zQwImD#1030BQk0Y4flVcM!mv8sXczlVsp!1xMvYpur~o5$V6w7$J^?nB93K898(i` z4L~pjjKoL89dG;FuU7rQEXYIX6+q%e($$(J%z+?}SPPHlu0ItIWKsCr&h!B8PBY+v z42jr#)c4tcBLhL6?WZWToUHW``^YasL7t|I3ATbQid%4=8Gs;P0fa>eO6gou{DxV4 zi7VvdCKV8}_>E@sg9Cwl(2b-?g3i9^`9D=3v z1URgurVbodi5(mWz!4>1e~)S}V=D+zY6Azi02{ygya>6g9;8Z(PAqU z@1Rn}t|!WRz2g>;n+R4?rl!_|HF%&SV5<(?^ngGXTR?!k_$m!FD3mx4m6_gYpa`1yVA#M+Smjgj11co}u|J#11;QA^-th21U>%hEYHuiwD?I z|9I*-z$(b%bP~A@f{Xg?!daGdk^%zuGJ~o`CYo3EgNjBijNs7g^`;fdYOY#YB*a0tLiBR}eutY7m4@)o{ix z$YS&##Q~na$QrW;26O~4*{FoffgG!Ge> z1@b(@l`ko~BSf%^(4eHL`U^OiJZ6MjejtJ_gCKTO))c>izE;BC!nV|}07S^*^miGg zF57pE&NY2oF$?6ghagtCkD!36M)e!W-wOuFUmFYv($W(cuyUL_3|J+044{(c&OaI@ zB3y(wHOByp!_RN*V<13of_jR2iciV-h!oLJy6Z-PiZ89WS`GO|F96<9{-X8ReXAqxwA{UAMIV(g zmQ}6T&@>DFUat_F{h3cB#mNUJ2Dt=?bvx3Krvtek?a|Vi{gyxS5t~3S0-J!Xxd4K% zgs5Co3;q{f#^uw?MHIXgEtBcPZ9qa-b7By2n9YN#h4x9`h+mevr7H93&K8%Su6~Ap z>OIi`Q2+jy{k>)?>E#+gc#d^=U8zltg|JENfIy$O#49Qv-se<@7{wJz3u6)60O3+{ zlSC>2;aUjFI#IBSvaMaL1rf&~&jGV7@(m!!4e^aM2#xHZAfckq_WCN`BQz!2$fL^PVZcsW{z&$jVZs{M}ys@jzCb7c;MLPQKW!ng~6Wreaf-=&TY*_o1 zh}1$xuCU-_Lp<$MX4%(z-N>em9X1~#3K?0}z@Ci!H5b_^dV--~yu&C&o~vDtfFTb7 z7_JmkpWC|tgHa$4NRLV!s2nYx=&?4cYOZqBZlKI?)2{a?TR@lu*XF^41+rkGdeEm2 z^EwEa2QuJ?owDc+i#SWu+3>eQ0)U@2Cg22+RaQR zTkL}pOAwKu7)E~M&L1oX2BGXN-D$FO9$m7fVjvjHk7}T!Xo8AikyDL#L^={V@_lf4 zWv*QbC-}V4(@oFxC2^??k^1B>Lm-fwr1=0=c@9I`Ck+tDfFOJGT=Z$9{Yurux#&eA zQFwhL%*6ITeS;VR#wJv%qOB8!7h)k5*lGwtR|)likVjE?z3SzFXQG?}EWky}X?m;o{iRBUmiwc{ zE#P!Qf>)iESJvIF&EZGOjWfvie5^@3Z1N%(0ft;f{bkgxP=>rj52PG;_R3jo^0sA%_TF_lE)^%3H3m2~sE z#T&KodT9Ebpz*>oB$KUXEZgTxU#d3aNW`%osX8&Qx5gl4tgV#fhKsJ?aCHmBl%cP#=3ggdt3KuIVNsR<6qMC|s^AgAXs%zoj- zkqH#G?T#CDdjxUad`$u@fFR5@!p|7E`;w~Oey1bQ!3b>6Gk(DXy9=5bOL69r zdr*d4X$^pn;vHzP8wW72`7C1F9nc^H9+6y_OD0rDp|a@?nDwQliOm^JlQK)lR0U%o z&lzV0h;Y+=0cenSbs9p0tnxr9jK-e)!xwO+_IWW2WQ_yopx#4;16ff62SGS`0uEMw zQU?dC#1041;qUD?A{}3a^R(45I~SIrh;7b6FG_-0ApEj$4jPhT76=nA;2^Nc*4gV99K-T>rs!tN~zMN_j zWckp0&MV!iKZ}#~16Y+)zka%ufqSV@fhqVZDL3BUTLq?4QC|iu|S&5Yu;*eeW4%AIvNNTvW50^W&x%Qtf6up-LIPRz#-0Ho^Q*bL#LX@)VeICMY zPmV^8UKO%XSrW-JnI}84_-$3t0W#VReSY?&q8(^%f>wF2RD_b#L$ zy}w74V9v)l(;groUpSonv`d=4Q6M0T6Ga<2`Q;0~66 z0RTkoRYBS8WugFpCMT3bzcADQfFl&O766=QcZFm!-PzdqMIMLpgY4^3a4*Y+*27pX zH3W1rc!AK1-r)u6kQ0adrK|Gmiboxe{F#I|lxA%+P-M54dnEY=*U8-0#RaI8HzNM| zWK~^&GJOC5e&*zu(N6#X$ns0_kXMjI>$A)f@;6*%`D(l_5I7X5!uy8T79VLa%q)MLnV`xSzJRKj~Y1Zu(yg&n(9{a7B%s8HkVsMf;^&j)g#LH6rGC@tDNc(6)X)1e7YDt3HpP=M4(EwzQ&(yEzxT2 zQBDB*MGy`w-UI+Z7PuGyQlrfc0Fhs4K20lB03c6)d%2qd^1BM4{x_eKF5ovh z0LX~d7RV)23)M$g6y&-?G%%{#Z7e`i1N*=L27o=1FaUCl!mfd$+cly&+8j190E9Ki z!+dE@%jmyMzZd|yQbI`s5c$OekiP(Xx9Gpn{GeFXN<$*@pe*SrwB$-;dosylQX1;`{S!MA2V^azH@& ze$kM<>tSYZi8}sbgqKKw$S+=iwlukj2OG0;eRaV!Hy~YHzhU-$sbupARL_Ct_P?5@$xR^JgN| zqW7h&m`)VFKWXc!?+U#)y4oe`V~>>GD0UItNvCf622gfoxZUCGjvASZ7z$4EdrtDL z3U13i0y*g*z{>01Z!4##`fcRg*C#1eX9+&);W zcv(lI%fagTxEuerl7PBBe3jUB8G2LSW^9{NbeZ>1_VCO<+H~2&!{~CBT6DPt@f6&tfAcKSUj!;m7qb`nsI>!6p(?Ku&j_O5D~Ew&xn4Ug6Sa(-H&d!+<~N zi1776m>ZqM^Tc>H@gYhX+j=OM0Wj!a$Vr@@kI-mkoJE_|c{mN_mg(;oyryk)!7@Fu zfBmS?=sYLfMEop8qviP?U~e{%f^y4e3Q-GB&(v*$p35`v#yIG?KBKDVvbsjkgT(WM zp0AvrPS00~UC+;Nt;G1p&{kJ!Qv~dzw?@(PEVk)+Z8Cn#QlaNwO#u&`OcbYehfx$g z%fPawIH#2LD2H_+h+*Q=Q)8Ye$A&HU94(9w_0gd7x(w{J^S$u!MCdbORd~iyNC?_s z-KAx@(N*a->HwlMN8$;5=HzBKo#0gN^IkaCOkIa)x;_Tz&wgq}y=BfDrBzA5-RG>n z_$>c^M!rlB#o%5OaCDW}=};<)6)qnY>Ma!_>rq@5=>aIzTW`WnGHm1W;2QM~-p>>2 zz0!m_^(gS4XFS!{E_9}D+RCJXiUq%PE3CR)_HEA1`fXgVpb zxdL`|%Lyk8O+O#|5Zx-j_5dQ2gF<*(7ub{trO#KuGRC##Aja-4%ea$pPR?-BijmUG z5Oy>k-aDl{^(sr|*_G}7?PuRL0ASaMpzS;t1pttb1IwW63k(*>^k4yJyWGmO*zrPp z3vOQap_*2)>u2$Wn^)sY7(ZbTfA7kqF#ZAOESnz&}Soe9d1r`jTfrTux zdg$i%7Y2Y*;!b!Syv-p#IU$^Z1vjbBUtRQ0P$N^Tjg$cfx{lxvqA6-CmBbF1TLV3EiTwKF2u8QB@k6ya6!HfSVvvjG`Jws2QEk=Rdv^A`LGdk za)F0^!G$c25q223>t(?t=^k^Zx9YB!uaV~m!*GX@>c5lq)*KyKU}+2WbHSnKJ+YUhOno@T+jF8_ZIOsV!ui3TL0GeEB-NX+iwH1tB=w#rhH$p@s;vCIKlL>e5q0kXWvA=vWY_J zvsqS@UalK0vWdLS=~dt8^f=O@S?at#+qWi9IFQoU>4-b3?`cay>kpa7XLtO)T>?|( zK&hxM2KR#vfyavxlzGYnERa=&YY>J&&cm}O`;%RRygG;wb2ih@Do=G1X3zD23~PR| z`4?Ph`@ElPvPJuQ*I*fo!lqnP;D((7(t)%McL_y9i)Ep5( zzi$r+x%($&FBkn5rr1K+m);n}r3+L-Iz`8KvlMBV<=6GG5favh&E7E^06Qr^wJN>^!;N;`?i;b8PW>M)os4 zL1&Sp_rg%aYZlJshKKL+hdWhr23dJ_@d)`f4eU|&!V9Ej{NQBo*QYKfd+PohX}FQ_ z(C|zb^2$+I^D+Ppk3_}fJt)eq4;mZ<6Q;Hi$}aPx>_De&188^_VvlEspLYoQO~olM zeXAw5JcF+1zYiGrI=O z%bgY#otZBrGhR*GvmP^F`B_wbmJSUg1h5t1pUc%Q3OQGa5H$m~q zYo#6iXmX+U@Z@uq9(_M-{4(*GUd9JmOWFAKUMU$2$)Q64nI8nWgLaL3-=048x0?Fb zfI*%f45zJk0R#D*mx3H~s{$J!i(m21K%Ac5AGjd1J4F;+$bZKZtE05Z+g}D^*Jz}-@dgK>A)rt!>Ge;&{aG|hzMI>y0>9Sp2{l>V1 zU=np_UVo;A*Z{&#ThLyqk<_wpKb=a5Up@gD}!{1TW3b_t4c(v1D>H#{8J zUC(w|==K?Uw?f=)FvXRd#Ed4Waj<3|?arz0ZF?xf=q#m+_6>2QTOeygqy|-f^_JB+juD)!UnK<@9 z7Dvfo#6C(eUxZ-VMkuMP#G~?X#jrB85=M#U`#P{<@=jW<0Aa+%UD8shJJw?b$Pn&= zSJ$@l!XS_#%Q@hZImXr3Cj+ARAXNvZ<`v4WkN<>$*LYDm63F~2JfZh1O{mlR zRbtosRFwx_&nUDn)T34JVWbtk7edsc_czaa;LM+;Ci}fi6uw(>Yu|p=-Jit?C=-6p z)w}9`FHAFD4SDBt4hQD|xqS+=p#PnE;_cTJvEAb--0z?wfUGF)mkW%J|Gsic<&SFb z%Jr8RJ|#nvv29?C@h=lcWDhsq)DS?|^#)OODSu{L0=h2qFE0M%#c}rn+kKW_$QPOF zV4%*`S!!I3Lqx*j$K0Db=Gpo4j359c z!Q3yeB7jiMi2Ot%|EQ9gIstX#(_Y4{k$|-(9nI^2020}ptnR0*z`R{|;JUV`k29M{M zev5d^=-DLR&;UQ$-hb;fItOecatr{^n?2+&@Rk&+UnxQVrAm$M0i9^6&67;-3MzF+=g^9R%CoC}1OIKRvfB9ew!c7Bn; z+APUF`mLhrk6LtJCqdJR^)(RyozmCzm)_@K-`DYtra#Bsd#s$JX5Zf=c1`DPMfvlB*;a1 zZle>xHm4u~@vwpfnc=h`0oJ9x81{Up`T0a&smS8^FtQw7 zoh0wC07I51znmULDegXE1Ne#{<7Io^EN6g0Rzy8{niM8C!>9y+4hf?~nhn+0h*3}( zf3S|51At!xpPtv|0T}YEeALGrKN&Ow1sa_5N3FX|R@CPYG&tw4qgDV7LjS|+an23k zkVOINLo(V_W(f1^NNZmmf~AVgvf~N;^oLKtjd~zhe8)5 zu+Sw1+f7g@EYo^w`96P`O9S)t@QYakR5)Ys8WmyoUx5lE4_=_^s|@%c{62CWYc{Zc z#T*=7s*AuLFfDt-ar^o!r2%wGcZ_jxP?}&#`qA+pMuZ?FecXNcSourcF=Cb25rKS| zy?57$EWFJrh`>ftj0fTRh5PVGl~9$!c*s(V!=NI9OcaCx4v#Y@7l@$5J-#mxZhwy@ zUqIx8wsVcp*ld1$2SORcuZ(-L`{uv%6-6!L9HJ-z1iJ!wh9SFuh@N zi5(8EnLspz1ED%C@x%*!zyb$ZY&}aH1_nZwLbb{nkF$hK6mZb^wSWU&O9!d%9no&`}C}szZYmQLBN5GHma03g$ z@0()F`3zfV1TugMdsInIkI9}ZM+x~hh-thW4KaWKS;O@Zwb$m50R&|ErBXmn2P?Tl zB7S?h6lXn$2U#T%h7knlv&FH1UcCKEIWe7bfChRKC1p^@e;5sdee?txtYoDQ4OWRA z4Jc^z_PdWV3LZj$TG4<|RzZU-_KF6jaDYs<7zZVzKt$rWaLn~TYcxUmTX$^#yw?|{ z0vd$S2A}YSXlbWafP*{(yihL|Uo;&68?V>e#Xl=<$O=D< z8^KR?#Eu&r%KC0$4 z1;4zeCy-#JL3K#5O6*8LIH~)1KIfTu36k@LT$6Hw$YL83c*O#cAWH=jI9~*Rf=m=S z$&}dHy=?^vl(UB4{@S|#K!Q91p3F%D;l*(e5D1ShG6^XqR8$k~&vIygbh(^&(M0=Y z-YdD!@x8zOhEYiw0*rBD%JGsBb3s=X#m}4m%jmvc;z+lE>Q{6j`hU)A6r%fC{uC_; zvhiP-h$6li3T<6teWkRE8~7FFGd=}27q_CU5lYlM<=g-T`S=E)AdkL4f-LESkiZ7? z3?!`L9bMs7Vuu8h;o9b5uOlSl0GrJqLB(Dn!RZPr6;1`uf&R)S0urP(gr_K~I0SuK zLlTBGA7hcC5f9D>mHO7CmS}|J7FHC{A$(`}rkZjX-`gc9X-Ze-oFiW=mD>;q`80~r z;G>h1eO2^7!rwWyy?-i~xV~Kc4gXw(mg*<-@PdC4xE47e&7u*%s`%nD$O+$ei@&Kj zseevaRDmc`9NiaYPQyRSBjYP-YPJ7q%Ps8#eLmBKzxD4FD)|t7iF}Ob@Zdcn<2vC# z|FPo%&h)*h*nqzDIzsWFlq*uP4G%n|^rcEIPDWF3@F4TR;;`Vy(XUnHGhE1b!VI0E z^NkImS_!3nt1)0=q`}gy$Ju}1sT3d(ITJ}k?b8GVgv}SFGzhjN@7M?gj9_@80`8!b zm@fq;F?^U#^KD*|(XzOm0RaJ#hmyfr%*&9e(CNX6&G&p2S?UtygbOcFqT-MxIr^J_ zP(|)|52!4--?)br{Xvl?O5KI&gF@eh&uY{CRi3+T|N8fjtNT-IMbh;a>wSJAc$L`I zUD}QfG8A4%sJgrH^M#7Ns=NCJRSF6#3{ibhsJqY}&oqzA403}}RA2ay(Kt^~zQz=E zM0|PI%uuL^hgo1uKMLeuq-}}b19+emO7H*%#;)HL599)DRV2$L=i{J8fvqW?65ST+ zdBB&#GS2R)WBa%3pCVns9D_wDd>x*hs6uEyREW7}mp$LPh(~G-Orr!UPExx20_;juOvrKRf@cIQby>0dqR@NZa!T?w_JB zN7@x?x&Yd4#64IL(!9*Rd9+F;f#6lT(n>89iZa0Dbn|ZUORE{;w2+7;Iqt)W@?{on&0i~plGOc2rvr( z&9-Mkyus;2Xtpo|-EL(gw@Hd>=f8h^BM*Ynhh2O3mz9syZRD%OzLCGR9pfK^F1nw; z&S~d(FCMO9^K-nAvLfl%ULLBQMTi2j)6U&vY~;cgTPyM>Bp9i-($OwN?b}8@sJ0N^ z@D-3{CuKj&MxF;Or+X=%X%7!AmkZ#qJ6@&ZU-j^pA&_JqgFd4ww=A6lYH}ay(%IAP zUa8lOGjM;Ro^Mm>Cg|NkKm62p295mwGphX||0jtJ0PNqpIUgLVSz68`xQ zLj(`|sn{zbcu_)?VzZ~5MltO(?}}&uhe!KlfB_|>ao9iR&K!eapIwRE{)W7Q!xjB&^lO^n58~Zb-A^y4%YoSjtO; z^k#Myr+QlwdhI7OlwVkQcr7T&*!lCEDoB3|{UWddvP^^&N^+5kz){9uYIX~1u5XHR zi=3eZOr9ToDcCP4;Xb6|fZS_z zT&GY#IoO*JW(Cx`8CZY=Sw7DAxC^Grh7n*SItsod-K>87EDryD1PNK>!GjAcYvwfo z9rXQCK8B#qetU-Fpo84s03Bq$ay}3h`>s7Fs=!^%|Zuwj*W!v_4ci3Kz|`3w2! z6&joacz?-auh8IK2vq8Vb~$EqJdlZi226(Omkcy0ne0wXzSdU*50sb|=EJ+*J)_YP zUc1nX?(Jv#0v;%>j5^NJyT|Jh*_vRsK5}b>M-4jw4R#xMSUe*qfUX2rfs|9LaA4Eo z464Tfk%^p3>Zs^P4g}~|fH&hjFDnNG=*y$zXijvf5Rk=j8nNl?0`Lro2+n?~$XuQM z`k*L78jj+fdr{nfS^nfbj4%*%KDz^gd-?AlHxPm~^aKd3B%=-lR*4-1QhzZL?M5h| zWU8o6cEMAn&j)XR7JEej`;_qiWhqeL;LapaKqg`{kpyCUuAqP{k6NAN+KoH|GArRH z$`(m@tw#Ry2v`K1t<#8?LC8-TU=Om1GAXgT_~imo{FK_Hqr-v7L9E5QrXA92uwR!F zDBx}{+yOE#_24;+&)qSc2uz;zgWUVHdIt21ps!ypIPctGoj&T5NQDOb-Z&@Z+375Z z|Fj^2ez|0(d4X&!0ewtjLK?TK^(dMMJ#&rx(ZN7iZp4mGoFeD3a|{6?e8K&prB%ZKRQV`G^j`KdhJdUo zsxDE1Jvs44MuDy>xIax%29UunK`L_Y&{K~KCXYFvE0@=hpuFiI0yoWtu@&^a9WgDM z#32>u1w;NQ$e^D#-jK!@0!Y|fh4GPVmwMKKeVPN1(D^Dz$nxeOad?gjxQMq@z)fNY z2^`ORr?Nqw0Fj;nJJ{=yf1zSikdVnZZxV)g_koI#6h>Q;=}GfzTV6C5uaUEFT#8VB znBc`CkZUg%0Vj@3HuhM73|L|X6y2I0Bfi&le1=5;5pZM?=t9`nuJ`N`9R&w%DN%dS zD-Hc**N?IVPrK#i23) zVyWyF#Tg)1Soy!X_=gu3unW*Akz-Ncp+JXFOZ$GyT+y00@S~21SYd4+kN(TMh9Uow z`=8mXLj8>>$cd*Z_5O>>hnJAL6jO9T{qyvT`sZGWrjsB+7Qc~NR1XAtf9L4zPyz?` z3}{B}XI<1^o>L_Z_#EyK)W0|C{~Xuv@GjSHli1Zil@?;R9i0BR5gHJ5KxBUxir%9B zrJ@t+FH*R?>giuaPB=>&%MA559^QpsNx4;?yHz;v z2W&b&-#OXoWf@dpAJru`DVl^tuVo1fdX)fQ2zrjSH8qe>d8NMb_@u^IG4*xffE5vC zZ#txt#y$_fES*sFX`|>y`J-_0-0zE`XK`|w?t?8~$aW_Js!dTdwtNu}27xzHLgz)s zo1^^Yb9i|%`+KAF&oTZE?=t>2iCyP!`0w7RK|`Bf_)P1;zZCvK=Y^=Z>byS}bY7&O zqluS|sGFbb1Rbq$?2HXg3ku4W)QZv!ZfMk>dy6b2HgzYSo^Kjq|5``}T3Wr&gT zL+Eq*#iRam0X|Q1PgSuEl76ru5Q1r}qKS?b3yimRKB5Myd;ow7@h~5`Zn)hlo4zfb zu2wn1V1U7jNlfq7mID@K@$dq2w9zF409l4$090o`;ETwCmL*>22&LC6Vd|Wl z4umRR3IXV!ONSIJe$lB${hwn69^PdIZW6ot-%}s5!-(F$jgT$>P?`|q7f1(#_tQu?>TgTAo-Z{3=<5JTni_BDymJB{ z*@&w;@0`Gsm-9pCvkX2}T%|?lv&bDCgswP!3FyCGl?v5f4U z-y5Y5^3cble20~5)HwS$iCyXGBAyzNjqH5kFRgoi;jFXgXR%f3?`|ux=Zh2>J&qnI zy@h)PGlj8@?nOaqSPVaD!#O$o1}O zSA!06b7q5p4x&TT_gFxj|B$PeMP zvh-+c=r1q8tL(Gs=RXs{Ev|q0d!zlq6M91XS6Wf0{j0>T{S-rJ+mqc7N}meiA0fNC z=_}1EwEyPGu4unVE%ru<{D}w^n()JxO6E$7#1ydAR@?%UHxY!+m51$eUpIeW%75+OLbk9)V*s4Wpv{S)RBU zCpJ?QRNfYTc+h!y`XteQd7sg^NV=DlC_oku&mRZN&i2pZ7$b!bESJBLn?x_x9ndNa z*yX9!-gFz$o4Zy5fWEOx0Fb{oFc3_jCtzSD4s|fFO6)K|2zICVjBEg%0DMw)L!s1knHGDg+wZ{#ux^Z64*-&N>_ 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 HI05_*RsPr* diff --git a/examples/basic_usage/data/08_51_15_间隔高度10m.xlsx b/examples/basic_usage/data/08_51_15_间隔高度10m.xlsx deleted file mode 100644 index b71b27315f0474d268aeff36dc339169106e04b7..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 179889 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