doc3gpp 0.1.1__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 (182) hide show
  1. doc3gpp-0.1.1/.env.example +26 -0
  2. doc3gpp-0.1.1/.github/workflows/publish.yml +45 -0
  3. doc3gpp-0.1.1/.gitignore +32 -0
  4. doc3gpp-0.1.1/AGENTS.md +313 -0
  5. doc3gpp-0.1.1/LICENSE +21 -0
  6. doc3gpp-0.1.1/PKG-INFO +510 -0
  7. doc3gpp-0.1.1/README.md +457 -0
  8. doc3gpp-0.1.1/doc3gpp.toml.example +1 -0
  9. doc3gpp-0.1.1/docs/3gpp-knowledge.md +548 -0
  10. doc3gpp-0.1.1/docs/architecture.md +615 -0
  11. doc3gpp-0.1.1/docs/cli.md +1599 -0
  12. doc3gpp-0.1.1/docs/code-map.md +148 -0
  13. doc3gpp-0.1.1/docs/conventions.md +356 -0
  14. doc3gpp-0.1.1/docs/known-constraints.md +141 -0
  15. doc3gpp-0.1.1/pyproject.toml +101 -0
  16. doc3gpp-0.1.1/scripts/dev_run.sh +6 -0
  17. doc3gpp-0.1.1/scripts/test_sqlite.sh +8 -0
  18. doc3gpp-0.1.1/src/doc3gpp/__init__.py +5 -0
  19. doc3gpp-0.1.1/src/doc3gpp/cli.py +3635 -0
  20. doc3gpp-0.1.1/src/doc3gpp/cli_auto_sync.py +333 -0
  21. doc3gpp-0.1.1/src/doc3gpp/cli_filters.py +155 -0
  22. doc3gpp-0.1.1/src/doc3gpp/cli_url_helpers.py +41 -0
  23. doc3gpp-0.1.1/src/doc3gpp/config.py +6 -0
  24. doc3gpp-0.1.1/src/doc3gpp/data/doc3gpp.toml.example +101 -0
  25. doc3gpp-0.1.1/src/doc3gpp/models/__init__.py +1 -0
  26. doc3gpp-0.1.1/src/doc3gpp/models/meeting.py +42 -0
  27. doc3gpp-0.1.1/src/doc3gpp/models/sync.py +90 -0
  28. doc3gpp-0.1.1/src/doc3gpp/models/tdoc.py +62 -0
  29. doc3gpp-0.1.1/src/doc3gpp/models/tdoc_cr.py +349 -0
  30. doc3gpp-0.1.1/src/doc3gpp/models/tdoc_file.py +73 -0
  31. doc3gpp-0.1.1/src/doc3gpp/models/tsg.py +27 -0
  32. doc3gpp-0.1.1/src/doc3gpp/models/wi.py +31 -0
  33. doc3gpp-0.1.1/src/doc3gpp/parsers/__init__.py +19 -0
  34. doc3gpp-0.1.1/src/doc3gpp/parsers/calendar_parser.py +209 -0
  35. doc3gpp-0.1.1/src/doc3gpp/parsers/cr/__init__.py +29 -0
  36. doc3gpp-0.1.1/src/doc3gpp/parsers/cr/cover_page.py +161 -0
  37. doc3gpp-0.1.1/src/doc3gpp/parsers/cr/cr_parsers.py +196 -0
  38. doc3gpp-0.1.1/src/doc3gpp/parsers/cr/header.py +60 -0
  39. doc3gpp-0.1.1/src/doc3gpp/parsers/cr/helpers.py +203 -0
  40. doc3gpp-0.1.1/src/doc3gpp/parsers/cr/ttcn_sections.py +400 -0
  41. doc3gpp-0.1.1/src/doc3gpp/parsers/cr_parser.py +75 -0
  42. doc3gpp-0.1.1/src/doc3gpp/parsers/direct_extractor.py +490 -0
  43. doc3gpp-0.1.1/src/doc3gpp/parsers/docx_converter.py +325 -0
  44. doc3gpp-0.1.1/src/doc3gpp/parsers/html_parsers.py +12 -0
  45. doc3gpp-0.1.1/src/doc3gpp/parsers/normalizers.py +45 -0
  46. doc3gpp-0.1.1/src/doc3gpp/parsers/tdoc_file_parser.py +191 -0
  47. doc3gpp-0.1.1/src/doc3gpp/parsers/tdoc_parser.py +391 -0
  48. doc3gpp-0.1.1/src/doc3gpp/parsers/tdoc_parsers.py +89 -0
  49. doc3gpp-0.1.1/src/doc3gpp/parsers/wi_parser.py +90 -0
  50. doc3gpp-0.1.1/src/doc3gpp/repository/__init__.py +1 -0
  51. doc3gpp-0.1.1/src/doc3gpp/repository/protocols.py +457 -0
  52. doc3gpp-0.1.1/src/doc3gpp/scraping/__init__.py +1 -0
  53. doc3gpp-0.1.1/src/doc3gpp/scraping/cache.py +377 -0
  54. doc3gpp-0.1.1/src/doc3gpp/scraping/cache_keys.py +60 -0
  55. doc3gpp-0.1.1/src/doc3gpp/scraping/calendar_source.py +17 -0
  56. doc3gpp-0.1.1/src/doc3gpp/scraping/client.py +189 -0
  57. doc3gpp-0.1.1/src/doc3gpp/scraping/ftp_source.py +116 -0
  58. doc3gpp-0.1.1/src/doc3gpp/scraping/portal_source.py +96 -0
  59. doc3gpp-0.1.1/src/doc3gpp/scraping/tdoc_zip_source.py +327 -0
  60. doc3gpp-0.1.1/src/doc3gpp/scraping/wi_source.py +43 -0
  61. doc3gpp-0.1.1/src/doc3gpp/services/__init__.py +1 -0
  62. doc3gpp-0.1.1/src/doc3gpp/services/factory.py +159 -0
  63. doc3gpp-0.1.1/src/doc3gpp/services/meetings_service.py +165 -0
  64. doc3gpp-0.1.1/src/doc3gpp/services/tdoc_cr_service.py +1086 -0
  65. doc3gpp-0.1.1/src/doc3gpp/services/tdoc_file_service.py +79 -0
  66. doc3gpp-0.1.1/src/doc3gpp/services/tdoc_service.py +96 -0
  67. doc3gpp-0.1.1/src/doc3gpp/services/tdoc_sync_coordinator.py +221 -0
  68. doc3gpp-0.1.1/src/doc3gpp/services/tsg_service.py +249 -0
  69. doc3gpp-0.1.1/src/doc3gpp/services/wi_service.py +69 -0
  70. doc3gpp-0.1.1/src/doc3gpp/settings/__init__.py +1 -0
  71. doc3gpp-0.1.1/src/doc3gpp/settings/config_source.py +113 -0
  72. doc3gpp-0.1.1/src/doc3gpp/settings/config_writer.py +326 -0
  73. doc3gpp-0.1.1/src/doc3gpp/settings/loader.py +33 -0
  74. doc3gpp-0.1.1/src/doc3gpp/settings/schema.py +387 -0
  75. doc3gpp-0.1.1/src/doc3gpp/storage/__init__.py +1 -0
  76. doc3gpp-0.1.1/src/doc3gpp/storage/backends/__init__.py +11 -0
  77. doc3gpp-0.1.1/src/doc3gpp/storage/backends/mysql.py +14 -0
  78. doc3gpp-0.1.1/src/doc3gpp/storage/backends/postgres.py +13 -0
  79. doc3gpp-0.1.1/src/doc3gpp/storage/backends/sqlite.py +43 -0
  80. doc3gpp-0.1.1/src/doc3gpp/storage/cache.py +15 -0
  81. doc3gpp-0.1.1/src/doc3gpp/storage/compression.py +49 -0
  82. doc3gpp-0.1.1/src/doc3gpp/storage/db/__init__.py +1 -0
  83. doc3gpp-0.1.1/src/doc3gpp/storage/db/base.py +7 -0
  84. doc3gpp-0.1.1/src/doc3gpp/storage/db/migrate.py +19 -0
  85. doc3gpp-0.1.1/src/doc3gpp/storage/db/migrations/README.md +8 -0
  86. doc3gpp-0.1.1/src/doc3gpp/storage/db/models.py +288 -0
  87. doc3gpp-0.1.1/src/doc3gpp/storage/db/session.py +37 -0
  88. doc3gpp-0.1.1/src/doc3gpp/storage/export.py +21 -0
  89. doc3gpp-0.1.1/src/doc3gpp/storage/repositories/__init__.py +1 -0
  90. doc3gpp-0.1.1/src/doc3gpp/storage/repositories/meeting_sql.py +264 -0
  91. doc3gpp-0.1.1/src/doc3gpp/storage/repositories/tdoc_cr_sql.py +263 -0
  92. doc3gpp-0.1.1/src/doc3gpp/storage/repositories/tdoc_cr_ttcn_sql.py +156 -0
  93. doc3gpp-0.1.1/src/doc3gpp/storage/repositories/tdoc_file_sql.py +173 -0
  94. doc3gpp-0.1.1/src/doc3gpp/storage/repositories/tdoc_sql.py +374 -0
  95. doc3gpp-0.1.1/src/doc3gpp/storage/repositories/tsg_sql.py +120 -0
  96. doc3gpp-0.1.1/src/doc3gpp/storage/repositories/wi_sql.py +120 -0
  97. doc3gpp-0.1.1/tests/conftest.py +17 -0
  98. doc3gpp-0.1.1/tests/fixtures/sample_pages/3GPP-meeting-R5.html +26 -0
  99. doc3gpp-0.1.1/tests/fixtures/tdoc_cr_doc/C6-250028.zip +0 -0
  100. doc3gpp-0.1.1/tests/fixtures/tdoc_cr_doc/R5-227476.zip +0 -0
  101. doc3gpp-0.1.1/tests/fixtures/tdoc_cr_doc/R5-253079.zip +0 -0
  102. doc3gpp-0.1.1/tests/fixtures/tdoc_cr_doc/R5s260009.zip +0 -0
  103. doc3gpp-0.1.1/tests/fixtures/tdoc_cr_doc/R5s260051.zip +0 -0
  104. doc3gpp-0.1.1/tests/fixtures/tdoc_cr_doc/R5s260135.zip +0 -0
  105. doc3gpp-0.1.1/tests/fixtures/tdoc_cr_doc/R5s260176.zip +0 -0
  106. doc3gpp-0.1.1/tests/fixtures/tdoc_xlsx/TDoc_List_Meeting_85434_portal.xlsx +0 -0
  107. doc3gpp-0.1.1/tests/fixtures/tdoc_xlsx/TDoc_List_Meeting_RAN5#111.xlsx +0 -0
  108. doc3gpp-0.1.1/tests/fixtures/wi_pages/R5.html +1 -0
  109. doc3gpp-0.1.1/tests/integration/test_auto_sync_sqlite.py +259 -0
  110. doc3gpp-0.1.1/tests/integration/test_cli_sqlite.py +79 -0
  111. doc3gpp-0.1.1/tests/integration/test_config_init_cli_sqlite.py +209 -0
  112. doc3gpp-0.1.1/tests/integration/test_config_set_cli_sqlite.py +122 -0
  113. doc3gpp-0.1.1/tests/integration/test_db_reset_sqlite.py +186 -0
  114. doc3gpp-0.1.1/tests/integration/test_meeting_service_sqlite.py +213 -0
  115. doc3gpp-0.1.1/tests/integration/test_mysql_backend.py +16 -0
  116. doc3gpp-0.1.1/tests/integration/test_online_3gpp_calendar.py +38 -0
  117. doc3gpp-0.1.1/tests/integration/test_online_tdoc_fetch_r5.py +63 -0
  118. doc3gpp-0.1.1/tests/integration/test_online_tdoc_parse.py +176 -0
  119. doc3gpp-0.1.1/tests/integration/test_sdk_integration.py +336 -0
  120. doc3gpp-0.1.1/tests/integration/test_sqlite_backend.py +14 -0
  121. doc3gpp-0.1.1/tests/integration/test_tdoc_cr_sqlite.py +1424 -0
  122. doc3gpp-0.1.1/tests/integration/test_tdoc_cr_ttcn_sqlite.py +601 -0
  123. doc3gpp-0.1.1/tests/integration/test_tdoc_file_sqlite.py +544 -0
  124. doc3gpp-0.1.1/tests/integration/test_tdoc_parse_direct_sqlite.py +412 -0
  125. doc3gpp-0.1.1/tests/integration/test_tdoc_parse_ftp_batch_sqlite.py +150 -0
  126. doc3gpp-0.1.1/tests/integration/test_tdoc_sqlite.py +548 -0
  127. doc3gpp-0.1.1/tests/integration/test_tsg_sqlite.py +110 -0
  128. doc3gpp-0.1.1/tests/integration/test_wi_sqlite.py +123 -0
  129. doc3gpp-0.1.1/tests/unit/test_cache_cli.py +298 -0
  130. doc3gpp-0.1.1/tests/unit/test_cache_keys.py +111 -0
  131. doc3gpp-0.1.1/tests/unit/test_calendar_parser.py +91 -0
  132. doc3gpp-0.1.1/tests/unit/test_calendar_parser_defensive.py +172 -0
  133. doc3gpp-0.1.1/tests/unit/test_cli_auto_sync.py +719 -0
  134. doc3gpp-0.1.1/tests/unit/test_cli_filters.py +243 -0
  135. doc3gpp-0.1.1/tests/unit/test_config_init_cli.py +320 -0
  136. doc3gpp-0.1.1/tests/unit/test_config_set_cli.py +311 -0
  137. doc3gpp-0.1.1/tests/unit/test_config_writer.py +525 -0
  138. doc3gpp-0.1.1/tests/unit/test_cr_parser.py +938 -0
  139. doc3gpp-0.1.1/tests/unit/test_db_reset_cli.py +255 -0
  140. doc3gpp-0.1.1/tests/unit/test_docx_converter.py +195 -0
  141. doc3gpp-0.1.1/tests/unit/test_ftp_listing.py +182 -0
  142. doc3gpp-0.1.1/tests/unit/test_list_output_format.py +405 -0
  143. doc3gpp-0.1.1/tests/unit/test_meeting_cli.py +133 -0
  144. doc3gpp-0.1.1/tests/unit/test_meeting_cli_filters_combined.py +27 -0
  145. doc3gpp-0.1.1/tests/unit/test_meeting_cli_invalid_fields.py +16 -0
  146. doc3gpp-0.1.1/tests/unit/test_meeting_cli_location_filter.py +19 -0
  147. doc3gpp-0.1.1/tests/unit/test_meeting_repository_combined_filters.py +98 -0
  148. doc3gpp-0.1.1/tests/unit/test_meeting_repository_filters.py +372 -0
  149. doc3gpp-0.1.1/tests/unit/test_meeting_repository_location_filter.py +52 -0
  150. doc3gpp-0.1.1/tests/unit/test_meeting_repository_upsert.py +197 -0
  151. doc3gpp-0.1.1/tests/unit/test_meeting_sync_tsg_validation.py +227 -0
  152. doc3gpp-0.1.1/tests/unit/test_parsers_utils.py +18 -0
  153. doc3gpp-0.1.1/tests/unit/test_portal_source.py +76 -0
  154. doc3gpp-0.1.1/tests/unit/test_scraper_client.py +296 -0
  155. doc3gpp-0.1.1/tests/unit/test_settings.py +8 -0
  156. doc3gpp-0.1.1/tests/unit/test_settings_config_file.py +641 -0
  157. doc3gpp-0.1.1/tests/unit/test_storage_utils.py +31 -0
  158. doc3gpp-0.1.1/tests/unit/test_tdoc_cache.py +325 -0
  159. doc3gpp-0.1.1/tests/unit/test_tdoc_cli_fields.py +366 -0
  160. doc3gpp-0.1.1/tests/unit/test_tdoc_cr_model.py +559 -0
  161. doc3gpp-0.1.1/tests/unit/test_tdoc_cr_service.py +641 -0
  162. doc3gpp-0.1.1/tests/unit/test_tdoc_cr_service_ftp_batch.py +371 -0
  163. doc3gpp-0.1.1/tests/unit/test_tdoc_file_parser.py +375 -0
  164. doc3gpp-0.1.1/tests/unit/test_tdoc_file_service.py +114 -0
  165. doc3gpp-0.1.1/tests/unit/test_tdoc_file_source.py +174 -0
  166. doc3gpp-0.1.1/tests/unit/test_tdoc_parse_cli.py +3162 -0
  167. doc3gpp-0.1.1/tests/unit/test_tdoc_parse_direct.py +1508 -0
  168. doc3gpp-0.1.1/tests/unit/test_tdoc_parse_url_batch.py +224 -0
  169. doc3gpp-0.1.1/tests/unit/test_tdoc_parser.py +531 -0
  170. doc3gpp-0.1.1/tests/unit/test_tdoc_repository_crud.py +143 -0
  171. doc3gpp-0.1.1/tests/unit/test_tdoc_repository_filters.py +608 -0
  172. doc3gpp-0.1.1/tests/unit/test_tdoc_service_sync.py +118 -0
  173. doc3gpp-0.1.1/tests/unit/test_tdoc_sync_cli.py +232 -0
  174. doc3gpp-0.1.1/tests/unit/test_tdoc_sync_coordinator.py +641 -0
  175. doc3gpp-0.1.1/tests/unit/test_tdoc_zip_source.py +543 -0
  176. doc3gpp-0.1.1/tests/unit/test_tsg_cli.py +189 -0
  177. doc3gpp-0.1.1/tests/unit/test_tsg_repository.py +105 -0
  178. doc3gpp-0.1.1/tests/unit/test_tsg_service.py +180 -0
  179. doc3gpp-0.1.1/tests/unit/test_tsg_url_pattern.py +47 -0
  180. doc3gpp-0.1.1/tests/unit/test_wi_cli.py +139 -0
  181. doc3gpp-0.1.1/tests/unit/test_wi_parser.py +64 -0
  182. doc3gpp-0.1.1/tests/unit/test_wi_repository_filters.py +159 -0
@@ -0,0 +1,26 @@
1
+ # Copy to .env and adjust values as needed.
2
+ #
3
+ # Only the env vars listed below are honoured by doc3gpp — every other
4
+ # DOC3GPP_* variable is silently ignored. Configure everything else via
5
+ # the TOML config file (`doc3gpp config set <key> <value>` or a hand-
6
+ # edited `doc3gpp.toml`); see doc3gpp.toml.example for the full schema.
7
+ #
8
+ # Recognised vars (see doc3gpp.settings.schema.ALLOWED_ENV_VARS):
9
+ # DOC3GPP_DATABASE_URL, DOC3GPP_DB_ECHO, DOC3GPP_LOG_LEVEL,
10
+ # DOC3GPP_HTTP_VERIFY, DOC3GPP_CACHE__DIR, DOC3GPP_SYNC__AUTO_SYNC
11
+
12
+ # DOC3GPP_DATABASE_URL: leave commented to use the default
13
+ # sqlite+pysqlite:///<Path.home()>/.local/share/doc3gpp/doc3gpp.db
14
+ # DOC3GPP_DATABASE_URL=sqlite+pysqlite:////absolute/path/to/doc3gpp.db
15
+ DOC3GPP_DB_ECHO=false
16
+ DOC3GPP_HTTP_VERIFY=false
17
+ DOC3GPP_LOG_LEVEL=INFO
18
+
19
+ # Cache directory for the TDoc extraction pipeline
20
+ # (overrides the XDG default ~/.cache/doc3gpp/tdocs).
21
+ # DOC3GPP_CACHE__DIR=/path/to/cache
22
+
23
+ # When true, `meeting list`, `tdoc list`, `tdoc show`, and DB-mode
24
+ # `tdoc parse` internally trigger the same sync paths used by explicit
25
+ # `meeting sync` / `tdoc sync`. Same skip rules apply.
26
+ DOC3GPP_SYNC__AUTO_SYNC=false
@@ -0,0 +1,45 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+
7
+ jobs:
8
+ build-n-publish:
9
+ name: Build and publish Python distro to PyPI
10
+ runs-on: ubuntu-latest
11
+
12
+ # environment + id-token: write are mandatory for Trusted Publishing (OIDC).
13
+ # The "testpypi" environment must match what's registered at
14
+ # https://test.pypi.org/manage/account/publishing/.
15
+ environment:
16
+ name: pypi
17
+ url: https://pypi.org/p/doc3gpp
18
+ permissions:
19
+ id-token: write # Required for OIDC authentication
20
+ contents: read
21
+
22
+ steps:
23
+ - name: Checkout code
24
+ uses: actions/checkout@v4
25
+
26
+ - name: Set up Python
27
+ uses: actions/setup-python@v5
28
+ with:
29
+ python-version: "3.12"
30
+
31
+ - name: Install build tools
32
+ run: |
33
+ python -m pip install --upgrade pip
34
+ python -m pip install build
35
+
36
+ - name: Build sdist and wheel
37
+ run: python -m build
38
+
39
+ - name: Inspect built artifacts
40
+ run: ls -lh dist/
41
+
42
+ - name: Publish to PyPI
43
+ uses: pypa/gh-action-pypi-publish@release/v1
44
+ # with:
45
+ # repository-url: https://test.pypi.org/legacy/
@@ -0,0 +1,32 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+
4
+ .venv/
5
+ venv/
6
+ env/
7
+
8
+ .env
9
+ .envrc
10
+
11
+ build/
12
+ dist/
13
+ *.egg-info/
14
+
15
+ .pytest_cache/
16
+ .coverage
17
+ .coverage.*
18
+ htmlcov/
19
+
20
+ .ruff_cache/
21
+
22
+ uv.lock
23
+
24
+ docs/_build/
25
+
26
+ tempCodeRunnerFile.py
27
+
28
+ .omo/
29
+ .opencode/
30
+ .serena/
31
+ .logs/
32
+ .codegraph/
@@ -0,0 +1,313 @@
1
+ # doc3gpp Agent Guide
2
+
3
+ **Generated:** 2026-07-10
4
+ **Branch:** main
5
+
6
+ A Python CLI/library that scrapes 3GPP meeting calendars, TDoc lists, auxiliary TDoc files, CR cover pages, and WIs into SQL.
7
+
8
+ This guide stays lean on purpose: it covers the **shape** of the
9
+ codebase — layout, where to look for a change, architecture rules, and
10
+ common commands. Everything that drifts easily (symbol tables, settings
11
+ caching details, filter grammar, anti-patterns, known constraints) lives
12
+ in [`docs/`](docs/) and is linked from the "Doc pointers" section below.
13
+
14
+ ## Quick start
15
+
16
+ ```bash
17
+ python -m venv .venv && source .venv/bin/activate
18
+ pip install -e ".[dev]"
19
+ cp .env.example .env
20
+ doc3gpp db init
21
+ doc3gpp db check
22
+ ```
23
+
24
+ Build system: **hatchling**. Stack: Python 3.10+, SQLAlchemy 2.0,
25
+ Pydantic v2 + pydantic-settings, httpx, BeautifulSoup4 + lxml, openpyxl,
26
+ alembic (installed but not wired). Optional extras:
27
+
28
+ - `.[cli]` — Typer CLI (also in `[dev]`).
29
+ - `.[mysql]` / `.[postgres]` — DB drivers.
30
+ - `.[extract]` — `python-docx` for the TDoc extraction pipeline.
31
+
32
+ `pip install doc3gpp` installs the SDK only; `pip install "doc3gpp[cli]"`
33
+ or `pipx install "doc3gpp[cli]"` adds the `doc3gpp` CLI command.
34
+
35
+ ## Structure (high level)
36
+
37
+ ```
38
+ doc3gpp/
39
+ ├── src/doc3gpp/ # package root
40
+ │ ├── cli.py # Typer commands (7 groups, 20 commands)
41
+ │ ├── models/ # domain dataclasses — never leak ORM attrs out
42
+ │ ├── repository/ # abstract repo contracts (Protocols)
43
+ │ ├── services/ # orchestration; CLI-injected via factory
44
+ │ ├── scraping/ # HTTP / FTP transport — no parsing
45
+ │ ├── parsers/ # HTML / Excel → domain objects — no network
46
+ │ ├── settings/ # pydantic-settings + TOML discovery
47
+ │ ├── storage/ # persistence umbrella
48
+ │ │ ├── compression.py # shared gzip JSON helpers (cover + TTCN sidecars)
49
+ │ │ ├── db/ # ORM models, engine, create_schema bootstrap
50
+ │ │ ├── backends/ # per-dialect engine kwargs
51
+ │ │ └── repositories/ # SQL impls of the repository Protocols
52
+ │ │ └── tdoc_cr_ttcn_sql.py # SQL impl of TDocCrTTCNDetailRepository
53
+ │ └── cli_filters.py # shared filter / TDoc-id grammar
54
+ ├── tests/
55
+ │ ├── unit/ # mock external calls
56
+ │ ├── integration/ # sqlite by default; online + mysql opt-in
57
+ │ └── fixtures/ # sample HTML + XLSX + zip docs
58
+ ├── docs/ # architecture, CLI ref, conventions, code map, constraints
59
+ └── scripts/ # test_sqlite.sh, dev_run.sh
60
+ ```
61
+
62
+ For the full symbol-to-file table, see
63
+ [`docs/code-map.md`](docs/code-map.md).
64
+
65
+ ## Where to look
66
+
67
+ | Task | Location | Notes |
68
+ | --- | --- | --- |
69
+ | Add a CLI command | `src/doc3gpp/cli.py` | Follow pattern: service → repo → CLI. |
70
+ | Add a config writer / CLI set command | `src/doc3gpp/settings/config_writer.py` + `src/doc3gpp/cli.py` (`config_app`) | TOML read-modify-write helpers; Typer `config set` command. |
71
+ | Add a data source | `src/doc3gpp/scraping/` + `src/doc3gpp/parsers/` | Network in `scraping/`, parsing in `parsers/`. |
72
+ | Add a domain model | `src/doc3gpp/models/` | `@dataclass(slots=True)`; never expose ORM attrs. |
73
+ | Add a storage backend | `src/doc3gpp/storage/backends/` | Engine kwargs per dialect. |
74
+ | Change filters for a list | `src/doc3gpp/repository/protocols.py` + `src/doc3gpp/storage/repositories/` | Update **both** the Protocol and the impl. |
75
+ | Run all tests | `./scripts/test_sqlite.sh` | Unit + integration, sqlite-only. |
76
+ | Run online tests | `python -m pytest -m online -rs` | Hits live 3gpp.org + FTP. |
77
+
78
+ For deeper conventions (filter grammar, settings caching, anti-patterns,
79
+ commit policy), see [`docs/conventions.md`](docs/conventions.md).
80
+
81
+ ## Architecture boundaries
82
+
83
+ Strict layered separation in `src/doc3gpp/`. Each layer depends only on
84
+ the layer below it; services reach down into storage through the
85
+ `repository/` Protocols rather than touching the concrete ORM. See
86
+ [`docs/architecture.md`](docs/architecture.md) for the layered diagram,
87
+ runtime data flow, and ORM schema.
88
+
89
+ | Layer | Rule |
90
+ | --- | --- |
91
+ | `models/` | pass between layers; **never leak ORM attributes** |
92
+ | `repository/` (abstract) | Protocol contracts only |
93
+ | `services/` | orchestration; injected with a repo impl via `services/factory.build_*` |
94
+ | `scraping/` | HTTP / FTP transport only — **no HTML parsing** |
95
+ | `parsers/` | HTML / Excel → domain only — **no network** |
96
+ | `storage/db/` | ORM models, engine factory, `create_schema` bootstrap |
97
+ | `storage/compression.py` | shared gzip JSON helpers for binary detail columns |
98
+ | `storage/repositories/` | SQL impls of the `repository/` Protocols |
99
+ | `settings/` | env + TOML config (pydantic-settings; precedence: CLI > env > file > defaults) |
100
+ | `cli.py` | thin Typer commands; never instantiate SQL repos directly |
101
+
102
+ Workflows in one line (full prose in `docs/architecture.md`):
103
+
104
+ - `doc3gpp meeting sync --tsg <s>` → `MeetingService.sync` → DynaReport
105
+ HTML → `parse_3gpp_calendar` → stamp `Meeting.tsg` →
106
+ `SQLAlchemyMeetingRepository.upsert_many`. Skips when the TSG was synced
107
+ within `Settings.sync.meeting_sync_interval` unless `--force` is used.
108
+ - `doc3gpp tdoc sync --meeting-id <id>` / `--meeting <name>` →
109
+ `TDocSyncCoordinator.sync_for_meeting_id` /
110
+ `sync_for_meeting_name` → `TDocService.sync_tdoc_list` +
111
+ `TDocFileService.sync_from_meeting_ftp`. The TDoc list comes from
112
+ `Settings.sync.tdoc_list_url_template` (default
113
+ `https://portal.3gpp.org/ngppapp/GenerateDocumentList.aspx?meetingId={meeting_id}`);
114
+ the auxiliary TDoc file scan still uses the stored meeting row's
115
+ FTP URL. **No meeting row → no TDoc sync.** Skips when the meeting
116
+ is outside `Settings.sync.tdoc_list_closed_window` or was synced
117
+ within `Settings.sync.tdoc_list_sync_interval`; `--force` bypasses
118
+ both checks.
119
+ - `doc3gpp tdoc sync` (no selector) →
120
+ `TDocSyncCoordinator.sync_all_tracked_meetings` → every distinct
121
+ ``meeting_id`` in the ``tdocs`` table is synced individually with the
122
+ same two skip rules applied per meeting.
123
+ - `doc3gpp wi sync --tsg <s>` → `WiService.sync` → `fetch_wis` →
124
+ `parse_3gpp_wis` → `SQLAlchemyWiRepository.upsert_many` (composite
125
+ PK `(wi_id, tsg_short)`; `tsgs` table is auto-seeded so the FK
126
+ validates).
127
+ - `doc3gpp tdoc parse <filters>` is end-to-end filter-driven — every
128
+ flag is a filter, capped by `Settings.tdoc_parse.max_batch`. In normal
129
+ mode the SQL query excludes rows already present in `tdoc_cr_details`,
130
+ so the batch cap applies only to pending TDocs; the preview and
131
+ confirmation list only pending rows. `--force` explicitly includes and
132
+ re-parses already-parsed matches. The parser returns
133
+ `TDocCRParseResult(cover, ttcn)` and `TDocCrService` fans the result
134
+ out across THREE independent upserts: the slim cover-page row in
135
+ `tdoc_cr_details`, the optional `tdoc_cr_ttcn_details` sidecar (only
136
+ when the parser recognised a TTCN CR), and the `tdoc_extracts`
137
+ metadata row. Full grammar and prompt-completion semantics in
138
+ [`docs/conventions.md`](docs/conventions.md) and
139
+ [`docs/cli.md`](docs/cli.md).
140
+ - `doc3gpp tdoc parse --from-path PATH` / `--from-url URL` is a
141
+ direct-mode alternative that bypasses the database filters. Local
142
+ files parse in-memory only; 3GPP-URL downloads follow the
143
+ FK-aware behaviour matrix in `docs/cli.md` (cache + DB writes
144
+ land when the filename's tdoc_id is present in `tdocs`; otherwise
145
+ the result is still emitted with a warning). The zip cache is
146
+ keyed on the **original (sanitized) filename** (D10 fix) so
147
+ multiple revisions of the same tdoc_id never collide. When
148
+ `Settings.sync.auto_sync` is enabled, `tdoc parse --from-url`
149
+ on a 3GPP FTP URL extracts tdoc_id candidates from the URL
150
+ (basename for file URLs; BFS up to `--max-depth` / `--recursive`
151
+ for folder URLs) and runs `trigger_auto_sync(...)` **before**
152
+ dispatching to the per-file parse helpers — see
153
+ [`src/doc3gpp/cli.py:1353-1366`](src/doc3gpp/cli.py) and the
154
+ "Auto-sync from URL candidates" section in `docs/cli.md`. The
155
+ ordering is TSG sync → meeting sync → parse, so the meeting_id
156
+ resolution can usually find the parent row by the time the parse
157
+ fires. Same skip rules as DB-mode apply; non-3GPP URLs never
158
+ trigger auto-sync; failures stay warnings.
159
+ - `doc3gpp tdoc show --tdoc <id>` resolves the parent `tdoc` row, then
160
+ looks up the slim cover-page details and the extract metadata by the
161
+ row's immutable `tdoc.ftp_url` (one row per URL — the URL is the row
162
+ identity for both `tdoc_cr_details` and `tdoc_extracts`). The TTCN
163
+ sidecar is joined in via `cr_ttcn_repo.get_by_url(tdoc.ftp_url)` only
164
+ when `is_ttcn_tdoc(tdoc.tdoc_id)` is `True`. Auxiliary files are
165
+ read once via `file_repo.get_for_tdoc_id(tdoc.tdoc_id)` and match
166
+ by `tdoc_id` (not URL) so all revisions / reviews / support files
167
+ surface in a single pass. The CLI's renderers emit `cover`, the
168
+ optional `ttcn` block, `extracted_at` (sourced from the
169
+ `tdoc_extracts` row via PK JOIN), and `files` (the auxiliary files
170
+ block / placeholder) as separate sections — the legacy `details` /
171
+ `parser_version` fields no longer appear in the output. The `tdoc_extracts` row carries a single `cache_file` column
172
+ (basename, derived from `tdoc.ftp_url` via `derive_cache_file()`);
173
+ the CLI reconstructs paths as `{cache.dir}/zips/<cache_file>` and
174
+ `{cache.dir}/markdown/<cache_file>` via `_build_cache().root` +
175
+ `derive_cache_file(ftp_url)`. **Both subtrees write real ZIPs** so
176
+ the `.zip` extension maps to a format `unzip` / 7z / WinZip can open
177
+ straight from disk — the zip subtree holds the 3GPP-served zip bytes;
178
+ the markdown subtree holds a `zipfile.ZipFile` wrapper produced by
179
+ `_wrap_markdown_zip` (single entry named `<docx stem>.md`,
180
+ `ZIP_DEFLATED`).
181
+ - `doc3gpp tdoc show --ftp-url <url>` resolves the URL into matching
182
+ rows across four tables (`tdocs`, `tdoc_cr_details`,
183
+ `tdoc_cr_ttcn_details`, `tdoc_files`) directly — `tdocs` and
184
+ `tdoc_files` use the new `get_by_ftp_url` lookups
185
+ (`SQLAlchemyTDocRepository.get_by_ftp_url` /
186
+ `SQLAlchemyTDocFileRepository.get_by_ftp_url`); the cover / TTCN
187
+ tables use the existing URL-PK lookups. The `--ftp-url` path is
188
+ **mutually exclusive** with `--tdoc` (an XOR validator raises
189
+ `BadParameter` when neither or both are supplied) and **does NOT
190
+ trigger** `trigger_auto_sync` — there's no parent TDoc to anchor a
191
+ meeting sync on, and a URL-keyed read should be a deterministic
192
+ snapshot of whatever is already in the DB. URL is normalised via
193
+ `normalize_ftp_path` so both full URLs
194
+ (`https://www.3gpp.org/ftp/...`) and bare relative paths resolve
195
+ the same row. `tdocs.ftp_url` is maintained as a 1:1 invariant by
196
+ the upload pipeline (no DB-level `UNIQUE` constraint); the lookup
197
+ returns a single row via `ORDER BY tdoc_id ASC LIMIT 1` as a
198
+ deterministic fallback if the invariant is ever violated.
199
+ `--format raw` on the URL path reads the cache file directly via
200
+ `derive_cache_file(url)` (no `TDocCrService.extract` detour)
201
+ because the URL is the row identity — a cache miss raises
202
+ `BadParameter` pointing at `doc3gpp tdoc parse --from-url <url>`
203
+ or `doc3gpp tdoc parse --tdoc <id>`. The CLI bundles the result
204
+ into a new `TDocShowRecordByUrl(ftp_url, tdoc, cover, ttcn,
205
+ extracted_at, files)` DTO and renders it under a
206
+ `# FTP URL` / `[FTP URL]` anchor; the renderer contract follows
207
+ the same omit-when-null convention as the `--tdoc` path's
208
+ `TDocShowRecord`.
209
+ - `doc3gpp config path` / `doc3gpp config show` dump the resolved
210
+ TOML + env settings for diffing against `doc3gpp.toml.example`.
211
+ - `doc3gpp config init --target <auto|project|user> [--force]` writes
212
+ the packaged default TOML template (full defaults) at the bootstrap
213
+ target — `auto` (default) picks `./doc3gpp.toml` when run from a
214
+ project root, otherwise `~/.config/doc3gpp/config.toml`. Refuses
215
+ while `DOC3GPP_CONFIG` is set; `--force` overwrites an existing file.
216
+ - `doc3gpp config set <key> <value>` writes one setting into the active
217
+ TOML config file (refuses when none is in use; run `config init` to
218
+ bootstrap one); the previous `--init` / `--target` / `--force` flags
219
+ are removed — see the plan at
220
+ `.omo/plans/config-set-command.md` for the full command contract.
221
+ - When `Settings.sync.auto_sync` is enabled, `meeting list`, `tdoc list`,
222
+ `tdoc show`, and database-mode `tdoc parse` internally trigger the
223
+ same meeting-calendar and TDoc-list sync paths used by explicit
224
+ `meeting sync` / `tdoc sync`. The same skip rules apply and are never
225
+ bypassed; failures are logged as warnings and do not abort the read
226
+ command. Direct-mode `tdoc parse --from-path` / `--from-url` never
227
+ triggers auto-sync. The `tdoc show --ftp-url` selector also never
228
+ triggers auto-sync — the URL is the row identity and there's no
229
+ parent TDoc / meeting to anchor a sync on; users wanting a fresh
230
+ extract at the URL must run `tdoc parse --from-url <url>` or
231
+ `tdoc parse --tdoc <id>` explicitly.
232
+
233
+ ## Common commands
234
+
235
+ ```bash
236
+ # Lint (ruff is the only configured tool)
237
+ ruff check .
238
+
239
+ # Full sqlite test suite (unit + integration, excludes online + mysql)
240
+ ./scripts/test_sqlite.sh
241
+
242
+ # Online tests (opt-in, hits live 3gpp.org and FTP)
243
+ python -m pytest -m online -rs
244
+
245
+ # MySQL tests (needs DOC3GPP_TEST_MYSQL_URL)
246
+ python -m pytest -m mysql
247
+
248
+ # Bootstrap dev environment
249
+ ./scripts/dev_run.sh
250
+ ```
251
+
252
+ `pyproject.toml [tool.pytest.ini_options]` sets `pythonpath = ["src"]`,
253
+ so tests resolve `doc3gpp.*` without an editable install.
254
+
255
+ ## Doc pointers
256
+
257
+ | Topic | Doc |
258
+ | --- | --- |
259
+ | Layered diagram, runtime data flow, ORM schema, CLI inventory, testing layout, design rules | [`docs/architecture.md`](docs/architecture.md) |
260
+ | Per-command CLI reference (every flag, default, example) | [`docs/cli.md`](docs/cli.md) |
261
+ | 3GPP URL conventions, naming conventions, parser field semantics | [`docs/3gpp-knowledge.md`](docs/3gpp-knowledge.md) |
262
+ | Where each public symbol lives (symbol → file reference) | [`docs/code-map.md`](docs/code-map.md) |
263
+ | Filter grammar, settings caching, commit policy, anti-patterns, unique styles | [`docs/conventions.md`](docs/conventions.md) |
264
+ | Open limitations (schema bootstrap, hardcoded FTP URL, R5-/C6- URL templates, test surface, …) | [`docs/known-constraints.md`](docs/known-constraints.md) |
265
+ | Per-knob TOML schema reference | [`doc3gpp.toml.example`](doc3gpp.toml.example) |
266
+
267
+ Update `README.md`, `AGENTS.md`, and the relevant `docs/*.md` in the
268
+ same change set when CLI surface or behaviour changes — see
269
+ [`docs/conventions.md`](docs/conventions.md) §"Documentation sync" for
270
+ the convention.
271
+
272
+ <!-- headroom:rtk-instructions -->
273
+ # RTK (Rust Token Killer) - Token-Optimized Commands
274
+
275
+ When running shell commands, **always prefix with `rtk`**. This reduces context
276
+ usage by 60-90% with zero behavior change. If rtk has no filter for a command,
277
+ it passes through unchanged — so it is always safe to use.
278
+
279
+ ## Key Commands
280
+ ```bash
281
+ # Git (59-80% savings)
282
+ rtk git status rtk git diff rtk git log
283
+
284
+ # Files & Search (60-75% savings)
285
+ rtk ls <path> rtk read <file> rtk grep <pattern>
286
+ rtk find <pattern> rtk diff <file>
287
+
288
+ # Test (90-99% savings) — shows failures only
289
+ rtk pytest tests/ rtk cargo test rtk test <cmd>
290
+
291
+ # Build & Lint (80-90% savings) — shows errors only
292
+ rtk tsc rtk lint rtk cargo build
293
+ rtk prettier --check rtk mypy rtk ruff check
294
+
295
+ # Analysis (70-90% savings)
296
+ rtk err <cmd> rtk log <file> rtk json <file>
297
+ rtk summary <cmd> rtk deps rtk env
298
+
299
+ # GitHub (26-87% savings)
300
+ rtk gh pr view <n> rtk gh run list rtk gh issue list
301
+
302
+ # Infrastructure (85% savings)
303
+ rtk docker ps rtk kubectl get rtk docker logs <c>
304
+
305
+ # Package managers (70-90% savings)
306
+ rtk pip list rtk pnpm install rtk npm run <script>
307
+ ```
308
+
309
+ ## Rules
310
+ - In command chains, prefix each segment: `rtk git add . && rtk git commit -m "msg"`
311
+ - For debugging, use raw command without rtk prefix
312
+ - `rtk proxy <cmd>` runs command without filtering but tracks usage
313
+ <!-- /headroom:rtk-instructions -->
doc3gpp-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 jerry wang
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.