tac-validate 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. tac_validate-0.1.0/.gitignore +188 -0
  2. tac_validate-0.1.0/PKG-INFO +47 -0
  3. tac_validate-0.1.0/README.md +38 -0
  4. tac_validate-0.1.0/pyproject.toml +42 -0
  5. tac_validate-0.1.0/src/tac_validate/__init__.py +11 -0
  6. tac_validate-0.1.0/src/tac_validate/api.py +35 -0
  7. tac_validate-0.1.0/src/tac_validate/cli.py +79 -0
  8. tac_validate-0.1.0/src/tac_validate/codec.py +12 -0
  9. tac_validate-0.1.0/src/tac_validate/models.py +77 -0
  10. tac_validate-0.1.0/src/tac_validate/product_rules.py +364 -0
  11. tac_validate-0.1.0/src/tac_validate/products.py +18 -0
  12. tac_validate-0.1.0/src/tac_validate/rules.py +120 -0
  13. tac_validate-0.1.0/tests/fixtures/README.md +26 -0
  14. tac_validate-0.1.0/tests/fixtures/accept/airmet_basic.tac +2 -0
  15. tac_validate-0.1.0/tests/fixtures/accept/metar_basic.tac +1 -0
  16. tac_validate-0.1.0/tests/fixtures/accept/sigmet_basic.tac +2 -0
  17. tac_validate-0.1.0/tests/fixtures/accept/speci_basic.tac +1 -0
  18. tac_validate-0.1.0/tests/fixtures/accept/taf_basic.tac +1 -0
  19. tac_validate-0.1.0/tests/fixtures/accept/tca_basic.tac +15 -0
  20. tac_validate-0.1.0/tests/fixtures/accept/vaa_basic.tac +15 -0
  21. tac_validate-0.1.0/tests/fixtures/manifest.json +284 -0
  22. tac_validate-0.1.0/tests/fixtures/negative/airmet/missing_valid.tac +2 -0
  23. tac_validate-0.1.0/tests/fixtures/negative/airmet/multi_phenomenon.tac +2 -0
  24. tac_validate-0.1.0/tests/fixtures/negative/metar/bad_cloud_token.tac +1 -0
  25. tac_validate-0.1.0/tests/fixtures/negative/metar/missing_cccc.tac +1 -0
  26. tac_validate-0.1.0/tests/fixtures/negative/metar/missing_obs_time.tac +1 -0
  27. tac_validate-0.1.0/tests/fixtures/negative/metar/missing_qnh.tac +1 -0
  28. tac_validate-0.1.0/tests/fixtures/negative/metar/missing_temp_dewpoint.tac +1 -0
  29. tac_validate-0.1.0/tests/fixtures/negative/metar/missing_visibility.tac +1 -0
  30. tac_validate-0.1.0/tests/fixtures/negative/metar/missing_wind.tac +1 -0
  31. tac_validate-0.1.0/tests/fixtures/negative/sigmet/missing_valid.tac +2 -0
  32. tac_validate-0.1.0/tests/fixtures/negative/sigmet/multi_phenomenon.tac +2 -0
  33. tac_validate-0.1.0/tests/fixtures/negative/speci/missing_cccc.tac +1 -0
  34. tac_validate-0.1.0/tests/fixtures/negative/speci/missing_temp_and_qnh.tac +1 -0
  35. tac_validate-0.1.0/tests/fixtures/negative/speci/missing_visibility.tac +1 -0
  36. tac_validate-0.1.0/tests/fixtures/negative/speci/missing_wind.tac +1 -0
  37. tac_validate-0.1.0/tests/fixtures/negative/taf/bad_cnl_shape.tac +1 -0
  38. tac_validate-0.1.0/tests/fixtures/negative/taf/missing_cccc.tac +1 -0
  39. tac_validate-0.1.0/tests/fixtures/negative/taf/missing_issue_time.tac +1 -0
  40. tac_validate-0.1.0/tests/fixtures/negative/taf/missing_validity.tac +1 -0
  41. tac_validate-0.1.0/tests/fixtures/negative/tca/missing_dtg.tac +11 -0
  42. tac_validate-0.1.0/tests/fixtures/negative/tca/missing_max_wind.tac +11 -0
  43. tac_validate-0.1.0/tests/fixtures/negative/vaa/missing_dtg.tac +9 -0
  44. tac_validate-0.1.0/tests/fixtures/negative/vaa/missing_vaac.tac +9 -0
  45. tac_validate-0.1.0/tests/test_issue_spans.py +30 -0
  46. tac_validate-0.1.0/tests/test_rule_offsets.py +61 -0
  47. tac_validate-0.1.0/tests/test_tac_issue_spans.py +30 -0
  48. tac_validate-0.1.0/tests/test_tac_validate_codec.py +12 -0
  49. tac_validate-0.1.0/tests/test_tac_validate_import.py +9 -0
  50. tac_validate-0.1.0/tests/test_tc_f10_002_terminator_info.py +53 -0
  51. tac_validate-0.1.0/tests/test_tc_f12_001_negative_fixtures.py +114 -0
  52. tac_validate-0.1.0/tests/test_tc_f12_001_template_gates.py +65 -0
  53. tac_validate-0.1.0/tests/test_tc_f12_cli_smoke.py +57 -0
  54. tac_validate-0.1.0/tests/test_tc_f12_wheel_smoke.py +96 -0
  55. tac_validate-0.1.0/tests/test_tc_f6_031_lint.py +130 -0
@@ -0,0 +1,188 @@
1
+ # =============================================================================
2
+ # METAR → IWXXM monorepo — apps/, packages/, vendor/
3
+ # =============================================================================
4
+
5
+ # --- Secrets & local operator env (never commit) ---
6
+ .env
7
+ .env.*
8
+ !.env.example
9
+ prod.env
10
+ .envrc
11
+
12
+ # Per-workspace env overrides (apps/* and packages/*)
13
+ apps/**/.env
14
+ apps/**/.env.*
15
+ !apps/**/.env.example
16
+ packages/**/.env
17
+ packages/**/.env.*
18
+ !packages/**/.env.example
19
+
20
+ # Legacy env paths (pre-migration; remove after cutover)
21
+ backend/.env
22
+ auth/.env
23
+ frontend/.env
24
+
25
+ # --- Python (uv workspace: apps/backend, packages/*) ---
26
+ __pycache__/
27
+ *.py[codz]
28
+ *$py.class
29
+ *.so
30
+ .Python
31
+ build/
32
+ develop-eggs/
33
+ dist/
34
+ downloads/
35
+ eggs/
36
+ .eggs/
37
+ lib/
38
+ lib64/
39
+ parts/
40
+ sdist/
41
+ var/
42
+ wheels/
43
+ share/python-wheels/
44
+ *.egg-info/
45
+ .installed.cfg
46
+ *.egg
47
+ MANIFEST
48
+ *.manifest
49
+ *.spec
50
+ pip-log.txt
51
+ pip-delete-this-directory.txt
52
+ __pypackages__/
53
+ *.sage.py
54
+ .venv/
55
+ venv/
56
+ env/
57
+ ENV/
58
+ env.bak/
59
+ venv.bak/
60
+ .pdm-python
61
+ .pdm-build/
62
+ .pixi
63
+
64
+ # --- Node / pnpm workspace (apps/*, packages/*) ---
65
+ **/node_modules/
66
+ .pnp
67
+ .pnp.js
68
+ .pnpm-store/
69
+ npm-debug.log*
70
+ yarn-debug.log*
71
+ yarn-error.log*
72
+ pnpm-debug.log*
73
+ .eslintcache
74
+ *.tsbuildinfo
75
+
76
+ # --- Build & bundle outputs ---
77
+ **/dist/
78
+ **/dist-ssr/
79
+ **/build/
80
+ **/.vite/
81
+ **/.turbo/
82
+ **/.next/
83
+ **/.nuxt/
84
+ **/.output/
85
+ **/.svelte-kit/
86
+
87
+ # --- Test & coverage ---
88
+ .tox/
89
+ .nox/
90
+ .coverage
91
+ .coverage.*
92
+ .coverage-baseline.json
93
+ .cache
94
+ nosetests.xml
95
+ coverage.xml
96
+ *.cover
97
+ *.py.cover
98
+ htmlcov/
99
+ htmlcov_ann/
100
+ cover/
101
+ .hypothesis/
102
+ .pytest_cache/
103
+ .nyc_output
104
+ **/coverage/
105
+
106
+ # Playwright (apps/e2e and legacy frontend/)
107
+ **/test-results/
108
+ **/playwright-report/
109
+ **/blob-report/
110
+ **/.playwright/
111
+ **/playwright/.cache/
112
+
113
+ # --- Typecheck & lint caches ---
114
+ .mypy_cache/
115
+ .dmypy.json
116
+ dmypy.json
117
+ .pyre/
118
+ .pytype/
119
+ .ruff_cache/
120
+ cython_debug/
121
+
122
+ # --- IDE & OS ---
123
+ .vscode/
124
+ .idea/
125
+ *.swp
126
+ *.swo
127
+ *~
128
+ .DS_Store
129
+ Thumbs.db
130
+
131
+ # --- Docs site builds ---
132
+ docs/_build/
133
+ /site
134
+
135
+ # --- Local tooling binaries / artifacts ---
136
+ .tools/
137
+ .local/
138
+
139
+ # --- Cursor (local indexing prefs only; .cursorignore is tracked) ---
140
+ .cursorindexingignore
141
+
142
+ # --- Misc generated ---
143
+ *.log
144
+ *.db
145
+ *.local
146
+ local_settings.py
147
+ db.sqlite3
148
+ db.sqlite3-journal
149
+ instance/
150
+ .webassets-cache
151
+ .scrapy
152
+ .ipynb_checkpoints
153
+ profile_default/
154
+ ipython_config.py
155
+ celerybeat-schedule
156
+ celerybeat.pid
157
+ .abstra/
158
+ .pypirc
159
+ marimo/_static/
160
+ marimo/_lsp/
161
+ __marimo__/
162
+
163
+ # --- Legacy submodule / pre-migration paths (remove after M7 cutover) ---
164
+ # Vendor snapshots live under vendor/schemas/ once migrated (tracked, not ignored).
165
+ GIFTs/.venv/
166
+ GIFTs/.gifts/
167
+ GIFTs/build/
168
+ GIFTs/dist/
169
+ GIFTs/data/
170
+ GIFTs/.coverage
171
+ GIFTs/.pytest_cache/
172
+ GIFTs/.mypy_cache/
173
+ GIFTs/sfstns.db
174
+ GIFTs/driver.py
175
+ GIFTs/wiki/
176
+
177
+ # --- Rust / maturin (tac2iwxxm / iwxxm-validate PyO3) ---
178
+ packages/tac2iwxxm/rust/target/
179
+ packages/iwxxm-validate/rust/target/
180
+
181
+ # E10-34 — synced runtime schema subset (generated; MANIFEST.json is tracked)
182
+ packages/iwxxm-validate/src/iwxxm_validate/schemas/iwxxm/
183
+ packages/iwxxm-validate/src/iwxxm_validate/schemas/iwxxm-us/
184
+ packages/iwxxm-validate/src/iwxxm_validate/schemas/LAST_SYNC.json
185
+
186
+ # ADR-027 — xsdata pydantic trees are committed (T3.7); ignore only bytecode
187
+ packages/shared/src/metar_shared/iwxxm_xsd/**/__pycache__/
188
+
@@ -0,0 +1,47 @@
1
+ Metadata-Version: 2.4
2
+ Name: tac-validate
3
+ Version: 0.1.0
4
+ Summary: TAC parse gate and business-rule pack (F6 lint)
5
+ License: MIT
6
+ Requires-Python: >=3.12
7
+ Requires-Dist: msgspec>=0.19
8
+ Description-Content-Type: text/markdown
9
+
10
+ # tac-validate
11
+
12
+ TAC parse gate and shared business-rule pack for F6 products. MIT licensed.
13
+
14
+ ## Install
15
+
16
+ ```bash
17
+ pip install tac-validate==0.1.0
18
+ # or from the monorepo workspace:
19
+ uv sync --package tac-validate
20
+ ```
21
+
22
+ ## Library
23
+
24
+ ```python
25
+ from tac_validate import lint
26
+
27
+ report = lint("METAR KJFK ...=", product="METAR")
28
+ if not report.ok:
29
+ for issue in report.issues:
30
+ print(issue.code, issue.message)
31
+ for fix in report.fixes:
32
+ print(fix.code, fix.replacement)
33
+ ```
34
+
35
+ ## CLI
36
+
37
+ ```bash
38
+ tac-validate --product METAR path/to/report.tac
39
+ tac-validate --product METAR --json path/to/report.tac
40
+ ```
41
+
42
+ Exit code `0` when lint `ok`; `1` when error-severity issues are present (or the file cannot be read).
43
+
44
+ Rule depth (E10-21): METAR/SPECI/TAF full checklist; SIGMET/AIRMET/VAA/TCA template+gates.
45
+ Citations live in `docs/domain/TAC_VALIDATION.md` — no Annex prose in the wheel.
46
+
47
+ See ADR-015 / ADR-016. No FastAPI/Supabase imports; HTTP maps msgspec → pydantic.
@@ -0,0 +1,38 @@
1
+ # tac-validate
2
+
3
+ TAC parse gate and shared business-rule pack for F6 products. MIT licensed.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install tac-validate==0.1.0
9
+ # or from the monorepo workspace:
10
+ uv sync --package tac-validate
11
+ ```
12
+
13
+ ## Library
14
+
15
+ ```python
16
+ from tac_validate import lint
17
+
18
+ report = lint("METAR KJFK ...=", product="METAR")
19
+ if not report.ok:
20
+ for issue in report.issues:
21
+ print(issue.code, issue.message)
22
+ for fix in report.fixes:
23
+ print(fix.code, fix.replacement)
24
+ ```
25
+
26
+ ## CLI
27
+
28
+ ```bash
29
+ tac-validate --product METAR path/to/report.tac
30
+ tac-validate --product METAR --json path/to/report.tac
31
+ ```
32
+
33
+ Exit code `0` when lint `ok`; `1` when error-severity issues are present (or the file cannot be read).
34
+
35
+ Rule depth (E10-21): METAR/SPECI/TAF full checklist; SIGMET/AIRMET/VAA/TCA template+gates.
36
+ Citations live in `docs/domain/TAC_VALIDATION.md` — no Annex prose in the wheel.
37
+
38
+ See ADR-015 / ADR-016. No FastAPI/Supabase imports; HTTP maps msgspec → pydantic.
@@ -0,0 +1,42 @@
1
+ [project]
2
+ name = "tac-validate"
3
+ version = "0.1.0"
4
+ description = "TAC parse gate and business-rule pack (F6 lint)"
5
+ readme = "README.md"
6
+ license = { text = "MIT" }
7
+ requires-python = ">=3.12"
8
+ dependencies = [
9
+ "msgspec>=0.19",
10
+ ]
11
+
12
+ [project.scripts]
13
+ tac-validate = "tac_validate.cli:main"
14
+
15
+ [build-system]
16
+ requires = ["hatchling>=1.25"]
17
+ build-backend = "hatchling.build"
18
+
19
+ [tool.hatch.build.targets.wheel]
20
+ packages = ["src/tac_validate"]
21
+
22
+ [tool.pytest.ini_options]
23
+ testpaths = ["tests"]
24
+ pythonpath = ["src"]
25
+ markers = [
26
+ "slow: longer local/CI smokes (clean-venv wheel installs)",
27
+ ]
28
+
29
+ [tool.coverage.run]
30
+ branch = true
31
+ source = ["src/tac_validate"]
32
+
33
+ [tool.coverage.report]
34
+ fail_under = 95
35
+ show_missing = true
36
+
37
+ [tool.ruff]
38
+ line-length = 120
39
+
40
+ [tool.ruff.lint]
41
+ select = ["E", "W", "F", "I"]
42
+ ignore = ["E501"]
@@ -0,0 +1,11 @@
1
+ """TAC parse gate and business-rule pack (F6 lint)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tac_validate.api import lint
6
+ from tac_validate.models import Fix, Issue, LintReport
7
+ from tac_validate.products import PRODUCTS
8
+
9
+ __version__ = "0.1.0"
10
+
11
+ __all__ = ["PRODUCTS", "Fix", "Issue", "LintReport", "__version__", "lint"]
@@ -0,0 +1,35 @@
1
+ """Public ``lint()`` entrypoint for TAC parse gate + rule pack."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from tac_validate.models import LintReport
6
+ from tac_validate.products import PRODUCTS
7
+ from tac_validate.rules import check_parse_gate, check_product_rules
8
+
9
+
10
+ def lint(tac_text: str, *, product: str = "METAR") -> LintReport:
11
+ """
12
+ Lint TAC text for ``product`` using the shared rule-pack skeleton.
13
+
14
+ Parameters
15
+ ----------
16
+ tac_text :
17
+ TAC report or fragment.
18
+ product :
19
+ One of AIRMET, METAR, SIGMET, SPECI, TAF, VAA, TCA.
20
+
21
+ Returns
22
+ -------
23
+ LintReport
24
+ ``ok`` is ``False`` when any error-severity issue is present.
25
+ Optional ``fixes`` may suggest repairs (Q9=C).
26
+ """
27
+ issues, fixes = check_parse_gate(tac_text, product)
28
+ if not any(i.severity == "error" for i in issues):
29
+ issues.extend(check_product_rules(tac_text, product))
30
+
31
+ ok = not any(i.severity == "error" for i in issues)
32
+ return LintReport(ok=ok, product=product, issues=issues, fixes=fixes)
33
+
34
+
35
+ __all__ = ["PRODUCTS", "lint"]
@@ -0,0 +1,79 @@
1
+ """Command-line interface for ``tac-validate`` (F12)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import sys
7
+ from pathlib import Path
8
+ from typing import Sequence
9
+
10
+ from tac_validate.api import lint
11
+ from tac_validate.codec import json_encoder
12
+ from tac_validate.products import PRODUCTS
13
+
14
+
15
+ def _build_parser() -> argparse.ArgumentParser:
16
+ parser = argparse.ArgumentParser(
17
+ prog="tac-validate",
18
+ description="Lint TAC text for F6 products (parse-gate + checklist/template gates).",
19
+ )
20
+ parser.add_argument(
21
+ "path",
22
+ type=Path,
23
+ help="Path to a TAC text file",
24
+ )
25
+ parser.add_argument(
26
+ "--product",
27
+ required=True,
28
+ choices=list(PRODUCTS),
29
+ help="F6 product id",
30
+ )
31
+ parser.add_argument(
32
+ "--json",
33
+ action="store_true",
34
+ help="Emit LintReport as JSON on stdout",
35
+ )
36
+ return parser
37
+
38
+
39
+ def main(argv: Sequence[str] | None = None) -> int:
40
+ """
41
+ Run ``tac-validate`` CLI.
42
+
43
+ Parameters
44
+ ----------
45
+ argv :
46
+ Argument vector (defaults to ``sys.argv[1:]``).
47
+
48
+ Returns
49
+ -------
50
+ int
51
+ ``0`` when ``report.ok``; ``1`` on lint errors or I/O failure.
52
+ """
53
+ parser = _build_parser()
54
+ args = parser.parse_args(list(argv) if argv is not None else None)
55
+ path: Path = args.path
56
+ try:
57
+ text = path.read_text(encoding="utf-8")
58
+ except OSError as exc:
59
+ print(f"error: cannot read {path}: {exc}", file=sys.stderr)
60
+ return 1
61
+
62
+ report = lint(text, product=args.product)
63
+ if args.json:
64
+ sys.stdout.write(json_encoder.encode(report).decode("utf-8"))
65
+ sys.stdout.write("\n")
66
+ else:
67
+ status = "ok" if report.ok else "fail"
68
+ print(f"{status} product={report.product} issues={len(report.issues)}")
69
+ for issue in report.issues:
70
+ span = ""
71
+ if issue.start is not None and issue.end is not None:
72
+ span = f" [{issue.start}:{issue.end}]"
73
+ print(f" {issue.severity}:{issue.code}{span} {issue.message}")
74
+
75
+ return 0 if report.ok else 1
76
+
77
+
78
+ if __name__ == "__main__":
79
+ raise SystemExit(main())
@@ -0,0 +1,12 @@
1
+ """Reusable msgspec JSON codec for LintReport (ADR-016)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import msgspec
6
+
7
+ from tac_validate.models import LintReport
8
+
9
+ json_encoder = msgspec.json.Encoder()
10
+ json_decoder = msgspec.json.Decoder(LintReport)
11
+
12
+ __all__ = ["json_decoder", "json_encoder"]
@@ -0,0 +1,77 @@
1
+ """msgspec models for TAC lint issues and fixes (ADR-016 / Q9=C)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import msgspec
6
+
7
+
8
+ class Issue(msgspec.Struct, frozen=True):
9
+ """
10
+ Structured TAC lint finding.
11
+
12
+ Parameters
13
+ ----------
14
+ severity :
15
+ ``error``, ``warning``, or ``info``.
16
+ code :
17
+ Machine-readable rule id.
18
+ message :
19
+ Human-readable description.
20
+ location :
21
+ Optional token / field hint (e.g. ``wind``).
22
+ start :
23
+ Optional inclusive character offset into the TAC string.
24
+ end :
25
+ Optional exclusive character offset into the TAC string.
26
+ """
27
+
28
+ severity: str
29
+ code: str
30
+ message: str
31
+ location: str | None = None
32
+ start: int | None = None
33
+ end: int | None = None
34
+
35
+
36
+ class Fix(msgspec.Struct, frozen=True):
37
+ """
38
+ Optional repair suggestion for a lint finding.
39
+
40
+ Parameters
41
+ ----------
42
+ code :
43
+ Fix identifier (e.g. ``add_terminator``).
44
+ message :
45
+ Human-readable description.
46
+ replacement :
47
+ Suggested replacement fragment or full TAC.
48
+ """
49
+
50
+ code: str
51
+ message: str
52
+ replacement: str
53
+
54
+
55
+ class LintReport(msgspec.Struct, frozen=True):
56
+ """
57
+ Result of ``lint()``.
58
+
59
+ Parameters
60
+ ----------
61
+ ok :
62
+ ``True`` when no error-severity issues.
63
+ product :
64
+ Product id used for rule selection.
65
+ issues :
66
+ Structured findings.
67
+ fixes :
68
+ Optional repair suggestions.
69
+ """
70
+
71
+ ok: bool
72
+ product: str
73
+ issues: list[Issue]
74
+ fixes: list[Fix] = msgspec.field(default_factory=list)
75
+
76
+
77
+ __all__ = ["Fix", "Issue", "LintReport"]