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.
Files changed (103) hide show
  1. comet_hunter-0.1.0/LICENSE +21 -0
  2. comet_hunter-0.1.0/PKG-INFO +159 -0
  3. comet_hunter-0.1.0/README.md +133 -0
  4. comet_hunter-0.1.0/backend/__init__.py +0 -0
  5. comet_hunter-0.1.0/backend/api/__init__.py +0 -0
  6. comet_hunter-0.1.0/backend/api/dependencies.py +77 -0
  7. comet_hunter-0.1.0/backend/api/dto/__init__.py +0 -0
  8. comet_hunter-0.1.0/backend/api/dto/api_response.py +17 -0
  9. comet_hunter-0.1.0/backend/api/dto/response_models.py +65 -0
  10. comet_hunter-0.1.0/backend/api/dto/serializers.py +31 -0
  11. comet_hunter-0.1.0/backend/api/middleware.py +44 -0
  12. comet_hunter-0.1.0/backend/api/routes/__init__.py +0 -0
  13. comet_hunter-0.1.0/backend/api/routes/frames.py +104 -0
  14. comet_hunter-0.1.0/backend/api/routes/health.py +53 -0
  15. comet_hunter-0.1.0/backend/api/routes/jobs.py +274 -0
  16. comet_hunter-0.1.0/backend/api/routes/scheduler.py +52 -0
  17. comet_hunter-0.1.0/backend/api/routes/slots.py +97 -0
  18. comet_hunter-0.1.0/backend/config.py +22 -0
  19. comet_hunter-0.1.0/backend/core/__init__.py +0 -0
  20. comet_hunter-0.1.0/backend/core/logging_config.py +31 -0
  21. comet_hunter-0.1.0/backend/core/request_context.py +9 -0
  22. comet_hunter-0.1.0/backend/core/storage.py +15 -0
  23. comet_hunter-0.1.0/backend/database/__init__.py +0 -0
  24. comet_hunter-0.1.0/backend/database/domain/__init__.py +0 -0
  25. comet_hunter-0.1.0/backend/database/domain/downlink_slot.py +77 -0
  26. comet_hunter-0.1.0/backend/database/domain/file_metadata.py +77 -0
  27. comet_hunter-0.1.0/backend/database/domain/processed_file.py +238 -0
  28. comet_hunter-0.1.0/backend/database/infrastructure/__init__.py +0 -0
  29. comet_hunter-0.1.0/backend/database/infrastructure/base.py +60 -0
  30. comet_hunter-0.1.0/backend/database/infrastructure/bootstrap.py +16 -0
  31. comet_hunter-0.1.0/backend/database/infrastructure/exceptions.py +14 -0
  32. comet_hunter-0.1.0/backend/database/infrastructure/query_executor.py +84 -0
  33. comet_hunter-0.1.0/backend/database/infrastructure/query_result.py +18 -0
  34. comet_hunter-0.1.0/backend/database/infrastructure/query_spec.py +35 -0
  35. comet_hunter-0.1.0/backend/database/repositories/__init__.py +0 -0
  36. comet_hunter-0.1.0/backend/database/repositories/downlink_slot_repository.py +465 -0
  37. comet_hunter-0.1.0/backend/database/repositories/file_metadata_repository.py +463 -0
  38. comet_hunter-0.1.0/backend/database/repositories/processed_file_repository.py +563 -0
  39. comet_hunter-0.1.0/backend/jobs/__init__.py +0 -0
  40. comet_hunter-0.1.0/backend/jobs/background_job_service.py +81 -0
  41. comet_hunter-0.1.0/backend/jobs/event_bus.py +109 -0
  42. comet_hunter-0.1.0/backend/jobs/event_models.py +42 -0
  43. comet_hunter-0.1.0/backend/jobs/exceptions.py +4 -0
  44. comet_hunter-0.1.0/backend/jobs/job.py +18 -0
  45. comet_hunter-0.1.0/backend/jobs/job_store.py +148 -0
  46. comet_hunter-0.1.0/backend/jobs/job_submission.py +7 -0
  47. comet_hunter-0.1.0/backend/jobs/runner.py +102 -0
  48. comet_hunter-0.1.0/backend/main.py +80 -0
  49. comet_hunter-0.1.0/backend/pipeline/__init__.py +0 -0
  50. comet_hunter-0.1.0/backend/pipeline/models.py +49 -0
  51. comet_hunter-0.1.0/backend/pipeline/pipeline.py +353 -0
  52. comet_hunter-0.1.0/backend/pipeline/scheduler.py +98 -0
  53. comet_hunter-0.1.0/backend/services/__init__.py +0 -0
  54. comet_hunter-0.1.0/backend/services/download_file_service.py +382 -0
  55. comet_hunter-0.1.0/backend/services/metadata_service.py +397 -0
  56. comet_hunter-0.1.0/backend/services/process_file_service.py +511 -0
  57. comet_hunter-0.1.0/backend/services/slot_service.py +300 -0
  58. comet_hunter-0.1.0/backend/util/__init__.py +0 -0
  59. comet_hunter-0.1.0/backend/util/constants.py +73 -0
  60. comet_hunter-0.1.0/backend/util/enums.py +115 -0
  61. comet_hunter-0.1.0/backend/util/funcs.py +27 -0
  62. comet_hunter-0.1.0/comet_hunter/__init__.py +1 -0
  63. comet_hunter-0.1.0/comet_hunter/cli.py +133 -0
  64. comet_hunter-0.1.0/comet_hunter/launcher.py +34 -0
  65. comet_hunter-0.1.0/comet_hunter/process_store.py +35 -0
  66. comet_hunter-0.1.0/comet_hunter.egg-info/PKG-INFO +159 -0
  67. comet_hunter-0.1.0/comet_hunter.egg-info/SOURCES.txt +101 -0
  68. comet_hunter-0.1.0/comet_hunter.egg-info/dependency_links.txt +1 -0
  69. comet_hunter-0.1.0/comet_hunter.egg-info/entry_points.txt +2 -0
  70. comet_hunter-0.1.0/comet_hunter.egg-info/requires.txt +12 -0
  71. comet_hunter-0.1.0/comet_hunter.egg-info/top_level.txt +3 -0
  72. comet_hunter-0.1.0/frontend/__init__.py +0 -0
  73. comet_hunter-0.1.0/frontend/app.py +90 -0
  74. comet_hunter-0.1.0/frontend/components/__init__.py +0 -0
  75. comet_hunter-0.1.0/frontend/components/active_slot_panel.py +47 -0
  76. comet_hunter-0.1.0/frontend/components/event_stream_panel.py +143 -0
  77. comet_hunter-0.1.0/frontend/components/footer.py +11 -0
  78. comet_hunter-0.1.0/frontend/components/get_frames_panel.py +315 -0
  79. comet_hunter-0.1.0/frontend/components/health_panel.py +29 -0
  80. comet_hunter-0.1.0/frontend/components/image_panel.py +137 -0
  81. comet_hunter-0.1.0/frontend/components/navbar.py +15 -0
  82. comet_hunter-0.1.0/frontend/components/scheduler_panel.py +220 -0
  83. comet_hunter-0.1.0/frontend/components/sync_frames_panel.py +437 -0
  84. comet_hunter-0.1.0/frontend/components/sync_slots_panel.py +123 -0
  85. comet_hunter-0.1.0/frontend/components/utc_clock.py +18 -0
  86. comet_hunter-0.1.0/frontend/config.py +20 -0
  87. comet_hunter-0.1.0/frontend/events/__init__.py +0 -0
  88. comet_hunter-0.1.0/frontend/events/sse.py +129 -0
  89. comet_hunter-0.1.0/frontend/models/__init__.py +0 -0
  90. comet_hunter-0.1.0/frontend/models/frame.py +8 -0
  91. comet_hunter-0.1.0/frontend/models/instruments.py +5 -0
  92. comet_hunter-0.1.0/frontend/models/job.py +16 -0
  93. comet_hunter-0.1.0/frontend/models/processed_file.py +8 -0
  94. comet_hunter-0.1.0/frontend/models/slot.py +6 -0
  95. comet_hunter-0.1.0/frontend/services/__init__.py +0 -0
  96. comet_hunter-0.1.0/frontend/services/api.py +279 -0
  97. comet_hunter-0.1.0/frontend/state/__init__.py +0 -0
  98. comet_hunter-0.1.0/frontend/state/event_store.py +33 -0
  99. comet_hunter-0.1.0/frontend/state/frame_store.py +86 -0
  100. comet_hunter-0.1.0/frontend/styles/__init__.py +0 -0
  101. comet_hunter-0.1.0/frontend/styles/theme.py +78 -0
  102. comet_hunter-0.1.0/pyproject.toml +50 -0
  103. 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