comet-hunter 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- comet_hunter-0.1.0/LICENSE +21 -0
- comet_hunter-0.1.0/PKG-INFO +159 -0
- comet_hunter-0.1.0/README.md +133 -0
- comet_hunter-0.1.0/backend/__init__.py +0 -0
- comet_hunter-0.1.0/backend/api/__init__.py +0 -0
- comet_hunter-0.1.0/backend/api/dependencies.py +77 -0
- comet_hunter-0.1.0/backend/api/dto/__init__.py +0 -0
- comet_hunter-0.1.0/backend/api/dto/api_response.py +17 -0
- comet_hunter-0.1.0/backend/api/dto/response_models.py +65 -0
- comet_hunter-0.1.0/backend/api/dto/serializers.py +31 -0
- comet_hunter-0.1.0/backend/api/middleware.py +44 -0
- comet_hunter-0.1.0/backend/api/routes/__init__.py +0 -0
- comet_hunter-0.1.0/backend/api/routes/frames.py +104 -0
- comet_hunter-0.1.0/backend/api/routes/health.py +53 -0
- comet_hunter-0.1.0/backend/api/routes/jobs.py +274 -0
- comet_hunter-0.1.0/backend/api/routes/scheduler.py +52 -0
- comet_hunter-0.1.0/backend/api/routes/slots.py +97 -0
- comet_hunter-0.1.0/backend/config.py +22 -0
- comet_hunter-0.1.0/backend/core/__init__.py +0 -0
- comet_hunter-0.1.0/backend/core/logging_config.py +31 -0
- comet_hunter-0.1.0/backend/core/request_context.py +9 -0
- comet_hunter-0.1.0/backend/core/storage.py +15 -0
- comet_hunter-0.1.0/backend/database/__init__.py +0 -0
- comet_hunter-0.1.0/backend/database/domain/__init__.py +0 -0
- comet_hunter-0.1.0/backend/database/domain/downlink_slot.py +77 -0
- comet_hunter-0.1.0/backend/database/domain/file_metadata.py +77 -0
- comet_hunter-0.1.0/backend/database/domain/processed_file.py +238 -0
- comet_hunter-0.1.0/backend/database/infrastructure/__init__.py +0 -0
- comet_hunter-0.1.0/backend/database/infrastructure/base.py +60 -0
- comet_hunter-0.1.0/backend/database/infrastructure/bootstrap.py +16 -0
- comet_hunter-0.1.0/backend/database/infrastructure/exceptions.py +14 -0
- comet_hunter-0.1.0/backend/database/infrastructure/query_executor.py +84 -0
- comet_hunter-0.1.0/backend/database/infrastructure/query_result.py +18 -0
- comet_hunter-0.1.0/backend/database/infrastructure/query_spec.py +35 -0
- comet_hunter-0.1.0/backend/database/repositories/__init__.py +0 -0
- comet_hunter-0.1.0/backend/database/repositories/downlink_slot_repository.py +465 -0
- comet_hunter-0.1.0/backend/database/repositories/file_metadata_repository.py +463 -0
- comet_hunter-0.1.0/backend/database/repositories/processed_file_repository.py +563 -0
- comet_hunter-0.1.0/backend/jobs/__init__.py +0 -0
- comet_hunter-0.1.0/backend/jobs/background_job_service.py +81 -0
- comet_hunter-0.1.0/backend/jobs/event_bus.py +109 -0
- comet_hunter-0.1.0/backend/jobs/event_models.py +42 -0
- comet_hunter-0.1.0/backend/jobs/exceptions.py +4 -0
- comet_hunter-0.1.0/backend/jobs/job.py +18 -0
- comet_hunter-0.1.0/backend/jobs/job_store.py +148 -0
- comet_hunter-0.1.0/backend/jobs/job_submission.py +7 -0
- comet_hunter-0.1.0/backend/jobs/runner.py +102 -0
- comet_hunter-0.1.0/backend/main.py +80 -0
- comet_hunter-0.1.0/backend/pipeline/__init__.py +0 -0
- comet_hunter-0.1.0/backend/pipeline/models.py +49 -0
- comet_hunter-0.1.0/backend/pipeline/pipeline.py +353 -0
- comet_hunter-0.1.0/backend/pipeline/scheduler.py +98 -0
- comet_hunter-0.1.0/backend/services/__init__.py +0 -0
- comet_hunter-0.1.0/backend/services/download_file_service.py +382 -0
- comet_hunter-0.1.0/backend/services/metadata_service.py +397 -0
- comet_hunter-0.1.0/backend/services/process_file_service.py +511 -0
- comet_hunter-0.1.0/backend/services/slot_service.py +300 -0
- comet_hunter-0.1.0/backend/util/__init__.py +0 -0
- comet_hunter-0.1.0/backend/util/constants.py +73 -0
- comet_hunter-0.1.0/backend/util/enums.py +115 -0
- comet_hunter-0.1.0/backend/util/funcs.py +27 -0
- comet_hunter-0.1.0/comet_hunter/__init__.py +1 -0
- comet_hunter-0.1.0/comet_hunter/cli.py +133 -0
- comet_hunter-0.1.0/comet_hunter/launcher.py +34 -0
- comet_hunter-0.1.0/comet_hunter/process_store.py +35 -0
- comet_hunter-0.1.0/comet_hunter.egg-info/PKG-INFO +159 -0
- comet_hunter-0.1.0/comet_hunter.egg-info/SOURCES.txt +101 -0
- comet_hunter-0.1.0/comet_hunter.egg-info/dependency_links.txt +1 -0
- comet_hunter-0.1.0/comet_hunter.egg-info/entry_points.txt +2 -0
- comet_hunter-0.1.0/comet_hunter.egg-info/requires.txt +12 -0
- comet_hunter-0.1.0/comet_hunter.egg-info/top_level.txt +3 -0
- comet_hunter-0.1.0/frontend/__init__.py +0 -0
- comet_hunter-0.1.0/frontend/app.py +90 -0
- comet_hunter-0.1.0/frontend/components/__init__.py +0 -0
- comet_hunter-0.1.0/frontend/components/active_slot_panel.py +47 -0
- comet_hunter-0.1.0/frontend/components/event_stream_panel.py +143 -0
- comet_hunter-0.1.0/frontend/components/footer.py +11 -0
- comet_hunter-0.1.0/frontend/components/get_frames_panel.py +315 -0
- comet_hunter-0.1.0/frontend/components/health_panel.py +29 -0
- comet_hunter-0.1.0/frontend/components/image_panel.py +137 -0
- comet_hunter-0.1.0/frontend/components/navbar.py +15 -0
- comet_hunter-0.1.0/frontend/components/scheduler_panel.py +220 -0
- comet_hunter-0.1.0/frontend/components/sync_frames_panel.py +437 -0
- comet_hunter-0.1.0/frontend/components/sync_slots_panel.py +123 -0
- comet_hunter-0.1.0/frontend/components/utc_clock.py +18 -0
- comet_hunter-0.1.0/frontend/config.py +20 -0
- comet_hunter-0.1.0/frontend/events/__init__.py +0 -0
- comet_hunter-0.1.0/frontend/events/sse.py +129 -0
- comet_hunter-0.1.0/frontend/models/__init__.py +0 -0
- comet_hunter-0.1.0/frontend/models/frame.py +8 -0
- comet_hunter-0.1.0/frontend/models/instruments.py +5 -0
- comet_hunter-0.1.0/frontend/models/job.py +16 -0
- comet_hunter-0.1.0/frontend/models/processed_file.py +8 -0
- comet_hunter-0.1.0/frontend/models/slot.py +6 -0
- comet_hunter-0.1.0/frontend/services/__init__.py +0 -0
- comet_hunter-0.1.0/frontend/services/api.py +279 -0
- comet_hunter-0.1.0/frontend/state/__init__.py +0 -0
- comet_hunter-0.1.0/frontend/state/event_store.py +33 -0
- comet_hunter-0.1.0/frontend/state/frame_store.py +86 -0
- comet_hunter-0.1.0/frontend/styles/__init__.py +0 -0
- comet_hunter-0.1.0/frontend/styles/theme.py +78 -0
- comet_hunter-0.1.0/pyproject.toml +50 -0
- comet_hunter-0.1.0/setup.cfg +4 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anand Krishna
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: comet-hunter
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Astronomical image ingestion and processing system for Sungrazer project
|
|
5
|
+
Author: Anand Krishna
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/AnandKri/comet-hunter
|
|
8
|
+
Project-URL: Documentation, https://anandkri.github.io/comet-hunter/
|
|
9
|
+
Keywords: astronomy,comet,system-design,processing-pipeline
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: fastapi>=0.136
|
|
14
|
+
Requires-Dist: nicegui>=3.12
|
|
15
|
+
Requires-Dist: requests>=2.34
|
|
16
|
+
Requires-Dist: numpy>=2.4
|
|
17
|
+
Requires-Dist: scipy>=1.17
|
|
18
|
+
Requires-Dist: sunpy>=7.1
|
|
19
|
+
Requires-Dist: pydantic>=2.13
|
|
20
|
+
Requires-Dist: matplotlib>=3.10
|
|
21
|
+
Requires-Dist: starlette>=1.2
|
|
22
|
+
Requires-Dist: reproject>=0.19
|
|
23
|
+
Requires-Dist: mpl_animators>=1.2
|
|
24
|
+
Requires-Dist: tzdata>=2026.1
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Comet Hunter
|
|
28
|
+
|
|
29
|
+
Comet Hunter is an automated astronomical image ingestion and processing system designed to assist in the discovery of sungrazing comets from SOHO LASCO imagery.
|
|
30
|
+
|
|
31
|
+
## About The Project
|
|
32
|
+
|
|
33
|
+
NASA's [Sungrazer Project](https://sungrazer.nrl.navy.mil/) enables the discovery and reporting of comets visible from the SOHO and STEREO satellites. To date, over five thousand comets have been discovered using the SOHO satellite. On board SOHO is the LASCO coronagraph, which consists of two telescopes — C2 and C3. Images from these telescopes are primarily used for reporting new comets.
|
|
34
|
+
|
|
35
|
+
### Why This Exists?
|
|
36
|
+
|
|
37
|
+
For comet discovery, users rely on fragmented tools for downloading, processing, and reviewing imagery. There is no unified platform that automates the complete workflow from raw image availability to chronological playback of processed frames. **Comet Hunter aims to bridge this gap**.
|
|
38
|
+
|
|
39
|
+
### Present Challenges
|
|
40
|
+
- RAW images must be processed before becoming usable
|
|
41
|
+
- Sungrazer comets are often indistinguishable in single frames
|
|
42
|
+
- Chronological playback significantly improves detectability
|
|
43
|
+
- Most comets are reported within minutes of data availability.
|
|
44
|
+
- **Time is critical.**
|
|
45
|
+
|
|
46
|
+
The problem is not merely detection - it is **rapid** detection.
|
|
47
|
+
|
|
48
|
+
This requires a **robust automation** of the complete workflow: from RAW image ingestion to chronological playback of processed frames.
|
|
49
|
+
|
|
50
|
+
## Current Capabilities
|
|
51
|
+
|
|
52
|
+
- Downlink slot synchronization
|
|
53
|
+
- Metadata ingestion from LASCO sources
|
|
54
|
+
- Parallel RAW image downloading
|
|
55
|
+
- Image processing pipelines for C2/C3
|
|
56
|
+
- Time-indexed frame retrieval
|
|
57
|
+
- REST API backend
|
|
58
|
+
- Scheduler-driven ingestion workflows
|
|
59
|
+
- Interactive frontend visualization
|
|
60
|
+
|
|
61
|
+
<h2>User Interface</h2>
|
|
62
|
+
|
|
63
|
+
<p align="center">
|
|
64
|
+
<img src="docs/images/ui-dashboard.png" width="900">
|
|
65
|
+
</p>
|
|
66
|
+
|
|
67
|
+
## Getting Started
|
|
68
|
+
|
|
69
|
+
### End User Installation
|
|
70
|
+
|
|
71
|
+
#### Install Comet Hunter directly from PyPI:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
pip install comet-hunter
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
#### End User Commands
|
|
78
|
+
|
|
79
|
+
Start the application
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
comet-hunter start
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Check application status
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
comet-hunter status
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Stop the application
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
comet-hunter stop
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
#### Note
|
|
98
|
+
|
|
99
|
+
When started, the application will be available at:
|
|
100
|
+
|
|
101
|
+
`http://localhost:8080`
|
|
102
|
+
|
|
103
|
+
Application data, logs, and database files are stored in:
|
|
104
|
+
|
|
105
|
+
```
|
|
106
|
+
Windows:
|
|
107
|
+
C:\Users\<username>\.comet_hunter
|
|
108
|
+
|
|
109
|
+
Linux/macOS:
|
|
110
|
+
~/.comet_hunter
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Development Setup
|
|
114
|
+
|
|
115
|
+
#### Clone Repository
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
git clone https://github.com/AnandKri/comet-hunter.git
|
|
119
|
+
cd comet-hunter
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
#### Create Virtual Environment
|
|
123
|
+
|
|
124
|
+
Linux/macOS
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
python -m venv .venv
|
|
128
|
+
source .venv/bin/activate
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Windows
|
|
132
|
+
```bash
|
|
133
|
+
python -m venv .venv
|
|
134
|
+
.venv\Scripts\activate
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
#### Install Dependencies
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
pip install -r requirements.txt
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### Run Backend
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
uvicorn backend.main:app --reload
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### Run Frontend
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
python frontend/app.py
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Documentation
|
|
156
|
+
|
|
157
|
+
<a href="https://anandkri.github.io/comet-hunter/" target="_blank">
|
|
158
|
+
View full documentation here
|
|
159
|
+
</a>
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Comet Hunter
|
|
2
|
+
|
|
3
|
+
Comet Hunter is an automated astronomical image ingestion and processing system designed to assist in the discovery of sungrazing comets from SOHO LASCO imagery.
|
|
4
|
+
|
|
5
|
+
## About The Project
|
|
6
|
+
|
|
7
|
+
NASA's [Sungrazer Project](https://sungrazer.nrl.navy.mil/) enables the discovery and reporting of comets visible from the SOHO and STEREO satellites. To date, over five thousand comets have been discovered using the SOHO satellite. On board SOHO is the LASCO coronagraph, which consists of two telescopes — C2 and C3. Images from these telescopes are primarily used for reporting new comets.
|
|
8
|
+
|
|
9
|
+
### Why This Exists?
|
|
10
|
+
|
|
11
|
+
For comet discovery, users rely on fragmented tools for downloading, processing, and reviewing imagery. There is no unified platform that automates the complete workflow from raw image availability to chronological playback of processed frames. **Comet Hunter aims to bridge this gap**.
|
|
12
|
+
|
|
13
|
+
### Present Challenges
|
|
14
|
+
- RAW images must be processed before becoming usable
|
|
15
|
+
- Sungrazer comets are often indistinguishable in single frames
|
|
16
|
+
- Chronological playback significantly improves detectability
|
|
17
|
+
- Most comets are reported within minutes of data availability.
|
|
18
|
+
- **Time is critical.**
|
|
19
|
+
|
|
20
|
+
The problem is not merely detection - it is **rapid** detection.
|
|
21
|
+
|
|
22
|
+
This requires a **robust automation** of the complete workflow: from RAW image ingestion to chronological playback of processed frames.
|
|
23
|
+
|
|
24
|
+
## Current Capabilities
|
|
25
|
+
|
|
26
|
+
- Downlink slot synchronization
|
|
27
|
+
- Metadata ingestion from LASCO sources
|
|
28
|
+
- Parallel RAW image downloading
|
|
29
|
+
- Image processing pipelines for C2/C3
|
|
30
|
+
- Time-indexed frame retrieval
|
|
31
|
+
- REST API backend
|
|
32
|
+
- Scheduler-driven ingestion workflows
|
|
33
|
+
- Interactive frontend visualization
|
|
34
|
+
|
|
35
|
+
<h2>User Interface</h2>
|
|
36
|
+
|
|
37
|
+
<p align="center">
|
|
38
|
+
<img src="docs/images/ui-dashboard.png" width="900">
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
## Getting Started
|
|
42
|
+
|
|
43
|
+
### End User Installation
|
|
44
|
+
|
|
45
|
+
#### Install Comet Hunter directly from PyPI:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install comet-hunter
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
#### End User Commands
|
|
52
|
+
|
|
53
|
+
Start the application
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
comet-hunter start
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Check application status
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
comet-hunter status
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Stop the application
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
comet-hunter stop
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
#### Note
|
|
72
|
+
|
|
73
|
+
When started, the application will be available at:
|
|
74
|
+
|
|
75
|
+
`http://localhost:8080`
|
|
76
|
+
|
|
77
|
+
Application data, logs, and database files are stored in:
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Windows:
|
|
81
|
+
C:\Users\<username>\.comet_hunter
|
|
82
|
+
|
|
83
|
+
Linux/macOS:
|
|
84
|
+
~/.comet_hunter
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Development Setup
|
|
88
|
+
|
|
89
|
+
#### Clone Repository
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git clone https://github.com/AnandKri/comet-hunter.git
|
|
93
|
+
cd comet-hunter
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Create Virtual Environment
|
|
97
|
+
|
|
98
|
+
Linux/macOS
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
python -m venv .venv
|
|
102
|
+
source .venv/bin/activate
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Windows
|
|
106
|
+
```bash
|
|
107
|
+
python -m venv .venv
|
|
108
|
+
.venv\Scripts\activate
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### Install Dependencies
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
pip install -r requirements.txt
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
#### Run Backend
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
uvicorn backend.main:app --reload
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### Run Frontend
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python frontend/app.py
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Documentation
|
|
130
|
+
|
|
131
|
+
<a href="https://anandkri.github.io/comet-hunter/" target="_blank">
|
|
132
|
+
View full documentation here
|
|
133
|
+
</a>
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from backend.database.infrastructure.query_executor import QueryExecutor
|
|
3
|
+
from backend.database.repositories.file_metadata_repository import FileMetadataRepository
|
|
4
|
+
from backend.database.repositories.processed_file_repository import ProcessedFileRepository
|
|
5
|
+
from backend.database.repositories.downlink_slot_repository import DownlinkSlotRepository
|
|
6
|
+
from backend.services.metadata_service import MetadataService
|
|
7
|
+
from backend.services.download_file_service import DownloadFileService
|
|
8
|
+
from backend.services.process_file_service import ProcessFileService
|
|
9
|
+
from backend.services.slot_service import SlotService
|
|
10
|
+
from backend.pipeline.pipeline import Pipeline
|
|
11
|
+
from backend.pipeline.scheduler import Scheduler
|
|
12
|
+
from backend.jobs.job_store import job_store, JobStore
|
|
13
|
+
from backend.jobs.background_job_service import BackgroundJobService
|
|
14
|
+
from backend.jobs.event_bus import event_bus, EventBus
|
|
15
|
+
from backend.core.storage import RAW_DIR, PROCESSED_DIR
|
|
16
|
+
|
|
17
|
+
background_job_service = BackgroundJobService(job_store)
|
|
18
|
+
|
|
19
|
+
executor = QueryExecutor()
|
|
20
|
+
|
|
21
|
+
metadata_repo = FileMetadataRepository(executor)
|
|
22
|
+
processed_repo = ProcessedFileRepository(executor)
|
|
23
|
+
slot_repo = DownlinkSlotRepository(executor)
|
|
24
|
+
|
|
25
|
+
metadata_service = MetadataService(metadata_repo)
|
|
26
|
+
slot_service = SlotService(slot_repo)
|
|
27
|
+
download_service = DownloadFileService(
|
|
28
|
+
processed_repository=processed_repo,
|
|
29
|
+
metadata_service=metadata_service,
|
|
30
|
+
download_directory=RAW_DIR,
|
|
31
|
+
)
|
|
32
|
+
process_service = ProcessFileService(
|
|
33
|
+
processed_repository=processed_repo,
|
|
34
|
+
metadata_service=metadata_service,
|
|
35
|
+
processed_directory=PROCESSED_DIR,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
pipeline = Pipeline(
|
|
39
|
+
slot_service=slot_service,
|
|
40
|
+
metadata_service=metadata_service,
|
|
41
|
+
download_service=download_service,
|
|
42
|
+
process_service=process_service,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
scheduler = Scheduler(pipeline)
|
|
46
|
+
|
|
47
|
+
def get_pipeline() -> Pipeline:
|
|
48
|
+
"""
|
|
49
|
+
Returns shared pipeline instance for handling API requests and background jobs.
|
|
50
|
+
"""
|
|
51
|
+
return pipeline
|
|
52
|
+
|
|
53
|
+
def get_scheduler() -> Scheduler:
|
|
54
|
+
"""
|
|
55
|
+
Returns shared scheduler instance for triggering background jobs.
|
|
56
|
+
"""
|
|
57
|
+
return scheduler
|
|
58
|
+
|
|
59
|
+
def get_job_store() -> JobStore:
|
|
60
|
+
"""
|
|
61
|
+
Returns a shared job_store instance.
|
|
62
|
+
"""
|
|
63
|
+
return job_store
|
|
64
|
+
|
|
65
|
+
def get_job_service() -> BackgroundJobService:
|
|
66
|
+
"""
|
|
67
|
+
Returns shared background job service instance.
|
|
68
|
+
Used for submitting and managing background jobs.
|
|
69
|
+
"""
|
|
70
|
+
return background_job_service
|
|
71
|
+
|
|
72
|
+
def get_event_bus() -> EventBus:
|
|
73
|
+
"""
|
|
74
|
+
Returns shared event bus instance.
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
return event_bus
|
|
File without changes
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pydantic import BaseModel
|
|
2
|
+
from pydantic.generics import GenericModel
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
T = TypeVar('T')
|
|
6
|
+
|
|
7
|
+
class ApiSuccessResponse(GenericModel, Generic[T]):
|
|
8
|
+
success: bool = True
|
|
9
|
+
data: T
|
|
10
|
+
|
|
11
|
+
class ApiErrorDetail(BaseModel):
|
|
12
|
+
code: str
|
|
13
|
+
message: str
|
|
14
|
+
|
|
15
|
+
class ApiErrorResponse(BaseModel):
|
|
16
|
+
success: bool = False
|
|
17
|
+
error: ApiErrorDetail
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from backend.util.enums import Instrument, JobType, JobStatus
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
from datetime import datetime, timedelta
|
|
4
|
+
from typing import Optional, Any
|
|
5
|
+
|
|
6
|
+
class ProcessedFileResponse(BaseModel):
|
|
7
|
+
processed_file_name: str
|
|
8
|
+
instrument: Instrument
|
|
9
|
+
processed_file_url: str
|
|
10
|
+
datetime_of_observation: datetime
|
|
11
|
+
|
|
12
|
+
class GetFramesResponse(BaseModel):
|
|
13
|
+
files: list[ProcessedFileResponse]
|
|
14
|
+
total: int
|
|
15
|
+
limit: int
|
|
16
|
+
offset: int
|
|
17
|
+
|
|
18
|
+
class SyncFramesResponse(BaseModel):
|
|
19
|
+
metadata_synced: int
|
|
20
|
+
downloaded: int
|
|
21
|
+
marked_ready: int
|
|
22
|
+
processed: int
|
|
23
|
+
|
|
24
|
+
class SyncSlotsResponse(BaseModel):
|
|
25
|
+
status: str
|
|
26
|
+
slots_synced: int
|
|
27
|
+
|
|
28
|
+
class SchedulerStatusResponse(BaseModel):
|
|
29
|
+
running: bool
|
|
30
|
+
next_run_at: Optional[datetime]
|
|
31
|
+
next_run_in: Optional[timedelta]
|
|
32
|
+
|
|
33
|
+
class SchedulerStartResponse(BaseModel):
|
|
34
|
+
started: bool
|
|
35
|
+
running: bool
|
|
36
|
+
|
|
37
|
+
class SchedulerStopResponse(BaseModel):
|
|
38
|
+
stopped: bool
|
|
39
|
+
running: bool
|
|
40
|
+
|
|
41
|
+
class HealthResponse(BaseModel):
|
|
42
|
+
status: str
|
|
43
|
+
database: bool
|
|
44
|
+
scheduler_initialized: bool
|
|
45
|
+
pipeline_initialized: bool
|
|
46
|
+
|
|
47
|
+
class SlotResponse(BaseModel):
|
|
48
|
+
start: Optional[datetime]
|
|
49
|
+
end: Optional[datetime]
|
|
50
|
+
|
|
51
|
+
class JobQueuedResponse(BaseModel):
|
|
52
|
+
job_id: str
|
|
53
|
+
existing: bool
|
|
54
|
+
status: str
|
|
55
|
+
|
|
56
|
+
class JobStatusResponse(BaseModel):
|
|
57
|
+
job_id: str
|
|
58
|
+
type: JobType
|
|
59
|
+
status: JobStatus
|
|
60
|
+
created_at: datetime
|
|
61
|
+
started_at: Optional[datetime] = None
|
|
62
|
+
stopped_at: Optional[datetime] = None
|
|
63
|
+
progress: Optional[Any] = None
|
|
64
|
+
result: Optional[Any] = None
|
|
65
|
+
error: Optional[str] = None
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from backend.api.dto.response_models import GetFramesResponse, ProcessedFileResponse
|
|
2
|
+
from backend.database.domain.processed_file import ProcessedFile
|
|
3
|
+
from backend.pipeline.models import GetProcessedFramesResult
|
|
4
|
+
|
|
5
|
+
def serialize_processed_file(
|
|
6
|
+
file: ProcessedFile
|
|
7
|
+
) -> ProcessedFileResponse:
|
|
8
|
+
"""
|
|
9
|
+
Converts ProcessedFile domain model to ProcessedFileResponse DTO
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
return ProcessedFileResponse(
|
|
13
|
+
processed_file_name=file.processed_file_name,
|
|
14
|
+
instrument=file.instrument,
|
|
15
|
+
processed_file_url=f"/media/{str(file.processed_file_name)}",
|
|
16
|
+
datetime_of_observation=file.datetime_of_observation
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
def serialize_get_frames_response(result: GetProcessedFramesResult) -> GetFramesResponse:
|
|
20
|
+
"""
|
|
21
|
+
Converts GetProcessedFramesResult pipeline result domain model to GetFramesResponse DTO
|
|
22
|
+
"""
|
|
23
|
+
return GetFramesResponse(
|
|
24
|
+
files=[
|
|
25
|
+
serialize_processed_file(file)
|
|
26
|
+
for file in result.processed_files
|
|
27
|
+
],
|
|
28
|
+
total=result.total,
|
|
29
|
+
limit=result.limit,
|
|
30
|
+
offset=result.offset
|
|
31
|
+
)
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
import uuid
|
|
4
|
+
from starlette.middleware.base import BaseHTTPMiddleware
|
|
5
|
+
from backend.core.request_context import set_request_id
|
|
6
|
+
|
|
7
|
+
logger = logging.getLogger("api")
|
|
8
|
+
|
|
9
|
+
class LoggingMiddleware(BaseHTTPMiddleware):
|
|
10
|
+
|
|
11
|
+
async def dispatch(self, request, call_next):
|
|
12
|
+
request_id = str(uuid.uuid4())
|
|
13
|
+
set_request_id(request_id)
|
|
14
|
+
|
|
15
|
+
start = time.time()
|
|
16
|
+
|
|
17
|
+
try:
|
|
18
|
+
response = await call_next(request)
|
|
19
|
+
duration = time.time() - start
|
|
20
|
+
|
|
21
|
+
logger.info(
|
|
22
|
+
"Request completed",
|
|
23
|
+
extra={
|
|
24
|
+
"request_id": request_id,
|
|
25
|
+
"method": request.method,
|
|
26
|
+
"path": request.url.path,
|
|
27
|
+
"status": response.status_code,
|
|
28
|
+
"duration_ms": round(duration * 1000, 2)
|
|
29
|
+
}
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
return response
|
|
33
|
+
|
|
34
|
+
except Exception:
|
|
35
|
+
logger.exception(
|
|
36
|
+
"Request failed",
|
|
37
|
+
extra={
|
|
38
|
+
"request_id": request_id,
|
|
39
|
+
"method": request.method,
|
|
40
|
+
"path": request.url.path
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
raise
|
|
File without changes
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from fastapi import APIRouter, Query, Depends
|
|
2
|
+
from backend.api.dependencies import get_pipeline, get_job_service
|
|
3
|
+
from backend.util.enums import Instrument, JobType
|
|
4
|
+
from backend.api.dto.api_response import ApiSuccessResponse
|
|
5
|
+
from backend.api.dto.response_models import JobQueuedResponse, GetFramesResponse
|
|
6
|
+
from backend.pipeline.pipeline import Pipeline
|
|
7
|
+
from backend.jobs.background_job_service import BackgroundJobService
|
|
8
|
+
from backend.api.dto.serializers import serialize_get_frames_response
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
router = APIRouter()
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
@router.get("/", response_model=ApiSuccessResponse[GetFramesResponse])
|
|
15
|
+
def get_processed_frames(
|
|
16
|
+
instrument: Instrument = Query(...),
|
|
17
|
+
start: str = Query(..., description = "ISO UTC observation start time"),
|
|
18
|
+
end: str = Query(..., description = "ISO UTC observation end time"),
|
|
19
|
+
limit: int = Query(15, ge=1, le=100),
|
|
20
|
+
offset: int = Query(0, ge=0),
|
|
21
|
+
pipeline: Pipeline = Depends(get_pipeline)
|
|
22
|
+
) -> ApiSuccessResponse[GetFramesResponse]:
|
|
23
|
+
"""
|
|
24
|
+
Retrieve of processed frames for a given observation window and instrument
|
|
25
|
+
"""
|
|
26
|
+
logger.info(
|
|
27
|
+
"Processed frames retrieval requested",
|
|
28
|
+
extra={
|
|
29
|
+
"instrument":instrument,
|
|
30
|
+
"observation_start_utc":start,
|
|
31
|
+
"observation_end_utc":end
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
try:
|
|
35
|
+
|
|
36
|
+
result = pipeline.get_processed_frames(
|
|
37
|
+
instrument,
|
|
38
|
+
start,
|
|
39
|
+
end,
|
|
40
|
+
limit,
|
|
41
|
+
offset
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
logger.info("Processed frames retrieval successful")
|
|
45
|
+
|
|
46
|
+
return ApiSuccessResponse[GetFramesResponse](
|
|
47
|
+
data=serialize_get_frames_response(result)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
except Exception:
|
|
51
|
+
logger.exception("Processed frames retrieval failed")
|
|
52
|
+
raise
|
|
53
|
+
|
|
54
|
+
@router.post("/sync", response_model=ApiSuccessResponse[JobQueuedResponse])
|
|
55
|
+
def sync_processed_frames(
|
|
56
|
+
instrument: Instrument = Query(...),
|
|
57
|
+
start: str = Query(..., description = "ISO UTC observation start time"),
|
|
58
|
+
end: str = Query(..., description = "ISO UTC observation end time"),
|
|
59
|
+
pipeline: Pipeline = Depends(get_pipeline),
|
|
60
|
+
background_job_service: BackgroundJobService = Depends(get_job_service)
|
|
61
|
+
) -> ApiSuccessResponse[JobQueuedResponse]:
|
|
62
|
+
"""
|
|
63
|
+
Trigger the job for syncing and updating processed frames based on
|
|
64
|
+
instrument and observation time
|
|
65
|
+
|
|
66
|
+
- Triggers metadata sync
|
|
67
|
+
- Downloads missing files
|
|
68
|
+
- Processes eligible frames
|
|
69
|
+
- Returns details of the sync operation including counts of metadata synced, files downloaded, frames marked ready and frames processed
|
|
70
|
+
"""
|
|
71
|
+
logger.info(
|
|
72
|
+
"Processed frames sync job requested",
|
|
73
|
+
extra={
|
|
74
|
+
"instrument":instrument,
|
|
75
|
+
"observation_start_utc":start,
|
|
76
|
+
"observation_end_utc":end
|
|
77
|
+
}
|
|
78
|
+
)
|
|
79
|
+
try:
|
|
80
|
+
job = background_job_service.submit(
|
|
81
|
+
JobType.SYNC_PROCESSED_FRAMES,
|
|
82
|
+
pipeline.sync_processed_frames,
|
|
83
|
+
instrument,
|
|
84
|
+
start,
|
|
85
|
+
end
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
if job.existing:
|
|
89
|
+
logger.warning("Processed frames sync job already running",
|
|
90
|
+
extra={"job_id": job.job.id})
|
|
91
|
+
else:
|
|
92
|
+
logger.info("Processed frames sync job queued",
|
|
93
|
+
extra={"job_id": job.job.id})
|
|
94
|
+
|
|
95
|
+
return ApiSuccessResponse[JobQueuedResponse](
|
|
96
|
+
data=JobQueuedResponse(
|
|
97
|
+
job_id=job.job.id,
|
|
98
|
+
existing=job.existing,
|
|
99
|
+
status=job.job.status.value
|
|
100
|
+
)
|
|
101
|
+
)
|
|
102
|
+
except Exception:
|
|
103
|
+
logger.exception("Processed frames sync failed to start")
|
|
104
|
+
raise
|