satnogs-predict 1.0__tar.gz → 1.2__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 (59) hide show
  1. {satnogs_predict-1.0 → satnogs_predict-1.2}/CHANGELOG.md +16 -0
  2. {satnogs_predict-1.0 → satnogs_predict-1.2}/CONTRIBUTING.md +88 -10
  3. {satnogs_predict-1.0 → satnogs_predict-1.2}/PKG-INFO +1 -1
  4. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/tle.py +19 -5
  5. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict.egg-info/PKG-INFO +1 -1
  6. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict.egg-info/scm_file_list.json +33 -33
  7. satnogs_predict-1.2/src/satnogs_predict.egg-info/scm_version.json +8 -0
  8. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/integration/test_fake_tle_integration.py +7 -4
  9. satnogs_predict-1.2/tests/test_tle.py +114 -0
  10. satnogs_predict-1.0/src/satnogs_predict.egg-info/scm_version.json +0 -8
  11. satnogs_predict-1.0/tests/test_tle.py +0 -53
  12. {satnogs_predict-1.0 → satnogs_predict-1.2}/.gitignore +0 -0
  13. {satnogs_predict-1.0 → satnogs_predict-1.2}/.gitlab-ci.yml +0 -0
  14. {satnogs_predict-1.0 → satnogs_predict-1.2}/.pre-commit-config.yaml +0 -0
  15. {satnogs_predict-1.0 → satnogs_predict-1.2}/.python-version +0 -0
  16. {satnogs_predict-1.0 → satnogs_predict-1.2}/LICENSE +0 -0
  17. {satnogs_predict-1.0 → satnogs_predict-1.2}/README.md +0 -0
  18. {satnogs_predict-1.0 → satnogs_predict-1.2}/docs/docs.md +0 -0
  19. {satnogs_predict-1.0 → satnogs_predict-1.2}/pyproject.toml +0 -0
  20. {satnogs_predict-1.0 → satnogs_predict-1.2}/setup.cfg +0 -0
  21. {satnogs_predict-1.0 → satnogs_predict-1.2}/setup.py +0 -0
  22. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/__init__.py +0 -0
  23. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/constraints/__init__.py +0 -0
  24. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/constraints/constraints.py +0 -0
  25. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/core/__init__.py +0 -0
  26. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/core/engine.py +0 -0
  27. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/__init__.py +0 -0
  28. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/constraints.py +0 -0
  29. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/geometry.py +0 -0
  30. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/observer.py +0 -0
  31. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/orbit.py +0 -0
  32. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/planner.py +0 -0
  33. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/time.py +0 -0
  34. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/validation.py +0 -0
  35. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/domain/window.py +0 -0
  36. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/planning/__init__.py +0 -0
  37. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/planning/planner.py +0 -0
  38. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/propagation/__init__.py +0 -0
  39. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/propagation/propagator.py +0 -0
  40. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/py.typed +0 -0
  41. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict/utils.py +0 -0
  42. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict.egg-info/SOURCES.txt +0 -0
  43. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict.egg-info/dependency_links.txt +0 -0
  44. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict.egg-info/requires.txt +0 -0
  45. {satnogs_predict-1.0 → satnogs_predict-1.2}/src/satnogs_predict.egg-info/top_level.txt +0 -0
  46. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/__init__.py +0 -0
  47. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/conftest.py +0 -0
  48. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/constraints/test_constraints.py +0 -0
  49. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/core/test_engine.py +0 -0
  50. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/domain/test_observer.py +0 -0
  51. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/domain/test_orbit.py +0 -0
  52. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/domain/test_planner_domain.py +0 -0
  53. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/domain/test_required_fields.py +0 -0
  54. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/domain/test_time.py +0 -0
  55. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/domain/test_window.py +0 -0
  56. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/helpers.py +0 -0
  57. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/planning/test_planner.py +0 -0
  58. {satnogs_predict-1.0 → satnogs_predict-1.2}/tests/propagation/test_propagator.py +0 -0
  59. {satnogs_predict-1.0 → satnogs_predict-1.2}/uv.lock +0 -0
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2
4
+
5
+ ### Added
6
+
7
+ - `generate_fake_tle()` encodes catalog numbers 100000–339999 in Alpha-5 format.
8
+
9
+ ### Fixed
10
+
11
+ - `generate_fake_tle()` raises `ValueError` for catalog numbers outside 0–339999. Previously 6-digit numbers were silently truncated to their first five digits.
12
+
13
+ ## 1.1
14
+
15
+ ### Fixed
16
+
17
+ - `generate_fake_tle()` writes the epoch year as the last two digits of the year. Previously it used the first two digits (e.g. `20` for 2026), so every generated TLE had an epoch in 2020.
18
+
3
19
  ## 1.0
4
20
 
5
21
  ### Breaking Changes
@@ -11,6 +11,7 @@
11
11
  * [Respond to review comments](#respond-to-review-comments)
12
12
  * [Don’t get discouraged - or impatient](#dont-get-discouraged-or-impatient)
13
13
  * [Sign your work - the Developer’s Certificate of Origin](#sign-your-work-the-developers-certificate-of-origin)
14
+ * [Generative AI / LLM Policy](#generative-ai--llm-policy)
14
15
  * [References](#references)
15
16
 
16
17
  ## Introduction ##
@@ -22,7 +23,7 @@ It is based on Linux kernel patch submission guidelines.
22
23
 
23
24
  All Libre Space Foundation projects code is maintained under Git.
24
25
  Obtain a copy of the source code using `git clone`.
25
- Development usually takes place under `master` or `dev` branch.
26
+ Development usually takes place under `main`, `master` or `dev` branch.
26
27
 
27
28
  ## Separate the changes ##
28
29
 
@@ -39,16 +40,16 @@ The point to remember is that each commit should make an easily understood chang
39
40
  Each commit should be justifiable on its own merits.
40
41
 
41
42
  When creating a merge request from a series of commits, take special care to ensure that the software builds and runs properly after each commit in the series.
42
- Developers using `git bisect` to track down a problem can end up splitting your merge request at any point; they will not thank you if you introduce bugs in the middle.
43
+ Developers using `git bisect` to track down a problem can end up splitting your merge request at any point; not thanking you if you introduce bugs in the middle.
43
44
 
44
45
  If you cannot condense your merge request into a smaller set of commits, then only submit say 15 or so at a time and wait for review and integration.
45
46
 
46
- Never commit binary files.
47
+ Never commit binary files or statically generated code.
47
48
  Once they are part of Git repository history they cannot be removed.
48
49
 
49
50
  ## Quality check of the changes ##
50
51
 
51
- Continuous Integration will check your changes automatically once you create a merge request.
52
+ Continuous Integration checks your changes automatically once you create a merge request.
52
53
  Nevertheless, local quality check should also be done before submitting a merge request.
53
54
  The checks can include:
54
55
  * Static code analysis and coverage
@@ -78,10 +79,10 @@ See [Separate the changes](#separate-the-changes).
78
79
 
79
80
  Create commit or merge requests descriptions that are self-contained.
80
81
  This benefits both the maintainers and reviewers.
81
- Always use your real name and e-mail address for committing changes.
82
+ Always use a known identity and email address for committing changes.
82
83
 
83
84
  If the commit fixes a reported issue, refer to that bug entry by issue number.
84
- The issue will be closed automatically after merge, if it matches [this regular expression](https://docs.gitlab.com/ce/user/project/issues/automatic_issue_closing.html).
85
+ The issue closes automatically after merge, if it matches [this regular expression](https://docs.gitlab.com/ce/user/project/issues/automatic_issue_closing.html).
85
86
 
86
87
  ## Create a merge request ##
87
88
 
@@ -91,7 +92,7 @@ Merge requests must not contain other merge requests; re-base your commits if ne
91
92
 
92
93
  ## Respond to review comments ##
93
94
 
94
- Your merge request will almost certainly get comments from reviewers on ways in which it can be improved.
95
+ Your merge request almost certainly gets comments from reviewers on ways in which it can be improved.
95
96
  You must respond to those comments; ignoring reviewers is a good way to get ignored in return.
96
97
  Be sure to tell the reviewers what changes you are making and to thank them for their time.
97
98
  Code review is a tiring and time-consuming process, and reviewers sometimes get grumpy.
@@ -106,9 +107,9 @@ Wait for a minimum of one week before pinging reviewers - possibly longer during
106
107
 
107
108
  ## Sign your work - the Developer’s Certificate of Origin ##
108
109
 
109
- To improve tracking of who did what, especially with commits that can percolate to their final resting place in Libre Space Foundation projects code-base through several layers of maintainers, we’ve introduced a “sign-off” procedure on commits.
110
+ To improve tracking of who did what, especially with commits that can percolate to their final resting place in Libre Space Foundation projects code-base through several layers of maintainers, a “sign-off” procedure on commits is enforced.
110
111
 
111
- The sign-off is a simple line at the end of the explanation for the commit, which certifies that you wrote it or otherwise have the right to pass it on as an open-source patch.
112
+ The sign-off is a simple line at the end of the explanation for the commit, which certifies that you wrote it or otherwise have the right to pass it on as an open source patch.
112
113
  The rules are pretty simple:
113
114
 
114
115
  if you can certify the below:
@@ -138,7 +139,7 @@ then you just add a line saying:
138
139
 
139
140
  `Signed-off-by: Random J Developer <random@developer.example.org>`
140
141
 
141
- using your real name (sorry, no pseudonyms or anonymous contributions.)
142
+ using a known identity (sorry, no anonymous contributions.)
142
143
 
143
144
  If you are a maintainer, sometimes you need to slightly modify commits order to merge them.
144
145
  If you stick strictly to rule (c), you should ask the submitter to rediff, but this is a totally counter-productive waste of time and energy.
@@ -152,6 +153,83 @@ Signed-off-by: Random J Developer <random@developer.example.org>
152
153
  Signed-off-by: Lucky K Maintainer <lucky@maintainer.example.org>
153
154
  ```
154
155
 
156
+ ## Generative AI / LLM Policy ##
157
+
158
+ We appreciate that we can't realistically police how you author your merge requests, which includes whether you employ large-language model (LLM)-based development tools.
159
+ So, we don't.
160
+
161
+ However, due to both legal and human reasons, we have to establish boundaries.
162
+
163
+ ### Overview ###
164
+
165
+ - We take the responsibility for this project very seriously and we expect you to take your responsibility for your contributions seriously, too.
166
+ This used to be a given, but it changed now that a merge request is just one prompt away.
167
+ - Every contribution has to be backed by a human who unequivocally owns the copyright for all changes.
168
+ No LLM bots in `Co-authored-by:`s.
169
+ - Repeated slop contributions are unacceptable, and will be closed without review.
170
+ We reserve the right to exclude contributors who continue to submit such material.
171
+ - Absolutely no unsupervised agentic tools like OpenClaw.
172
+
173
+ By submitting a merge request, you certify that:
174
+
175
+ - You are the author of the contribution or have the legal right to submit it.
176
+ - You either hold the copyright to the changes or have explicit legal authorization to contribute them under this project's license.
177
+ - You understand the code.
178
+ - You accept full responsibility for it.
179
+
180
+ ### Legal ###
181
+
182
+ There is ongoing legal uncertainty regarding the copyright status of LLM-generated works and their provenance.
183
+ Because of this, allowing contributions by LLMs has unpredictable consequences for the copyright status of this project - even when leaving aside possible copyright violations due to plagiarism.
184
+
185
+ ### Human ###
186
+
187
+ As the makers of software that is used by many people worldwide and with a reputation for high-quality maintenance, we take our responsibility to our users very seriously.
188
+ No matter what LLM vendors or boosters on LinkedIn tell you, we have to manually review every change before merging, because it's our responsibility to keep the project stable.
189
+
190
+ Please understand that by opening low-quality merge requests you're not helping anyone.
191
+ Worse, you're [poisoning the open source ecosystem](https://lwn.net/Articles/1058266/) that was precarious even before the arrival of LLM tools.
192
+ Having to wade through plausible-looking-but-low-quality merge requests and trying to determine which ones are legit is extremely demoralizing and has already burned out many good maintainers.
193
+
194
+ In particular, note that [code reviews are a social process](https://blog.glyph.im/2026/03/what-is-code-review-for.html) and are *not* intended to catch bugs in PRs created by an LLM.
195
+ If you submit a merge request, we expect you to engage with reviewers and learn from the review, not
196
+ simply update the prompt that generated the PR and regenerate the code.
197
+
198
+ Put bluntly, we have no time or interest to become part of your vibe coding loop where you drop LLM slop at our door, we spend time and energy to review it, and you just feed it back into the LLM for another iteration.
199
+
200
+ This dynamic is especially pernicious because it poisons the well for mentoring new contributors which we are committed to.
201
+
202
+ ### Summary ###
203
+
204
+ In practice, this means:
205
+
206
+ - Merge requests that have an LLM product listed as co-author can't be merged and will be closed without further discussion.
207
+ We cannot risk the copyright status of this project.
208
+
209
+ If you used LLM tools during development, you may still submit - but you must remove any LLM co-author tags and take full ownership of every line.
210
+
211
+ - By submitting a merge request, you take full technical and legal responsibility for the contents of the merge request and promise that you hold the copyright for the changes submitted.
212
+
213
+ "An LLM wrote it" is *not* an acceptable response to questions or critique.
214
+ If you cannot explain and defend the changes you submit, do not submit them and open a high-quality bug report or feature request instead.
215
+
216
+ - Accounts that exercise bot-like behavior, like automated mass merge requests, will be permanently banned, whether they belong to a human or not.
217
+
218
+ - LLM-generated comments must be concise and accurate, and you must be prepared to stand by them.
219
+ Do not post summaries unless you are certain that they add value to the discussion.
220
+ Remember that all LLM output *looks* plausible.
221
+ When using these tools, it's your responsibility to ensure that the output is correct, and useful.
222
+
223
+ - Remember that LLM generated content is generally easier for you to produce, but *harder* for others to read, review or interpret.
224
+ Prioritising your time over that of others is contrary to the project's code of conduct.
225
+ Verbose, repetitive, or off topic comments may be marked as spam.
226
+
227
+ ### Thanks ###
228
+
229
+ This LLM policy was based on the policy of the `pip` and `attrs` project.
230
+ Many thanks to them for developing it.
231
+
155
232
  ## References ##
156
233
 
157
234
  * Linux kernel documentation - [Submitting patches: the essential guide to getting your code into the kernel](https://www.kernel.org/doc/html/latest/process/submitting-patches.html)
235
+ * pip AI policy - [Generative AI / LLM Policy](https://github.com/pypa/pip/blob/main/AI_POLICY.md)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: satnogs-predict
3
- Version: 1.0
3
+ Version: 1.2
4
4
  Summary: A package for calculating passes and observation windows for satellites.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -29,6 +29,19 @@ class FakeTLEParameters:
29
29
 
30
30
  DEFAULT_FAKE_TLE_PARAMETERS = FakeTLEParameters()
31
31
 
32
+ # Alpha-5 prefix letters for 10..33; I and O are skipped to avoid confusion with 1 and 0.
33
+ _ALPHA5_LETTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ"
34
+ _MAX_ALPHA5_CATALOG_NUM = 339_999
35
+
36
+
37
+ def _format_catalog_number(catalog_num: int) -> str:
38
+ if 0 <= catalog_num <= 99_999:
39
+ return f"{catalog_num:05d}"
40
+ if 100_000 <= catalog_num <= _MAX_ALPHA5_CATALOG_NUM:
41
+ prefix, remainder = divmod(catalog_num, 10_000)
42
+ return f"{_ALPHA5_LETTERS[prefix - 10]}{remainder:04d}"
43
+ raise ValueError(f"catalog_num must be between 0 and {_MAX_ALPHA5_CATALOG_NUM}")
44
+
32
45
 
33
46
  def _tle_checksum(line_without_checksum: str) -> int:
34
47
  total = 0
@@ -71,17 +84,18 @@ def generate_fake_tle(
71
84
  right_ascension = (observer_location.lst_hours_at(skyfield_time) * 15.0) % 360.0
72
85
  mean_anomaly = latitude % 360.0
73
86
 
74
- epoch_year = epoch.year
87
+ catalog_number = _format_catalog_number(params.catalog_num)
88
+ epoch_year = epoch.year % 100
75
89
  epoch_day = _epoch_day_of_year_fraction(epoch)
76
90
 
77
91
  line0 = params.name.ljust(24)
78
92
 
79
93
  line1_no_checksum = (
80
- "1 {:5.5}{:.1} {:8.8} {:.2}{:012.8f} {:>10.10} {:>8.8} {:>8.8} 0 {:>4.4}".format(
81
- f"{params.catalog_num:05d}",
94
+ "1 {:5.5}{:.1} {:8.8} {:02d}{:012.8f} {:>10.10} {:>8.8} {:>8.8} 0 {:>4.4}".format(
95
+ catalog_number,
82
96
  params.classifier,
83
97
  params.cospar_id,
84
- str(epoch_year),
98
+ epoch_year,
85
99
  epoch_day,
86
100
  params.mean_motion_first,
87
101
  params.mean_motion_second,
@@ -92,7 +106,7 @@ def generate_fake_tle(
92
106
 
93
107
  line2_no_checksum = (
94
108
  "2 {:>5.5} {:>8.8} {:>8.4f} {:>7.7} {:>8.8} {:>8.4f} {:>11.11}{:>5.5}".format(
95
- f"{params.catalog_num:05d}",
109
+ catalog_number,
96
110
  params.inclination,
97
111
  right_ascension,
98
112
  params.eccentricity,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: satnogs-predict
3
- Version: 1.0
3
+ Version: 1.2
4
4
  Summary: A package for calculating passes and observation windows for satellites.
5
5
  Requires-Python: >=3.11
6
6
  Description-Content-Type: text/markdown
@@ -3,50 +3,50 @@
3
3
  ".gitignore",
4
4
  ".gitlab-ci.yml",
5
5
  ".pre-commit-config.yaml",
6
- "CHANGELOG.md",
7
6
  ".python-version",
8
- "README.md",
7
+ "CHANGELOG.md",
9
8
  "CONTRIBUTING.md",
10
- "uv.lock",
11
- "pyproject.toml",
12
9
  "LICENSE",
10
+ "README.md",
11
+ "docs/docs.md",
12
+ "pyproject.toml",
13
13
  "setup.py",
14
- "tests/conftest.py",
15
- "tests/__init__.py",
16
- "tests/helpers.py",
17
- "tests/test_tle.py",
18
- "tests/integration/test_fake_tle_integration.py",
19
- "tests/domain/test_time.py",
20
- "tests/domain/test_planner_domain.py",
21
- "tests/domain/test_required_fields.py",
22
- "tests/domain/test_orbit.py",
23
- "tests/domain/test_observer.py",
24
- "tests/domain/test_window.py",
25
- "tests/planning/test_planner.py",
26
- "tests/propagation/test_propagator.py",
27
- "tests/core/test_engine.py",
28
- "tests/constraints/test_constraints.py",
29
14
  "src/satnogs_predict/__init__.py",
30
- "src/satnogs_predict/py.typed",
31
- "src/satnogs_predict/utils.py",
32
- "src/satnogs_predict/tle.py",
15
+ "src/satnogs_predict/constraints/__init__.py",
16
+ "src/satnogs_predict/constraints/constraints.py",
17
+ "src/satnogs_predict/core/__init__.py",
18
+ "src/satnogs_predict/core/engine.py",
19
+ "src/satnogs_predict/domain/__init__.py",
33
20
  "src/satnogs_predict/domain/constraints.py",
34
- "src/satnogs_predict/domain/time.py",
21
+ "src/satnogs_predict/domain/geometry.py",
35
22
  "src/satnogs_predict/domain/observer.py",
36
- "src/satnogs_predict/domain/validation.py",
23
+ "src/satnogs_predict/domain/orbit.py",
37
24
  "src/satnogs_predict/domain/planner.py",
38
- "src/satnogs_predict/domain/__init__.py",
25
+ "src/satnogs_predict/domain/time.py",
26
+ "src/satnogs_predict/domain/validation.py",
39
27
  "src/satnogs_predict/domain/window.py",
40
- "src/satnogs_predict/domain/geometry.py",
41
- "src/satnogs_predict/domain/orbit.py",
42
- "src/satnogs_predict/planning/planner.py",
43
28
  "src/satnogs_predict/planning/__init__.py",
29
+ "src/satnogs_predict/planning/planner.py",
44
30
  "src/satnogs_predict/propagation/__init__.py",
45
31
  "src/satnogs_predict/propagation/propagator.py",
46
- "src/satnogs_predict/core/engine.py",
47
- "src/satnogs_predict/core/__init__.py",
48
- "src/satnogs_predict/constraints/constraints.py",
49
- "src/satnogs_predict/constraints/__init__.py",
50
- "docs/docs.md"
32
+ "src/satnogs_predict/py.typed",
33
+ "src/satnogs_predict/tle.py",
34
+ "src/satnogs_predict/utils.py",
35
+ "tests/__init__.py",
36
+ "tests/conftest.py",
37
+ "tests/constraints/test_constraints.py",
38
+ "tests/core/test_engine.py",
39
+ "tests/domain/test_observer.py",
40
+ "tests/domain/test_orbit.py",
41
+ "tests/domain/test_planner_domain.py",
42
+ "tests/domain/test_required_fields.py",
43
+ "tests/domain/test_time.py",
44
+ "tests/domain/test_window.py",
45
+ "tests/helpers.py",
46
+ "tests/integration/test_fake_tle_integration.py",
47
+ "tests/planning/test_planner.py",
48
+ "tests/propagation/test_propagator.py",
49
+ "tests/test_tle.py",
50
+ "uv.lock"
51
51
  ]
52
52
  }
@@ -0,0 +1,8 @@
1
+ {
2
+ "tag": "1.2",
3
+ "distance": 0,
4
+ "node": "g536f81b75ea7f5aed4ff17646823a9e6a9aa2c9a",
5
+ "dirty": false,
6
+ "branch": "HEAD",
7
+ "node_date": "2026-10-23"
8
+ }
@@ -82,12 +82,15 @@ class TestFakeTleIntegration:
82
82
  TLE_EPOCH + timedelta(hours=3),
83
83
  )
84
84
  passes = find_passes(satellite, observer, time_range)
85
+ # Use a pass away from zenith with a gradual azimuth sweep.
85
86
  thepass = next(
86
87
  thepass
87
88
  for thepass in passes
88
- if thepass.start_sample.azimuth_deg < 220.0 and thepass.end_sample.azimuth_deg > 330.0
89
+ if thepass.max_altitude_sample.altitude_deg < 80.0
90
+ and thepass.start_sample.azimuth_deg < 260.0
91
+ and thepass.end_sample.azimuth_deg > 300.0
89
92
  )
90
- constraint = AzimuthWindowConstraint(az_start=220.0, az_end=330.0)
93
+ constraint = AzimuthWindowConstraint(az_start=260.0, az_end=300.0)
91
94
  context = ConstraintContext(satellite=satellite, observer=observer)
92
95
 
93
96
  constrained = constraint.apply(thepass, context=context)
@@ -95,5 +98,5 @@ class TestFakeTleIntegration:
95
98
  assert constrained.constraint_applied_result == ConstraintAppliedResult.MODIFIED
96
99
  assert constrained.start_sample.instant > thepass.start_sample.instant
97
100
  assert constrained.end_sample.instant < thepass.end_sample.instant
98
- assert 220.0 <= constrained.start_sample.azimuth_deg <= 330.0
99
- assert 220.0 <= constrained.end_sample.azimuth_deg <= 330.0
101
+ assert 260.0 <= constrained.start_sample.azimuth_deg <= 300.0
102
+ assert 260.0 <= constrained.end_sample.azimuth_deg <= 300.0
@@ -0,0 +1,114 @@
1
+ from __future__ import annotations
2
+
3
+ from datetime import datetime, timezone
4
+
5
+ import pytest
6
+ from skyfield.api import EarthSatellite, load # type: ignore[import-untyped]
7
+
8
+ from satnogs_predict import generate_fake_tle
9
+ from satnogs_predict.tle import FakeTLEParameters
10
+
11
+ ts = load.timescale()
12
+
13
+
14
+ def _checksum(line_without_checksum: str) -> int:
15
+ total = 0
16
+ for char in line_without_checksum:
17
+ if char.isdigit():
18
+ total += int(char)
19
+ elif char == "-":
20
+ total += 1
21
+ return total % 10
22
+
23
+
24
+ class TestGenerateFakeTle:
25
+ @pytest.mark.parametrize(
26
+ ("date_iso", "expected_epoch"),
27
+ [
28
+ ("2026-10-01T12:00:00+00:00", "26274.50000000"),
29
+ ("2006-01-01T00:00:00+00:00", "06001.00000000"),
30
+ ("1999-12-31T12:00:00+00:00", "99365.50000000"),
31
+ ("2024-02-29T06:00:00+00:00", "24060.25000000"),
32
+ ("2026-01-01T01:00:00+02:00", "25365.95833333"),
33
+ ],
34
+ )
35
+ def test_given_aware_date_when_generated_then_preserves_epoch(
36
+ self, date_iso: str, expected_epoch: str
37
+ ) -> None:
38
+ date = datetime.fromisoformat(date_iso)
39
+ tle = generate_fake_tle(latitude=37.9838, longitude=23.7275, date=date)
40
+
41
+ assert tle.line1[18:32] == expected_epoch
42
+ sat = EarthSatellite(tle.line1, tle.line2, tle.line0, ts)
43
+ assert abs((sat.epoch.utc_datetime() - date).total_seconds()) < 0.001
44
+
45
+ def test_given_valid_input_when_generated_then_returns_parseable_tle(self) -> None:
46
+ tle = generate_fake_tle(
47
+ latitude=37.9838,
48
+ longitude=23.7275,
49
+ date=datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc),
50
+ )
51
+
52
+ assert len(tle.line1) == 69
53
+ assert len(tle.line2) == 69
54
+ assert tle.line1[-1] == str(_checksum(tle.line1[:-1]))
55
+ assert tle.line2[-1] == str(_checksum(tle.line2[:-1]))
56
+
57
+ sat = EarthSatellite(tle.line1, tle.line2, tle.line0, ts)
58
+ assert sat.name == tle.line0.strip()
59
+
60
+ def test_given_same_input_when_generated_twice_then_result_is_deterministic(self) -> None:
61
+ date = datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc)
62
+
63
+ tle1 = generate_fake_tle(latitude=10.0, longitude=20.0, date=date)
64
+ tle2 = generate_fake_tle(latitude=10.0, longitude=20.0, date=date)
65
+
66
+ assert tle1 == tle2
67
+
68
+ def test_given_naive_date_when_generated_then_raises(self) -> None:
69
+ with pytest.raises(ValueError, match="timezone-aware"):
70
+ generate_fake_tle(
71
+ latitude=0.0,
72
+ longitude=0.0,
73
+ date=datetime(2026, 3, 6, 12, 0, 0),
74
+ )
75
+
76
+ @pytest.mark.parametrize(
77
+ ("catalog_num", "expected_field"),
78
+ [
79
+ (0, "00000"),
80
+ (25544, "25544"),
81
+ (99999, "99999"),
82
+ (100000, "A0000"),
83
+ (148493, "E8493"),
84
+ (182345, "J2345"),
85
+ (234567, "P4567"),
86
+ (339999, "Z9999"),
87
+ ],
88
+ )
89
+ def test_given_catalog_num_when_generated_then_encodes_alpha5(
90
+ self, catalog_num: int, expected_field: str
91
+ ) -> None:
92
+ tle = generate_fake_tle(
93
+ latitude=0.0,
94
+ longitude=0.0,
95
+ date=datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc),
96
+ params=FakeTLEParameters(catalog_num=catalog_num),
97
+ )
98
+
99
+ assert tle.line1[2:7] == expected_field
100
+ assert tle.line2[2:7] == expected_field
101
+ sat = EarthSatellite(tle.line1, tle.line2, tle.line0, ts)
102
+ assert sat.model.satnum == catalog_num
103
+
104
+ @pytest.mark.parametrize("catalog_num", [-1, 340000, 1000000])
105
+ def test_given_out_of_range_catalog_num_when_generated_then_raises(
106
+ self, catalog_num: int
107
+ ) -> None:
108
+ with pytest.raises(ValueError, match="catalog_num"):
109
+ generate_fake_tle(
110
+ latitude=0.0,
111
+ longitude=0.0,
112
+ date=datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc),
113
+ params=FakeTLEParameters(catalog_num=catalog_num),
114
+ )
@@ -1,8 +0,0 @@
1
- {
2
- "tag": "1.0",
3
- "distance": 0,
4
- "node": "g302d7309b6fa2c41ed4296c82e03362c8a8350b7",
5
- "dirty": false,
6
- "branch": "HEAD",
7
- "node_date": "2026-07-01"
8
- }
@@ -1,53 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime, timezone
4
-
5
- import pytest
6
- from skyfield.api import EarthSatellite, load # type: ignore[import-untyped]
7
-
8
- from satnogs_predict import generate_fake_tle
9
-
10
- ts = load.timescale()
11
-
12
-
13
- def _checksum(line_without_checksum: str) -> int:
14
- total = 0
15
- for char in line_without_checksum:
16
- if char.isdigit():
17
- total += int(char)
18
- elif char == "-":
19
- total += 1
20
- return total % 10
21
-
22
-
23
- class TestGenerateFakeTle:
24
- def test_given_valid_input_when_generated_then_returns_parseable_tle(self) -> None:
25
- tle = generate_fake_tle(
26
- latitude=37.9838,
27
- longitude=23.7275,
28
- date=datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc),
29
- )
30
-
31
- assert len(tle.line1) == 69
32
- assert len(tle.line2) == 69
33
- assert tle.line1[-1] == str(_checksum(tle.line1[:-1]))
34
- assert tle.line2[-1] == str(_checksum(tle.line2[:-1]))
35
-
36
- sat = EarthSatellite(tle.line1, tle.line2, tle.line0, ts)
37
- assert sat.name == tle.line0.strip()
38
-
39
- def test_given_same_input_when_generated_twice_then_result_is_deterministic(self) -> None:
40
- date = datetime(2026, 3, 6, 12, 0, 0, tzinfo=timezone.utc)
41
-
42
- tle1 = generate_fake_tle(latitude=10.0, longitude=20.0, date=date)
43
- tle2 = generate_fake_tle(latitude=10.0, longitude=20.0, date=date)
44
-
45
- assert tle1 == tle2
46
-
47
- def test_given_naive_date_when_generated_then_raises(self) -> None:
48
- with pytest.raises(ValueError, match="timezone-aware"):
49
- generate_fake_tle(
50
- latitude=0.0,
51
- longitude=0.0,
52
- date=datetime(2026, 3, 6, 12, 0, 0),
53
- )
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes