fix: improve Space-Track data ingestion pipeline - #14
Conversation
|
CodeAnt AI is reviewing your PR. |
📝 WalkthroughWalkthroughThe Space-Track ingestion pipeline now uses retryable requests, bulk duplicate-safe MongoDB upserts, structured sync statuses, updated API/task consumers, and comprehensive mocked ingestion tests. ChangesSpace-Track ingestion
Estimated code review effort: 4 (Complex) | ~60 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
| import pytest | ||
|
|
||
| from app.services.spacetrack import SpaceTrackService, SYNC_GROUPS | ||
|
|
There was a problem hiding this comment.
Wrong import path — module does not exist
The test imports from app.services.spacetrack import SpaceTrackService, SYNC_GROUPS, but no file at that path exists in the repo. The actual module lives at orbital/spacetrack.py, which is imported everywhere else as from orbital.spacetrack import ... (see celery_tasks.py and catalog.py). There is no app/services/ directory at all (verified: backend/app/ contains only core/, tasks/, main.py, and __init__.py). As written, the entire test file will fail at collection time with ModuleNotFoundError — none of the 8 tests can actually run.
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/tests/test_ingestion.py
Line: 22
Comment:
**Wrong import path — module does not exist**
The test imports `from app.services.spacetrack import SpaceTrackService, SYNC_GROUPS`, but no file at that path exists in the repo. The actual module lives at `orbital/spacetrack.py`, which is imported everywhere else as `from orbital.spacetrack import ...` (see `celery_tasks.py` and `catalog.py`). There is no `app/services/` directory at all (verified: `backend/app/` contains only `core/`, `tasks/`, `main.py`, and `__init__.py`). As written, the entire test file will fail at collection time with `ModuleNotFoundError` — none of the 8 tests can actually run.
How can I resolve this? If you propose a fix, please make it concise.| ops = [ | ||
| UpdateOne({"noradId": d["noradId"]}, {"$set": d}, upsert=True) | ||
| for d in docs |
There was a problem hiding this comment.
createdAt overwritten on every sync
UpdateOne(filter, {"$set": d}, upsert=True) passes the entire document dict — including createdAt — to $set. On a new insert this is fine, but on subsequent syncs of the same NORAD ID, $set overwrites createdAt with the current timestamp, so the field no longer records when a satellite was first persisted. Use $setOnInsert for createdAt and restrict $set to the mutable fields so existing documents keep their original creation time.
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/orbital/spacetrack.py
Line: 346-348
Comment:
**`createdAt` overwritten on every sync**
`UpdateOne(filter, {"$set": d}, upsert=True)` passes the entire document dict — including `createdAt` — to `$set`. On a **new** insert this is fine, but on subsequent syncs of the same NORAD ID, `$set` overwrites `createdAt` with the current timestamp, so the field no longer records when a satellite was first persisted. Use `$setOnInsert` for `createdAt` and restrict `$set` to the mutable fields so existing documents keep their original creation time.
How can I resolve this? If you propose a fix, please make it concise.| success=True, | ||
| message=f"Synced group '{group}': {count} objects upserted", | ||
| data={"group": group, "upserted": count}, | ||
| success=status["failed"] == 0, |
There was a problem hiding this comment.
success=True when the DB is unreachable
When sync_group cannot reach MongoDB it returns {"failed": 0, "upserted": 0, "errors": ["db_unreachable"]}. The condition status["failed"] == 0 evaluates to True because failed is 0, so the endpoint responds with {"success": true} despite the sync never running. Any monitoring that relies on the success flag to detect infrastructure issues will silently miss DB connectivity failures.
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/api/v1/endpoints/catalog.py
Line: 163
Comment:
**`success=True` when the DB is unreachable**
When `sync_group` cannot reach MongoDB it returns `{"failed": 0, "upserted": 0, "errors": ["db_unreachable"]}`. The condition `status["failed"] == 0` evaluates to `True` because `failed` is `0`, so the endpoint responds with `{"success": true}` despite the sync never running. Any monitoring that relies on the `success` flag to detect infrastructure issues will silently miss DB connectivity failures.
How can I resolve this? If you propose a fix, please make it concise.| def _retry(max_attempts: int, base_delay: float, max_delay: float, retry_on): | ||
| """Decorator: retry the wrapped callable with exponential backoff.""" | ||
| def decorator(fn): | ||
| def wrapper(*args, **kwargs): | ||
| last_exc: Optional[BaseException] = None | ||
| for attempt in range(1, max_attempts + 1): | ||
| try: | ||
| return fn(*args, **kwargs) | ||
| except retry_on as exc: # type: ignore[misc] | ||
| last_exc = exc | ||
| if attempt >= max_attempts: | ||
| break | ||
| delay = min(base_delay * (2 ** (attempt - 1)), max_delay) | ||
| logger.warning( | ||
| f"[SpaceTrack] Retry {attempt}/{max_attempts} for " | ||
| f"{fn.__name__} after {delay:.1f}s — {exc}" | ||
| ) | ||
| time.sleep(delay) | ||
| logger.error( | ||
| f"[SpaceTrack] {fn.__name__} failed after {max_attempts} " | ||
| f"attempts: {last_exc}" | ||
| ) | ||
| raise last_exc # type: ignore[misc] | ||
| return wrapper | ||
| return decorator |
There was a problem hiding this comment.
_retry decorator missing functools.wraps
Without @functools.wraps(fn) on the wrapper function, wrapper.__name__ will be "wrapper" rather than the decorated function's name. Log messages inside _retry use fn.__name__ directly (captured via closure) so those are fine, but introspection on the decorated methods — e.g. in stack traces or Celery's task registry — will show wrapper instead of the real name.
Prompt To Fix With AI
This is a comment left during a code review.
Path: backend/orbital/spacetrack.py
Line: 54-78
Comment:
**`_retry` decorator missing `functools.wraps`**
Without `@functools.wraps(fn)` on the `wrapper` function, `wrapper.__name__` will be `"wrapper"` rather than the decorated function's name. Log messages inside `_retry` use `fn.__name__` directly (captured via closure) so those are fine, but introspection on the decorated methods — e.g. in stack traces or Celery's task registry — will show `wrapper` instead of the real name.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| OperationFailure, | ||
| ) | ||
|
|
||
| from app.database.session import MongoSession |
There was a problem hiding this comment.
Suggestion: The import path points to app.database.session, but this package does not exist in this repository (backend/database/session.py is the actual module). Importing this file will raise ModuleNotFoundError at runtime and break all Space-Track endpoints/tasks. Use the existing database.session module path instead. [import error]
Severity Level: Critical 🚨
- ❌ Space-Track service import fails, breaking catalog sync endpoint.
- ❌ Celery Space-Track sync task crashes on module import.Steps of Reproduction ✅
1. Load the Celery tasks module at `backend/app/tasks/celery_tasks.py:1-4`, which imports
`spacetrack_service` via `from orbital.spacetrack import spacetrack_service` (verified by
BulkRead and Grep).
2. Python evaluates `backend/orbital/spacetrack.py`, reaching line 40 `from
app.database.session import MongoSession` (seen in BulkRead of spacetrack.py lines 1-60).
3. The repository has `backend/database/session.py` defining `class MongoSession` at line
305 and is imported elsewhere as `from database.session import get_db, MongoSession` (e.g.
`backend/api/v1/endpoints/catalog.py:16`), but there is no
`backend/app/database/session.py` or `app/database` package (confirmed via LS of
`backend/app` and `backend/database`).
4. At runtime, importing `app.database.session` fails with `ModuleNotFoundError`,
preventing `orbital.spacetrack` from loading and breaking all Space-Track-dependent code
paths (catalog endpoints and Celery task `sync_spacetrack_data` in
`backend/app/tasks/celery_tasks.py:10-24`).(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** backend/orbital/spacetrack.py
**Line:** 40:40
**Comment:**
*Import Error: The import path points to `app.database.session`, but this package does not exist in this repository (`backend/database/session.py` is the actual module). Importing this file will raise `ModuleNotFoundError` at runtime and break all Space-Track endpoints/tasks. Use the existing `database.session` module path instead.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
|
||
| import pytest | ||
|
|
||
| from app.services.spacetrack import SpaceTrackService, SYNC_GROUPS |
There was a problem hiding this comment.
Suggestion: This test imports SpaceTrackService from app.services.spacetrack, but the implementation in this PR lives in orbital.spacetrack and there is no app/services/spacetrack.py. The test module will fail to import before any assertions run. Update the import to the actual module path used by production code. [import error]
Severity Level: Major ⚠️
- ⚠️ Ingestion tests fail to import Space-Track service module.
- ⚠️ CI cannot validate Space-Track ingestion regression protections.Steps of Reproduction ✅
1. Open the ingestion tests module `backend/tests/test_ingestion.py`, which imports
`SpaceTrackService` at line 21 using `from app.services.spacetrack import
SpaceTrackService, SYNC_GROUPS` (verified via BulkRead of the file).
2. The actual implementation of `SpaceTrackService` is defined in
`backend/orbital/spacetrack.py:116` (found by Grep for `SpaceTrackService`), and
production code imports it as `from orbital.spacetrack import spacetrack_service,
SYNC_GROUPS` in `backend/api/v1/endpoints/catalog.py:18` and
`backend/app/tasks/celery_tasks.py:3`.
3. LS of `backend/app` shows only `core`, `tasks`, `main.py`, and `__init__.py`; LS of
`backend/services` shows `risk_service.py`, `simulation_service.py`, and
`weather_service.py`, with no `spacetrack.py` and no `app/services` subpackage under
`backend/app`, so the module `app.services.spacetrack` does not exist in this repository.
4. Running `python -m pytest backend/tests/test_ingestion.py -v` attempts to import the
test module, leading Python to resolve `app.services.spacetrack`; since that module is
missing, import fails with `ModuleNotFoundError`, and none of the ingestion pipeline
assertions (e.g. `test_pipeline_upserts_and_dashboard_reflects_count` at lines 176-195)
are executed until the import path is corrected (e.g. to `from orbital.spacetrack import
SpaceTrackService, SYNC_GROUPS`).(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** backend/tests/test_ingestion.py
**Line:** 21:21
**Comment:**
*Import Error: This test imports `SpaceTrackService` from `app.services.spacetrack`, but the implementation in this PR lives in `orbital.spacetrack` and there is no `app/services/spacetrack.py`. The test module will fail to import before any assertions run. Update the import to the actual module path used by production code.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix|
CodeAnt AI finished reviewing your PR. |
There was a problem hiding this comment.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
backend/orbital/spacetrack.py (2)
379-470: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win
db_unreachable/no_recordsearly returns reportfailed: 0, so a fully-failed sync looks like a clean success at the aggregate level.When
_ensure_db_connectionfails orfetch_group_jsonreturns no records (e.g. missingSPACETRACK_USERNAME/SPACETRACK_PASSWORD, exactly as shown in this PR's own screenshot — "0 upserted, 0 failed, 0 fetched"),sync_groupreturnsfailed: 0.sync_all_groupsthen sums these intototal_failed, so a complete pipeline failure across every group producestotal_failed == 0. Downstream,celery_tasks.py'ssync_spacetrack_data()branches onstatus["total_failed"] > 0, so it will log "Space-Track sync succeeded — upserted=0." even though nothing was ingested at all. This directly contradicts the PR's stated "no silent failures" objective and issue#10's goal of surfacing ingestion problems clearly.🩹 Suggested fix: make "nothing synced" register as a failure
if not self._ensure_db_connection(db): - return {"group": group, "fetched": 0, "parsed": 0, - "upserted": 0, "failed": 0, "errors": ["db_unreachable"]} + return {"group": group, "fetched": 0, "parsed": 0, + "upserted": 0, "failed": 1, "errors": ["db_unreachable"]} logger.info(f"[SpaceTrack] Upsert start: group '{group}'.") records = self.fetch_group_json(group, limit=limit or 500) if not records: logger.warning(f"[SpaceTrack] Upsert aborted for '{group}': 0 records fetched.") - return {"group": group, "fetched": 0, "parsed": 0, - "upserted": 0, "failed": 0, "errors": ["no_records"]} + return {"group": group, "fetched": 0, "parsed": 0, + "upserted": 0, "failed": 1, "errors": ["no_records"]}Alternatively, add an explicit
ok/successboolean to the status dict that all callers (API endpoint, Celery task) check instead of inferring success fromfailed == 0.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/orbital/spacetrack.py` around lines 379 - 470, Update sync_group so db_unreachable and no_records outcomes report a nonzero failed count, ensuring sync_all_groups aggregates these as failures and downstream callers do not treat an empty or unreachable sync as successful. Preserve the existing error identifiers and status structure, and ensure the failure count reflects that the requested group was not synced.
245-256: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick winReject non-numeric catalog numbers here
catalog_numberis passed straight from/catalog/objects/{catalog_number}into the Space-Track path with no digit-only guard. Add a numeric check before building the URL.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/orbital/spacetrack.py` around lines 245 - 256, Update fetch_by_catalog_json to validate catalog_number contains only digits before constructing the Space-Track URL or sending a request; return None immediately for non-numeric values while preserving the existing authentication and valid-request flow.
🧹 Nitpick comments (2)
backend/tests/test_ingestion.py (1)
212-228: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winNo coverage for the
db_unreachable/no_recordsstatus paths.All new tests exercise the happy path only; none assert on
status["errors"]when_ensure_db_connectionfails orfetch_group_jsonreturns empty. Givenfailedstays0in both cases (persync_group's early returns), a test assertingerrorsis non-empty there would have caught thesuccess/logging gaps flagged incatalog.pyandcelery_tasks.py.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/tests/test_ingestion.py` around lines 212 - 228, The ingestion tests only cover successful synchronization and omit status errors for database-unreachable and empty-record cases. Extend the tests around test_all_groups_sync_returns_meaningful_status and the existing service helpers to simulate _ensure_db_connection failure and fetch_group_json returning no records, then assert status["errors"] is non-empty while preserving the expected failed count of zero.backend/orbital/spacetrack.py (1)
166-166: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winRecurring blind
except Exception(Ruff BLE001, 6 occurrences).
authenticate(),fetch_by_catalog_json(),_ensure_db_connection(), and bothsync_group()handlers each catch bareException. This is flagged by Ruff at all six sites and, combined with logging that only prints{exc}(noexc_info/traceback), means genuine programming bugs (e.g. anAttributeErrorfrom a typo) get silently reclassified as ordinary operational failures ("auth failed", "db unreachable", "upsert failure") — somewhat at odds with this PR's "no silent failures" goal. Where feasible, narrow the caught types to the ones actually expected (network/auth/parsing errors) or at least log withexc_info=Trueso unexpected bugs are distinguishable from expected transient failures in the logs.Also applies to: 254-254, 328-328, 415-415, 436-436, 483-483
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@backend/orbital/spacetrack.py` at line 166, Replace the broad Exception handlers in authenticate(), fetch_by_catalog_json(), _ensure_db_connection(), and both sync_group() handlers with the specific expected network, authentication, parsing, or database exception types; where broad handling remains necessary, pass exc_info=True to the associated logger so unexpected programming errors retain their traceback and are not silently classified as operational failures.Source: Linters/SAST tools
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@backend/api/v1/endpoints/catalog.py`:
- Around line 161-168: Update the success calculation in the catalog endpoint
around sync_group to require both zero failed records and no entries in
status["errors"]. Preserve the existing response message and data payload while
ensuring early-return errors such as db_unreachable and no_records cannot report
success.
In `@backend/app/tasks/celery_tasks.py`:
- Around line 15-24: The Celery task’s success check relies on
`status["total_failed"]`, but `sync_all_groups` does not count failures
represented only in each group’s `errors`. Update `sync_group` and the
aggregation in `sync_all_groups` so `db_unreachable`/`no_records` early returns
contribute to the failure total, ensuring fully failed syncs reach the warning
branch in the task logger.
In `@backend/orbital/spacetrack.py`:
- Around line 472-485: Update sync_by_catalog so _bulk_upsert failures are
propagated to callers instead of being logged and followed by find_one. Preserve
the existing None result for records absent from Space-Track, and use the
function’s return contract to distinguish successful upserts from upsert errors,
such as re-raising the exception or returning an explicit document/error result.
- Around line 321-330: Update _ensure_db_connection to retry transient MongoDB
ping failures using the module’s existing exponential-backoff retry mechanism,
matching _bulk_upsert. Preserve the boolean contract and ensure the final
exhausted failure returns False, or adjust sync_group to catch the propagated
exception so group synchronization still reports db_unreachable.
- Around line 191-214: Update _send_request to handle HTTP 401/403 responses
separately from the _retry-wrapped transient request flow: reset authentication
and re-login immediately, then retry the request without consuming the general
retry attempts. Preserve retry handling for other httpx.RequestError and
HTTPStatusError cases, and keep existing response validation behavior.
- Around line 338-373: Update failed ID extraction in _bulk_upsert to read the
failed NORAD ID from each write error’s op.q.noradId, with appropriate safe
access as needed, instead of e["key"]. Preserve the existing failed_ids logging
and return behavior.
- Around line 54-79: The retry implementation in _retry uses blocking
time.sleep, which stalls the asyncio event loop when sync_all_groups is invoked
by job_sync_spacetrack. Replace the blocking retry path with an async-compatible
approach, or ensure sync_all_groups and its decorated calls run in a worker
thread/process, while preserving the existing exponential backoff and retry
behavior.
---
Outside diff comments:
In `@backend/orbital/spacetrack.py`:
- Around line 379-470: Update sync_group so db_unreachable and no_records
outcomes report a nonzero failed count, ensuring sync_all_groups aggregates
these as failures and downstream callers do not treat an empty or unreachable
sync as successful. Preserve the existing error identifiers and status
structure, and ensure the failure count reflects that the requested group was
not synced.
- Around line 245-256: Update fetch_by_catalog_json to validate catalog_number
contains only digits before constructing the Space-Track URL or sending a
request; return None immediately for non-numeric values while preserving the
existing authentication and valid-request flow.
---
Nitpick comments:
In `@backend/orbital/spacetrack.py`:
- Line 166: Replace the broad Exception handlers in authenticate(),
fetch_by_catalog_json(), _ensure_db_connection(), and both sync_group() handlers
with the specific expected network, authentication, parsing, or database
exception types; where broad handling remains necessary, pass exc_info=True to
the associated logger so unexpected programming errors retain their traceback
and are not silently classified as operational failures.
In `@backend/tests/test_ingestion.py`:
- Around line 212-228: The ingestion tests only cover successful synchronization
and omit status errors for database-unreachable and empty-record cases. Extend
the tests around test_all_groups_sync_returns_meaningful_status and the existing
service helpers to simulate _ensure_db_connection failure and fetch_group_json
returning no records, then assert status["errors"] is non-empty while preserving
the expected failed count of zero.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: d3461d33-463e-4988-8776-0da4661354cc
📒 Files selected for processing (4)
backend/api/v1/endpoints/catalog.pybackend/app/tasks/celery_tasks.pybackend/orbital/spacetrack.pybackend/tests/test_ingestion.py
| status = spacetrack_service.sync_group(db, group, type_override, limit=limit) | ||
| return APIResponse( | ||
| success=True, | ||
| message=f"Synced group '{group}': {count} objects upserted", | ||
| data={"group": group, "upserted": count}, | ||
| success=status["failed"] == 0, | ||
| message=( | ||
| f"Synced group '{group}': {status['upserted']} upserted, " | ||
| f"{status['failed']} failed" | ||
| ), | ||
| data=status, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
success ignores status["errors"] — silently reports success on total failure.
sync_group's early-return paths (db_unreachable, no_records) set failed: 0 while populating errors. Since success here is computed as status["failed"] == 0, a DB-unreachable or zero-records sync reports success=True with a "0 upserted, 0 failed" message — exactly the silent-failure scenario issue #10 asks to prevent.
🐛 Proposed fix
status = spacetrack_service.sync_group(db, group, type_override, limit=limit)
return APIResponse(
- success=status["failed"] == 0,
+ success=status["failed"] == 0 and not status["errors"],
message=(
f"Synced group '{group}': {status['upserted']} upserted, "
- f"{status['failed']} failed"
+ f"{status['failed']} failed"
+ + (f" — errors: {status['errors']}" if status["errors"] else "")
),
data=status,
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| status = spacetrack_service.sync_group(db, group, type_override, limit=limit) | |
| return APIResponse( | |
| success=True, | |
| message=f"Synced group '{group}': {count} objects upserted", | |
| data={"group": group, "upserted": count}, | |
| success=status["failed"] == 0, | |
| message=( | |
| f"Synced group '{group}': {status['upserted']} upserted, " | |
| f"{status['failed']} failed" | |
| ), | |
| data=status, | |
| status = spacetrack_service.sync_group(db, group, type_override, limit=limit) | |
| return APIResponse( | |
| success=status["failed"] == 0 and not status["errors"], | |
| message=( | |
| f"Synced group '{group}': {status['upserted']} upserted, " | |
| f"{status['failed']} failed" | |
| (f" — errors: {status['errors']}" if status["errors"] else "") | |
| ), | |
| data=status, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/api/v1/endpoints/catalog.py` around lines 161 - 168, Update the
success calculation in the catalog endpoint around sync_group to require both
zero failed records and no entries in status["errors"]. Preserve the existing
response message and data payload while ensuring early-return errors such as
db_unreachable and no_records cannot report success.
| status = spacetrack_service.sync_all_groups(db, limit_per_group=500) | ||
| if status["total_failed"] > 0: | ||
| logger.warning( | ||
| f"Space-Track sync completed with issues — " | ||
| f"upserted={status['total_upserted']}, failed={status['total_failed']}." | ||
| ) | ||
| else: | ||
| logger.info( | ||
| f"Space-Track sync succeeded — upserted={status['total_upserted']}." | ||
| ) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
total_failed under-reports failures for the same reason as catalog.py.
sync_all_groups sums each group's failed, but sync_group's db_unreachable/no_records early returns have failed: 0 with only errors populated. A fully-failed sync (e.g., Mongo unreachable for every group) logs at info level ("Space-Track sync succeeded") instead of warning, masking the failure in logs that this PR is meant to make visible.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/app/tasks/celery_tasks.py` around lines 15 - 24, The Celery task’s
success check relies on `status["total_failed"]`, but `sync_all_groups` does not
count failures represented only in each group’s `errors`. Update `sync_group`
and the aggregation in `sync_all_groups` so `db_unreachable`/`no_records` early
returns contribute to the failure total, ensuring fully failed syncs reach the
warning branch in the task logger.
| def _retry(max_attempts: int, base_delay: float, max_delay: float, retry_on): | ||
| """Decorator: retry the wrapped callable with exponential backoff.""" | ||
| def decorator(fn): | ||
| def wrapper(*args, **kwargs): | ||
| last_exc: Optional[BaseException] = None | ||
| for attempt in range(1, max_attempts + 1): | ||
| try: | ||
| return fn(*args, **kwargs) | ||
| except retry_on as exc: # type: ignore[misc] | ||
| last_exc = exc | ||
| if attempt >= max_attempts: | ||
| break | ||
| delay = min(base_delay * (2 ** (attempt - 1)), max_delay) | ||
| logger.warning( | ||
| f"[SpaceTrack] Retry {attempt}/{max_attempts} for " | ||
| f"{fn.__name__} after {delay:.1f}s — {exc}" | ||
| ) | ||
| time.sleep(delay) | ||
| logger.error( | ||
| f"[SpaceTrack] {fn.__name__} failed after {max_attempts} " | ||
| f"attempts: {last_exc}" | ||
| ) | ||
| raise last_exc # type: ignore[misc] | ||
| return wrapper | ||
| return decorator | ||
|
|
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the target file and find its callers.
ast-grep outline backend/orbital/spacetrack.py --view expanded || true
printf '\n--- symbols/usages ---\n'
rg -n "sync_group|sync_all_groups|_retry\(" backend -S || true
printf '\n--- scheduler / async entrypoints ---\n'
rg -n "AsyncIOScheduler|scheduler|FastAPI|`@app`\.(get|post|put|delete)|async def|await .*sync_group|await .*sync_all_groups" backend -S || trueRepository: 7-Blocks/Kepler
Length of output: 5934
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the implementation around the retry decorator and the sync entrypoints.
file=backend/orbital/spacetrack.py
wc -l "$file"
sed -n '1,220p' "$file" | cat -nRepository: 7-Blocks/Kepler
Length of output: 10337
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Look for task scheduling / worker setup that would determine whether blocking sleep can hit the event loop.
rg -n "celery|rq|dramatiq|apscheduler|AsyncIOScheduler|ThreadPoolExecutor|ProcessPoolExecutor|run_in_executor|to_thread|background task|BackgroundTasks|asyncio\.create_task" . -S || trueRepository: 7-Blocks/Kepler
Length of output: 2619
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Focus on the scheduling and call sites for the SpaceTrack sync entrypoints.
rg -n "sync_group|sync_all_groups" . -S
printf '\n---\n'
rg -n "AsyncIOScheduler|BackgroundScheduler|ThreadPoolExecutor|run_in_executor|to_thread|celery|BackgroundTasks|async def" backend . -SRepository: 7-Blocks/Kepler
Length of output: 5711
🏁 Script executed:
#!/bin/bash
set -euo pipefail
sed -n '1,220p' backend/app/core/scheduler.py | cat -n
printf '\n--- catalog endpoint ---\n'
sed -n '130,220p' backend/api/v1/endpoints/catalog.py | cat -n
printf '\n--- celery task ---\n'
sed -n '1,120p' backend/app/tasks/celery_tasks.py | cat -nRepository: 7-Blocks/Kepler
Length of output: 11891
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Show the async scheduler implementation details and the task wrappers.
sed -n '1,220p' backend/app/core/scheduler.py | cat -nRepository: 7-Blocks/Kepler
Length of output: 5994
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the async scheduler, async endpoint, and Celery wrapper bodies to see if sync calls are offloaded.
printf '--- scheduler ---\n'
sed -n '1,220p' backend/app/core/scheduler.py | cat -n
printf '\n--- catalog endpoint ---\n'
sed -n '130,220p' backend/api/v1/endpoints/catalog.py | cat -n
printf '\n--- celery tasks ---\n'
sed -n '1,120p' backend/app/tasks/celery_tasks.py | cat -nRepository: 7-Blocks/Kepler
Length of output: 244
Keep retry backoff off the asyncio scheduler
The built-in asyncio scheduler calls sync_all_groups() directly inside async def job_sync_spacetrack(), so time.sleep() here will block the event loop during retries. Offload this sync ingestion path to a worker thread/process or switch the backoff to a non-blocking sleep.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/orbital/spacetrack.py` around lines 54 - 79, The retry implementation
in _retry uses blocking time.sleep, which stalls the asyncio event loop when
sync_all_groups is invoked by job_sync_spacetrack. Replace the blocking retry
path with an async-compatible approach, or ensure sync_all_groups and its
decorated calls run in a worker thread/process, while preserving the existing
exponential backoff and retry behavior.
| @_retry( | ||
| HTTP_MAX_ATTEMPTS, | ||
| HTTP_BASE_DELAY, | ||
| HTTP_MAX_DELAY, | ||
| retry_on=(httpx.RequestError, httpx.HTTPStatusError), | ||
| ) | ||
| def _send_request(self, url: str, expect_list: bool = True) -> List[Dict[str, Any]]: | ||
| """Send a GET request with retry/backoff. Raises on persistent failure.""" | ||
| logger.info(f"[SpaceTrack] Request sent: GET {url}") | ||
| resp = self.client.get(url) | ||
| resp.raise_for_status() | ||
| data = resp.json() | ||
| if expect_list and not isinstance(data, list): | ||
| logger.error( | ||
| f"[SpaceTrack] Response received but unexpected type {type(data)} — " | ||
| f"preview: {str(data)[:300]}" | ||
| ) | ||
| self._reset_auth() | ||
| raise ValueError(f"Unexpected Space-Track response type: {type(data)}") | ||
| logger.info( | ||
| f"[SpaceTrack] Response received: {len(data) if isinstance(data, list) else '?'} " | ||
| f"records (HTTP {resp.status_code})." | ||
| ) | ||
| return data # type: ignore[return-value] |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the file and surrounding symbols first
ast-grep outline backend/orbital/spacetrack.py --view expanded
# Show the relevant section with line numbers
sed -n '1,320p' backend/orbital/spacetrack.py | cat -n
# Search for retry/auth reset usage in this module
rg -n "_reset_auth|_send_request|retry_on|HTTP_MAX_ATTEMPTS|HTTP_BASE_DELAY|HTTP_MAX_DELAY|fetch_group_json|fetch" backend/orbital/spacetrack.pyRepository: 7-Blocks/Kepler
Length of output: 18942
Avoid retrying auth failures in _send_request
resp.raise_for_status() will surface 401/403 as httpx.HTTPStatusError, so _retry(...) burns all 4 attempts on a stale session before fetch_group_json finally resets auth. Handle auth failures separately and re-login immediately; keep the retry loop for transient errors.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/orbital/spacetrack.py` around lines 191 - 214, Update _send_request
to handle HTTP 401/403 responses separately from the _retry-wrapped transient
request flow: reset authentication and re-login immediately, then retry the
request without consuming the general retry attempts. Preserve retry handling
for other httpx.RequestError and HTTPStatusError cases, and keep existing
response validation behavior.
| def _ensure_db_connection(self, db: MongoSession) -> bool: | ||
| """Verify the Mongo client can reach the server; log the result.""" | ||
| try: | ||
| logger.info("[SpaceTrack] Database connection: pinging MongoDB …") | ||
| db.client.admin.command("ping") | ||
| logger.info("[SpaceTrack] Database connection: ✅ reachable.") | ||
| return True | ||
| except Exception as exc: | ||
| logger.error(f"[SpaceTrack] Database connection: ❌ unreachable: {exc}") | ||
| return False |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
_ensure_db_connection isn't retried, unlike _bulk_upsert — a single transient ping failure aborts the whole group sync.
The module's stated goal is exponential-backoff retries for "both Space-Track HTTP requests and MongoDB bulk writes," and _bulk_upsert is correctly decorated with @_retry(...). _ensure_db_connection's ping check has no such decorator, so one flaky ping immediately short-circuits sync_group with db_unreachable before it even attempts to fetch/parse/upsert — undermining the resilience this PR otherwise adds.
🔁 Suggested fix
+ `@_retry`(
+ MONGO_MAX_ATTEMPTS,
+ MONGO_BASE_DELAY,
+ MONGO_MAX_DELAY,
+ retry_on=(ConnectionFailure, ServerSelectionTimeoutError, NetworkTimeout),
+ )
def _ensure_db_connection(self, db: MongoSession) -> bool:
"""Verify the Mongo client can reach the server; log the result."""
try:
logger.info("[SpaceTrack] Database connection: pinging MongoDB …")
db.client.admin.command("ping")
logger.info("[SpaceTrack] Database connection: ✅ reachable.")
return True
- except Exception as exc:
+ except (ConnectionFailure, ServerSelectionTimeoutError, NetworkTimeout) as exc:
logger.error(f"[SpaceTrack] Database connection: ❌ unreachable: {exc}")
- return False
+ raise(Note: raising instead of returning False changes call-site handling — adjust sync_group to catch the exception, or keep a final except Exception: return False fallback around the retried call.)
🧰 Tools
🪛 Ruff (0.15.21)
[warning] 328-328: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/orbital/spacetrack.py` around lines 321 - 330, Update
_ensure_db_connection to retry transient MongoDB ping failures using the
module’s existing exponential-backoff retry mechanism, matching _bulk_upsert.
Preserve the boolean contract and ensure the final exhausted failure returns
False, or adjust sync_group to catch the propagated exception so group
synchronization still reports db_unreachable.
| def _bulk_upsert( | ||
| self, db: MongoSession, collection: str, docs: List[Dict[str, Any]] | ||
| ) -> Tuple[int, List[str]]: | ||
| """ | ||
| Upsert a Space-Track GP record into the satellites collection. | ||
| Returns the noradId on success, None on failure. | ||
| Duplicate-safe bulk upsert using UpdateOne(upsert=True) keyed on noradId. | ||
| Returns (successful_writes, failed_norad_ids). | ||
| ordered=False so a single bad document does not abort the batch. | ||
| """ | ||
| norad_id = str(rec.get("NORAD_CAT_ID", "")).strip() | ||
| if not norad_id: | ||
| return None | ||
|
|
||
| doc = self._gp_to_satellite_doc(rec, object_type_override) | ||
|
|
||
| ops = [ | ||
| UpdateOne({"noradId": d["noradId"]}, {"$set": d}, upsert=True) | ||
| for d in docs | ||
| ] | ||
| try: | ||
| db.db["satellites"].update_one( | ||
| {"noradId": norad_id}, | ||
| {"$set": doc}, | ||
| upsert=True, | ||
| result = db.db[collection].bulk_write(ops, ordered=False) | ||
| written = (result.upserted_count or 0) + (result.modified_count or 0) | ||
| logger.info( | ||
| f"[SpaceTrack] Bulk write OK on '{collection}': {written} " | ||
| f"upserted/modified (matched={result.matched_count})." | ||
| ) | ||
| return norad_id | ||
| except Exception as exc: | ||
| logger.warning(f"[SpaceTrack] Upsert failed for NORAD {norad_id}: {exc}") | ||
| return None | ||
|
|
||
| def _upsert_debris_doc( | ||
| self, db: MongoSession, rec: Dict[str, Any] | ||
| ) -> Optional[str]: | ||
| """Upsert a debris record into the debris collection.""" | ||
| norad_id = str(rec.get("NORAD_CAT_ID", "")).strip() | ||
| if not norad_id: | ||
| return None | ||
|
|
||
| doc = self._gp_to_satellite_doc(rec, object_type_override="DEBRIS") | ||
|
|
||
| try: | ||
| db.db["debris"].update_one( | ||
| {"noradId": norad_id}, | ||
| {"$set": doc}, | ||
| upsert=True, | ||
| return written, [] | ||
| except BulkWriteError as exc: | ||
| details = exc.details or {} | ||
| write_errors = details.get("writeErrors", []) | ||
| failed_ids = [str(e.get("key", {}).get("noradId", "?")) for e in write_errors] | ||
| written = (details.get("nUpserted", 0) or 0) + (details.get("nModified", 0) or 0) | ||
| logger.error( | ||
| f"[SpaceTrack] Bulk write partial failure on '{collection}': " | ||
| f"{written} written, {len(failed_ids)} failed — {failed_ids[:10]}" | ||
| ) | ||
| return norad_id | ||
| return written, failed_ids | ||
| except (ConnectionFailure, ServerSelectionTimeoutError, NetworkTimeout, OperationFailure): | ||
| # Let the retry decorator handle transient Mongo errors. | ||
| raise | ||
| except Exception as exc: | ||
| logger.warning(f"[SpaceTrack] Debris upsert failed for NORAD {norad_id}: {exc}") | ||
| return None | ||
| logger.error(f"[SpaceTrack] Bulk write unexpected error on '{collection}': {exc}") | ||
| raise |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Inspect the relevant section of the file
sed -n '300,390p' backend/orbital/spacetrack.py
# Check whether pymongo is available in the environment for a quick local probe
python3 - <<'PY'
import importlib.util
mods = ["pymongo", "bson"]
for m in mods:
print(m, importlib.util.find_spec(m) is not None)
PYRepository: 7-Blocks/Kepler
Length of output: 4043
🌐 Web query:
PyMongo BulkWriteError.details writeErrors op structure UpdateOne q u index code errmsg 4.10.1
💡 Result:
When a bulk write operation in PyMongo encounters an error, it raises a BulkWriteError exception [1][2]. The details of the error are accessible through the.details attribute of this exception, which contains the raw server response document [2][3]. The writeErrors field within these details is a list containing information about each operation that failed [4][3]. For an UpdateOne operation, the structure of an entry in the writeErrors list typically includes [4][5]: index: The zero-based integer index of the operation in the original bulk write request list [4][5]. code: An integer value identifying the specific MongoDB error code [4][5]. errmsg: A string describing the error [4][5]. op: A document identifying the operation that failed [4]. For an UpdateOne operation, this document specifically includes [4][5]: q: The query filter used to select the documents to update [4][5]. u: The update document specifying the modifications [4][5]. multi: A boolean indicating if the operation was configured to update multiple documents (or specifically, false for UpdateOne) [4][5]. upsert: A boolean indicating if the operation was configured as an upsert [4][5]. Example structure of a writeErrors entry for an UpdateOne failure: { 'index': 0, 'code': 11000, 'errmsg': '...duplicate key error...', 'op': { 'q': {'a': 1}, 'u': {'$set': {'b': 2}}, 'multi': False, 'upsert': True } } In ordered bulk writes, the operation stops at the first error, so the writeErrors list will contain at most one entry [2][6]. In unordered bulk writes, the driver continues to process the remaining operations, and the list may contain multiple error entries [6][3].
Citations:
- 1: https://github.com/mongodb/mongo-python-driver/blob/master/pymongo/errors.py
- 2: https://pymongo.readthedocs.io/en/stable/api/pymongo/errors.html?highlight=BulkWriteError
- 3: https://pymongo.readthedocs.io/en/4.9.2/examples/bulk.html
- 4: https://www.mongodb.com/docs/manual/reference/method/bulkwriteresult/
- 5: https://github.com/mongodb/mongo-python-driver/blob/master/test/test_bulk.py
- 6: https://github.com/mongodb/specifications/blob/master/source/crud/bulk-write.md
Read failed NORAD IDs from op.q, not key BulkWriteError.details["writeErrors"] entries expose op/index; key is not present, so this will usually log "?" instead of the failed NORAD id. Use e["op"]["q"]["noradId"] here, or map e["index"] back to docs.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/orbital/spacetrack.py` around lines 338 - 373, Update failed ID
extraction in _bulk_upsert to read the failed NORAD ID from each write error’s
op.q.noradId, with appropriate safe access as needed, instead of e["key"].
Preserve the existing failed_ids logging and return behavior.
| def sync_by_catalog(self, db: MongoSession, catalog_number: str) -> Optional[Dict]: | ||
| """Fetch and upsert a single object by catalog number. Returns the doc.""" | ||
| rec = self.fetch_by_catalog_json(catalog_number) | ||
| if not rec: | ||
| return None | ||
| obj_type = _TYPE_MAP.get(rec.get("OBJECT_TYPE", "UNKNOWN"), "UNKNOWN") | ||
| if obj_type == "DEBRIS": | ||
| self._upsert_debris_doc(db, rec) | ||
| else: | ||
| self._upsert_satellite_doc(db, rec, obj_type) | ||
| return self._find_by_norad(db, "satellites", catalog_number) or \ | ||
| self._find_by_norad(db, "debris", catalog_number) | ||
| collection = "debris" if obj_type == "DEBRIS" else "satellites" | ||
| doc = self._gp_to_satellite_doc(rec, obj_type) | ||
| try: | ||
| self._bulk_upsert(db, collection, [doc]) | ||
| logger.info(f"[SpaceTrack] ✅ Upserted single catalog {catalog_number}.") | ||
| except Exception as exc: | ||
| logger.error(f"[SpaceTrack] ❌ Single upsert failed for {catalog_number}: {exc}") | ||
| return db.db[collection].find_one({"noradId": catalog_number}, {"_id": 0}) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
sync_by_catalog swallows upsert failures and always returns whatever find_one finds, masking failure as "not found" or stale data.
If _bulk_upsert raises (even after exhausting Mongo retries), the exception is only logged — execution continues to find_one. If the document never existed, callers get None, indistinguishable from "record not found on Space-Track." If the document existed from a prior successful sync, callers silently get the stale pre-failure document with no indication the current upsert failed. Unlike sync_group, this function has no structured status to signal partial/total failure, so this specific ingestion path can silently fail exactly the way issue #10 was raised against.
Consider returning a tuple/dict that distinguishes "not found on Space-Track" from "found but upsert failed," e.g. re-raising or returning (doc, error).
🧰 Tools
🪛 Ruff (0.15.21)
[warning] 483-483: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@backend/orbital/spacetrack.py` around lines 472 - 485, Update sync_by_catalog
so _bulk_upsert failures are propagated to callers instead of being logged and
followed by find_one. Preserve the existing None result for records absent from
Space-Track, and use the function’s return contract to distinguish successful
upserts from upsert errors, such as re-raising the exception or returning an
explicit document/error result.
|
@deepmhatre13 Thank you for this effort, really appreciate it |
|
@krishkhinchi thanks for the merge,kindly add ECSoc26 label to this pr. |
|
Question: add the ecsoc26 label Answer: |
|
Hi @krishkhinchi, I noticed this PR wasn't processed by ecsoc-sentinel and didn't receive an ECSoc26-L1/L2/L3 label, so it isn't showing up on the ECSoC leaderboard. Could you please check if the sentinel workflow can be triggered for this PR? |
|
@deepmhatre13, Thank you for the update. I'll resolve this issue asap. |
|
@deepmhatre13, Thank you for bringing this issue to our attention. We appreciate you informing us that the ECSoC26, ECSoC26-L1, ECSoC26-L2, and ECSoC26-L3 labels were not being added automatically. Our team has investigated and resolved the issue. Going forward, these labels will be applied automatically to all newly created pull requests. |
|
Hi @krishkhinchi, thank you for fixing the automation. My merged PR still hasn't been processed by ecsoc-sentinel, and my leaderboard score hasn't changed. Could you please rerun or retrigger the ecsoc-sentinel workflow for my merged PR so the bot can apply the appropriate ECSoC26-L* label and update the leaderboard? |
|
Hi @krishkhinchi, I noticed that manually adding the ECSoC26-L2 label didn't update the leaderboard. In my other ECSoC contributions, the leaderboard was updated only after the ecsoc-sentinel bot processed the PR and applied the final ECSoC level label. Could you please check whether ecsoc-sentinel is installed and configured for this repository, and whether it can process this merged PR? |
|
Hi @deepmhatre13, |
|
@krishkhinchi |
|
The ecsoc sentinel bot is not processing still,thats the main reason,it's working in other repos |
|
@krishkhinchi do check have you configured the bot in this repo |
|
@krishkhinchi do check have you configured the bot in this repo |
|
@deepmhatre13, Already added |
|
@krishkhinchi ,for one last time remove the labels and add them again,kindly. |
|
@deepmhatre13 label added thanks for your patience |

User description
Fixes #10
Summary
This PR fixes the Space-Track data ingestion pipeline by improving reliability, observability, and the scheduled synchronization flow.
Changes Made
sync_all_groups().Verification
python -m pytest tests/test_ingestion.py -v→ 8 tests passedSPACETRACK_USERNAMEandSPACETRACK_PASSWORD. Without them, the application reports the missing configuration explicitly instead of failing silently.Screenshots
8 passed)CodeAnt-AI Description
Make Space-Track ingestion report failures, retry outages, and avoid duplicate records
What Changed
Impact
✅ Fewer duplicate satellite records✅ Clearer sync failure reporting✅ Fewer missed Space-Track updates after temporary outages💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by CodeRabbit
Greptile Summary
This PR fixes the Space-Track ingestion pipeline that was silently failing due to a missing method (
sync_objects) in the Celery task, per-record upserts with wrong field names, and no error propagation. It introduces bulk upserts, exponential backoff retry, structured logging, and a status-dict return shape throughout the pipeline.orbital/spacetrack.py: Replaced per-recordupdate_onecalls with a singlebulk_writeper group; added a_retrydecorator with configurable backoff for HTTP and Mongo failures;sync_groupandsync_all_groupsnow return structured status dicts.celery_tasks.py: Fixed the Celery task to callsync_all_groups()instead of the non-existentsync_objects(); addedexc_info=Trueto error logging.catalog.py: Updatedtrigger_syncto use the new status dict for the API response.tests/test_ingestion.py: Added 8 tests covering authentication, parsing, bulk upsert, duplicate prevention, and log emission — but the import path (app.services.spacetrack) does not exist in the repository, so the tests will fail to collect.Confidence Score: 3/5
Not safe to merge without fixing the broken test import path, the createdAt data corruption on every sync, and the false-success API response on DB failure.
The Celery task fix and bulk-upsert refactor are solid, but three real defects remain: (1) the new test suite imports from a module path that doesn't exist anywhere in the repo, so all 8 tests fail at collection — the verification evidence in the PR description cannot reflect what is actually in this diff; (2) every $set bulk upsert unconditionally overwrites createdAt for existing documents, corrupting the creation timestamp on every scheduled sync; (3) the /catalog/sync endpoint returns success: true when MongoDB is unreachable because the early-exit status dict carries failed: 0, making the field useless for incident detection.
backend/tests/test_ingestion.py (wrong import path), backend/orbital/spacetrack.py (createdAt handling in _bulk_upsert), and backend/api/v1/endpoints/catalog.py (success flag logic in trigger_sync).
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant Celery as Celery Task participant ST as SpaceTrackService participant STAPI as Space-Track API participant Mongo as MongoDB Celery->>ST: sync_all_groups(db) loop For each SYNC_GROUP ST->>ST: _ensure_db_connection(db) ST->>Mongo: admin.command("ping") Mongo-->>ST: ok / error ST->>ST: authenticate() ST->>STAPI: POST /ajaxauth/login STAPI-->>ST: session cookie ST->>ST: _send_request(url) [retry x4] ST->>STAPI: GET /basicspacedata/query/... STAPI-->>ST: JSON list of GP records ST->>ST: _gp_to_satellite_doc() per record ST->>ST: _bulk_upsert(db, collection, docs) [retry x3] ST->>Mongo: "bulk_write([UpdateOne($set, upsert=True)])" Mongo-->>ST: BulkWriteResult ST-->>ST: "status dict {fetched, parsed, upserted, failed}" end ST-->>Celery: "aggregate status {total_upserted, total_failed}"%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant Celery as Celery Task participant ST as SpaceTrackService participant STAPI as Space-Track API participant Mongo as MongoDB Celery->>ST: sync_all_groups(db) loop For each SYNC_GROUP ST->>ST: _ensure_db_connection(db) ST->>Mongo: admin.command("ping") Mongo-->>ST: ok / error ST->>ST: authenticate() ST->>STAPI: POST /ajaxauth/login STAPI-->>ST: session cookie ST->>ST: _send_request(url) [retry x4] ST->>STAPI: GET /basicspacedata/query/... STAPI-->>ST: JSON list of GP records ST->>ST: _gp_to_satellite_doc() per record ST->>ST: _bulk_upsert(db, collection, docs) [retry x3] ST->>Mongo: "bulk_write([UpdateOne($set, upsert=True)])" Mongo-->>ST: BulkWriteResult ST-->>ST: "status dict {fetched, parsed, upserted, failed}" end ST-->>Celery: "aggregate status {total_upserted, total_failed}"Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "fix: improve Space-Track data ingestion ..." | Re-trigger Greptile