openom-core 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 (93) hide show
  1. openom_core-0.1.0/.gitignore +62 -0
  2. openom_core-0.1.0/.hypothesis/.gitignore +9 -0
  3. openom_core-0.1.0/.hypothesis/constants/001fe83bc75f70ff +4 -0
  4. openom_core-0.1.0/.hypothesis/constants/17983501d9df90f5 +4 -0
  5. openom_core-0.1.0/.hypothesis/constants/1bad5597e3545771 +4 -0
  6. openom_core-0.1.0/.hypothesis/constants/1e120eed62cb9e41 +4 -0
  7. openom_core-0.1.0/.hypothesis/constants/2599456d76b7b639 +4 -0
  8. openom_core-0.1.0/.hypothesis/constants/2599a2740a02ac70 +4 -0
  9. openom_core-0.1.0/.hypothesis/constants/2bbc47f0d389ac49 +4 -0
  10. openom_core-0.1.0/.hypothesis/constants/2dc28f4fae47c5df +4 -0
  11. openom_core-0.1.0/.hypothesis/constants/391bb7a7db122863 +4 -0
  12. openom_core-0.1.0/.hypothesis/constants/3b99d738d4809a52 +4 -0
  13. openom_core-0.1.0/.hypothesis/constants/3bee2e16f7a8fc8e +4 -0
  14. openom_core-0.1.0/.hypothesis/constants/517da07006529f54 +4 -0
  15. openom_core-0.1.0/.hypothesis/constants/5273145f2f12a42a +4 -0
  16. openom_core-0.1.0/.hypothesis/constants/8a59fd8a0a527e9a +4 -0
  17. openom_core-0.1.0/.hypothesis/constants/b82be5d38e8f5622 +4 -0
  18. openom_core-0.1.0/.hypothesis/constants/ccfdabc45d5fe4c6 +4 -0
  19. openom_core-0.1.0/.hypothesis/constants/d7dad3c83a335ac4 +4 -0
  20. openom_core-0.1.0/.hypothesis/constants/de803f019b0b542d +4 -0
  21. openom_core-0.1.0/.hypothesis/constants/e1e4fe9940bd1f03 +4 -0
  22. openom_core-0.1.0/.hypothesis/constants/ec0635288f17334e +4 -0
  23. openom_core-0.1.0/.hypothesis/constants/fa756589e5af3e25 +4 -0
  24. openom_core-0.1.0/.hypothesis/tmp/tmpexu8bi5n +0 -0
  25. openom_core-0.1.0/.hypothesis/tmp/tmpxlvp8s0d +0 -0
  26. openom_core-0.1.0/.hypothesis/unicode_data/14.0.0/charmap.json.gz +0 -0
  27. openom_core-0.1.0/.hypothesis/unicode_data/14.0.0/codec-utf-8.json.gz +0 -0
  28. openom_core-0.1.0/.hypothesis/unicode_data/14.0.0/codec-v2-utf-8.json.gz +0 -0
  29. openom_core-0.1.0/PKG-INFO +55 -0
  30. openom_core-0.1.0/README.md +20 -0
  31. openom_core-0.1.0/pyproject.toml +111 -0
  32. openom_core-0.1.0/scripts/characterize_corpus.py +71 -0
  33. openom_core-0.1.0/scripts/gen_fuzz_corpus.py +87 -0
  34. openom_core-0.1.0/scripts/gen_vectors.py +197 -0
  35. openom_core-0.1.0/src/openom_core/__init__.py +65 -0
  36. openom_core-0.1.0/src/openom_core/canonical.py +166 -0
  37. openom_core-0.1.0/src/openom_core/embed.py +437 -0
  38. openom_core-0.1.0/src/openom_core/errors.py +57 -0
  39. openom_core-0.1.0/src/openom_core/images.py +181 -0
  40. openom_core-0.1.0/src/openom_core/inspect.py +218 -0
  41. openom_core-0.1.0/src/openom_core/om-0.1.schema.json +214 -0
  42. openom_core-0.1.0/src/openom_core/py.typed +0 -0
  43. openom_core-0.1.0/src/openom_core/schema.py +37 -0
  44. openom_core-0.1.0/src/openom_core/summary.py +133 -0
  45. openom_core-0.1.0/src/openom_core/text.py +130 -0
  46. openom_core-0.1.0/src/openom_core/types.py +75 -0
  47. openom_core-0.1.0/src/openom_core/validate.py +475 -0
  48. openom_core-0.1.0/src/openom_core/xmp.py +214 -0
  49. openom_core-0.1.0/tests/_make_rich.py +43 -0
  50. openom_core-0.1.0/tests/_make_scan.py +75 -0
  51. openom_core-0.1.0/tests/_render.py +62 -0
  52. openom_core-0.1.0/tests/conftest.py +79 -0
  53. openom_core-0.1.0/tests/fixtures/gen_producer_fixtures.py +86 -0
  54. openom_core-0.1.0/tests/fixtures/gen_signed_fixture.py +83 -0
  55. openom_core-0.1.0/tests/fixtures/producers/producer-hybrid.pdf +108 -0
  56. openom_core-0.1.0/tests/fixtures/producers/producer-native.pdf +0 -0
  57. openom_core-0.1.0/tests/fixtures/producers/producer-scanned.pdf +250 -0
  58. openom_core-0.1.0/tests/fixtures/signed-approval.pdf +123 -0
  59. openom_core-0.1.0/tests/test_canonical.py +197 -0
  60. openom_core-0.1.0/tests/test_classify_eval.py +44 -0
  61. openom_core-0.1.0/tests/test_codes.py +29 -0
  62. openom_core-0.1.0/tests/test_consistency.py +97 -0
  63. openom_core-0.1.0/tests/test_consistency_real_om.py +86 -0
  64. openom_core-0.1.0/tests/test_crossimpl.py +69 -0
  65. openom_core-0.1.0/tests/test_decrypt_crossimpl.py +34 -0
  66. openom_core-0.1.0/tests/test_embed.py +112 -0
  67. openom_core-0.1.0/tests/test_embed_edges.py +120 -0
  68. openom_core-0.1.0/tests/test_format_assertion.py +33 -0
  69. openom_core-0.1.0/tests/test_fuzz.py +27 -0
  70. openom_core-0.1.0/tests/test_images.py +315 -0
  71. openom_core-0.1.0/tests/test_inspect.py +88 -0
  72. openom_core-0.1.0/tests/test_negatives.py +26 -0
  73. openom_core-0.1.0/tests/test_nondestructive.py +123 -0
  74. openom_core-0.1.0/tests/test_pathology_vectors.py +46 -0
  75. openom_core-0.1.0/tests/test_producers.py +91 -0
  76. openom_core-0.1.0/tests/test_reembed.py +148 -0
  77. openom_core-0.1.0/tests/test_rejections.py +32 -0
  78. openom_core-0.1.0/tests/test_render_extra.py +50 -0
  79. openom_core-0.1.0/tests/test_rfc8785.py +35 -0
  80. openom_core-0.1.0/tests/test_samples.py +68 -0
  81. openom_core-0.1.0/tests/test_schema.py +19 -0
  82. openom_core-0.1.0/tests/test_schema_packaged.py +36 -0
  83. openom_core-0.1.0/tests/test_signed_embed.py +137 -0
  84. openom_core-0.1.0/tests/test_smoke.py +15 -0
  85. openom_core-0.1.0/tests/test_summary.py +46 -0
  86. openom_core-0.1.0/tests/test_summary_crossimpl.py +50 -0
  87. openom_core-0.1.0/tests/test_survival.py +97 -0
  88. openom_core-0.1.0/tests/test_text.py +88 -0
  89. openom_core-0.1.0/tests/test_types.py +45 -0
  90. openom_core-0.1.0/tests/test_validate.py +313 -0
  91. openom_core-0.1.0/tests/test_validate_default_schema.py +43 -0
  92. openom_core-0.1.0/tests/test_vectors.py +81 -0
  93. openom_core-0.1.0/tests/test_xmp.py +169 -0
@@ -0,0 +1,62 @@
1
+ # --- OpenOM repo hygiene (see CLAUDE.md Rule 2) ---
2
+
3
+ # Marketing mock - NEVER committed
4
+ OpenOM - Make your deal legible to the buy-side's AI.htm
5
+ OpenOM - Make your deal legible to the buy-side's AI_files/
6
+
7
+ # Planning scratch - git-ignored by rule
8
+ docs/superpowers/
9
+ .planning/
10
+
11
+ # Eval / tooling scratch
12
+ .deepeval/
13
+
14
+ # Real OM corpus - private test fixtures, never committed (confidential)
15
+ OMs/
16
+
17
+ # --- Python (/core, /cli, /mcp) ---
18
+ __pycache__/
19
+ *.py[cod]
20
+ *.egg-info/
21
+ .eggs/
22
+ build/
23
+ dist/
24
+ .venv/
25
+ venv/
26
+ .env
27
+ .mypy_cache/
28
+ .ruff_cache/
29
+ .pytest_cache/
30
+ .coverage
31
+ htmlcov/
32
+
33
+ # --- Node / TS (/js, /extension) ---
34
+ node_modules/
35
+ *.tsbuildinfo
36
+ .npm/
37
+ extension/src/generated/
38
+ extension/test-results/
39
+ extension/playwright-report/
40
+
41
+ # --- OS / editor ---
42
+ .DS_Store
43
+ Thumbs.db
44
+ .idea/
45
+ .vscode/
46
+ coverage/
47
+ extension/*.zip
48
+
49
+ js/test-results/
50
+
51
+ # marketing mock (Rule 2 - never commit)
52
+ *.htm
53
+ *_files/
54
+ .playwright-mcp/
55
+ om-normative-spec-draft-v0.1.md
56
+ om-standard-handoff-v4-updated.md
57
+ recon.md
58
+ openOM-decision-memo.md
59
+
60
+ # audit/backlog scratch is temp - lives in .planning/ (see Rule 2)
61
+ /BACKLOG.md
62
+ /*-audit*.md
@@ -0,0 +1,9 @@
1
+ # This .gitignore file was automatically created by Hypothesis. Hypothesis gitignores
2
+ # .hypothesis by default, because we generally recommend that .hypothesis not be checked
3
+ # into version control.
4
+ #
5
+ # If you *would* like to check .hypothesis into version control, you should delete this
6
+ # file. Hypothesis will not re-create this .gitignore unless .hypothesis is deleted (and
7
+ # if it does, that's a bug - please report it!)
8
+
9
+ *
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\xmp.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ [b'\xef\xbb\xbf', ' </rdf:RDF>', '&', '&amp;', '&gt;', '&lt;', '/Metadata', '<', '</rdf:RDF>', '>', 'assertedDate', 'omspec', 'openOM', 'payloadFilename', 'payloadHash', 'replace', 'sourceDocHash', 'specName', 'specVersion', 'supersedes', 'utf-8', '{', '}']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\__init__.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ ['0.0.0+unknown', '0.1', 'DealSummary', 'Finding', 'ImageManifest', 'OMPayload', 'PayloadTooLargeError', 'Profile', 'ReadResult', 'RealEstateListing', 'Report', 'SPEC_VERSION', 'Severity', 'TextResult', 'Tolerances', '__version__', 'canonicalize', 'classify', 'embed', 'extract_images', 'extract_text', 'hash_bytes', 'load_schema', 'openom-core', 'payload_hash', 'read', 'reembed_warnings', 'summarize_deal', 'validate']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\images.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ [100000000, 'DeviceGray', 'DeviceRGB', 'colorspace', 'contentHash', 'deduped', 'error', 'hasSMask', 'height', 'image/png', 'images', 'mime', 'path', 'pdf', 'sha256:', 'unknown', 'width', 'xref']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\schema.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ ['om-0.1.schema.json', 'openom_core', 'spec', 'utf-8']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\text.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ [100000, ',', '-', ':', 'ascii', 'h', 'malformed cursor', 'nextCursor', 'off', 'page', 'pageRange', 'pdf', 'rows', 'tables', 'text', 'truncated']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\canonical.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ [55296, 57343, 'NFC', 'meta', 'sha256:', 'signature']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\images.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ [1.0, 2.0, 100000000, 'DeviceGray', 'DeviceRGB', 'colorspace', 'contentHash', 'deduped', 'error', 'hasSMask', 'height', 'image/png', 'images', 'mime', 'page', 'path', 'pdf', 'rendered-page', 'sha256:', 'source', 'unknown', 'width', 'xref']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\inspect.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ [0.0, 0.2, 0.5, 0.6, 0.8, 0.85, 1.0, 3.0, 100, 'Profile', 'bbox', 'chars', 'class', 'classConfidence', 'colorspaces', 'count', 'hasSMask', 'hashValid', 'hybrid', 'images', 'native', 'ocrOverlay', 'pages', 'payload', 'pdf', 'present', 'scanned', 'specVersion', 'text', 'textCoverage', 'type', 'unknown']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\xmp.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ [b'\xef\xbb\xbf', ' </rdf:RDF>', '&', '&amp;', '&gt;', '&lt;', '/Metadata', '<', '</rdf:RDF>', '>', 'assertedDate', 'omspec', 'openOM', 'payloadFilename', 'payloadHash', 'replace', 'specName', 'specVersion', 'supersedes', 'utf-8', '{', '}']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\summary.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ [', ', 'USD', 'address', 'addressLocality', 'addressRegion', 'askingPrice', 'assertedBy', 'assertedDate', 'broker', 'brokerage', 'buildingSF', 'capRate', 'commencement', 'currency', 'deal', 'expiration', 'lease', 'leaseTypeAsserted', 'license', 'noi', 'noiAsOfDate', 'noiType', 'postalCode', 'pricePerSF', 'pricePerUnit', 'property', 'propertyType', 'streetAddress', 'tenantEntity', 'termMonths', 'units']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\canonical.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ [55296, 57343, 'NFC', 'meta', 'sha256:', 'signature']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\embed.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ [5000000, '/AF', '/DP', '/DecodeParms', '/EF', '/F', '/Filter', '/FlateDecode', '/UF', '/application/ld+json', '/assertedDate', '0.1', 'OMW-W051', 'application/ld+json', 'assertedDate', 'om.json', 'payloadHash', 'specVersion', 'supersedes', 'warning']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\validate.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ [0.005, 0.01, 0.02, 0.2, 30.4375, 31.0, "'", '-', '/', '/assertedDate', '/currency', '/deal', '/deal/askingPrice', '/deal/capRate', '/deal/noi', '/deal/noiAsOfDate', '/deal/pricePerSF', '/lease/expiration', '/lease/rentSchedule', '/lease/termMonths', '/meta/signature', '/meta/supersedes', '/property/buildingSF', 'E', 'I', 'NN', 'NNN', 'OM-CONS-010', 'OM-CONS-011', 'OM-CONS-012', 'OM-CONS-013', 'OM-CONS-014', 'OM-CONS-020', 'OM-CONS-021', 'OM-CONS-022', 'OM-CONS-023', 'OM-CONS-024', 'OM-CONS-025', 'OM-CONS-026', 'OM-CONS-030', 'OM-CONS-031', 'OM-CONS-032', 'OM-CONS-033', 'OM-CONS-034', 'OM-CONS-040', 'OM-CONS-041', 'OM-CONS-050', 'OM-CONS-060', 'OM-DD', 'OM-DD-001', 'OM-DD-002', 'OM-DD-004', 'OM-ERR-013', 'OM-ERR-014', 'OM-ERR-090', 'OMI-I001', 'OMI-I002', 'OMI-I003', 'OMV-E001', 'OMV-E002', 'OMV-E003', 'OMV-E010', 'OMW-W010', 'OMW-W011', 'OMW-W012', 'OMW-W013', 'OMW-W014', 'OMW-W020', 'OMW-W021', 'OMW-W022', 'OMW-W023', 'OMW-W024', 'OMW-W025', 'OMW-W026', 'OMW-W030', 'OMW-W031', 'OMW-W032', 'OMW-W033', 'OMW-W034', 'OMW-W040', 'OMW-W041', 'OMW-W050', 'OMW-W060', 'W', 'absolute-net', 'annualRent', 'askingPrice', 'assertedDate', 'buildingSF', 'cam', 'capRate', 'commencement', 'currency', 'deal', 'error', 'escalationFromPrior', 'expiration', 'gross', 'hvac', 'in-place', 'inf', 'info', 'insurance', 'lease', 'leaseTypeAsserted', 'meta', 'modified-gross', 'monthlyRent', 'noi', 'noiAsOfDate', 'noiType', 'parking', 'periodEnd', 'periodStart', 'pricePerSF', 'pro-forma', 'property', 'remainingTermMonths', 'rentPSF', 'rentSchedule', 'required', 'roof', 'signature', 'source', 'structure', 'supersedes', 'taxes', 'termMonths', 'verified', 'warning']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\inspect.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ [0.2, 0.5, 0.8, 0.85, 1.0, 3.0, 100, 'Profile', 'bbox', 'class', 'classConfidence', 'colorspaces', 'count', 'hasSMask', 'hashValid', 'hybrid', 'images', 'native', 'pages', 'payload', 'pdf', 'present', 'scanned', 'specVersion', 'text', 'textCoverage', 'unknown']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\xmp.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ [b'\xef\xbb\xbf', ' </rdf:RDF>', '&', '&amp;', '&gt;', '&lt;', '/Metadata', '<', '</rdf:RDF>', '>', 'assertedDate', 'omspec', 'openOM', 'payloadFilename', 'payloadHash', 'replace', 'specName', 'specVersion', 'supersedes', 'utf-8', '{', '}']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\types.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ ['@context', '@type', 'OMPayload', 'assertedBy', 'assertedDate', 'currency', 'deal', 'ext', 'lease', 'meta', 'property', 'specVersion']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\__init__.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ ['0.1.0.dev0']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\validate.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ [0.005, 0.01, 0.02, 0.2, 30.4375, 31.0, "'", '-', '/', '/assertedDate', '/currency', '/deal', '/deal/askingPrice', '/deal/capRate', '/deal/noi', '/deal/noiAsOfDate', '/deal/pricePerSF', '/lease/expiration', '/lease/rentSchedule', '/lease/termMonths', '/meta/signature', '/meta/supersedes', '/property/buildingSF', 'E', 'I', 'NN', 'NNN', 'OM-CONS-010', 'OM-CONS-011', 'OM-CONS-012', 'OM-CONS-013', 'OM-CONS-014', 'OM-CONS-020', 'OM-CONS-021', 'OM-CONS-022', 'OM-CONS-023', 'OM-CONS-024', 'OM-CONS-025', 'OM-CONS-026', 'OM-CONS-030', 'OM-CONS-031', 'OM-CONS-032', 'OM-CONS-033', 'OM-CONS-034', 'OM-CONS-040', 'OM-CONS-041', 'OM-CONS-050', 'OM-CONS-060', 'OM-DD-001', 'OM-DD-002', 'OM-DD-003', 'OM-DD-004', 'OM-ERR-013', 'OM-ERR-014', 'OM-ERR-090', 'OM-TRUST-009', 'OM-TRUST-010', 'OMI-I001', 'OMI-I002', 'OMI-I003', 'OMV-E001', 'OMV-E002', 'OMV-E003', 'OMV-E010', 'OMW-W010', 'OMW-W011', 'OMW-W012', 'OMW-W013', 'OMW-W014', 'OMW-W020', 'OMW-W021', 'OMW-W022', 'OMW-W023', 'OMW-W024', 'OMW-W025', 'OMW-W026', 'OMW-W030', 'OMW-W031', 'OMW-W032', 'OMW-W033', 'OMW-W034', 'OMW-W040', 'OMW-W041', 'OMW-W050', 'OMW-W051', 'OMW-W052', 'OMW-W060', 'OMW-W061', 'US', 'W', 'absolute-net', 'address', 'addressCountry', 'annualRent', 'askingPrice', 'assertedDate', 'buildingSF', 'cam', 'capRate', 'commencement', 'currency', 'deal', 'error', 'escalationFromPrior', 'expiration', 'gross', 'hvac', 'in-place', 'inf', 'info', 'insurance', 'lease', 'leaseTypeAsserted', 'meta', 'modified-gross', 'monthlyRent', 'noi', 'noiAsOfDate', 'noiType', 'parking', 'periodEnd', 'periodStart', 'pricePerSF', 'pro-forma', 'property', 'remainingTermMonths', 'rentPSF', 'rentSchedule', 'required', 'roof', 'source', 'structure', 'supersedes', 'taxes', 'termMonths', 'verified', 'warning']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\errors.py
2
+ # hypothesis_version: 6.165.9
3
+
4
+ ['OM-IO-BADUTF8', 'OM-IO-BOMB', 'OM-IO-DUPKEY', 'OM-IO-NUMRANGE', 'OM-IO-STRUCTURE', 'error', 'info', 'warning']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\embed.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ [5000000, '/AF', '/DP', '/DecodeParms', '/EF', '/F', '/Filter', '/FlateDecode', '/UF', '/application/ld+json', '/assertedDate', '0.1', 'OMW-W051', 'application/ld+json', 'assertedDate', 'om.json', 'payloadHash', 'sourceDocHash', 'specVersion', 'supersedes', 'warning']
@@ -0,0 +1,4 @@
1
+ # file: E:\OpenOM\core\src\openom_core\text.py
2
+ # hypothesis_version: 6.155.2
3
+
4
+ [100000, ',', '-', ':', 'ascii', 'h', 'malformed cursor', 'nextCursor', 'off', 'page', 'pageRange', 'pdf', 'rows', 'tables', 'text', 'truncated']
@@ -0,0 +1,55 @@
1
+ Metadata-Version: 2.5
2
+ Name: openom-core
3
+ Version: 0.1.0
4
+ Summary: openOM deterministic core - embed/read/inspect/validate CRE offering-memorandum payloads. Zero inference, ever.
5
+ Project-URL: Homepage, https://openom.app
6
+ Project-URL: Documentation, https://openom.app/docs/
7
+ Project-URL: Repository, https://github.com/Vervelio-Labs/OpenOM
8
+ License-Expression: MIT
9
+ Keywords: commercial-real-estate,cre,data-standard,factur-x,json-ld,json-schema,offering-memorandum,open-standard,openom,pdf,pdf-a-3,proptech,real-estate
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Topic :: Office/Business :: Financial
15
+ Classifier: Topic :: Text Processing :: Markup
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.11
18
+ Requires-Dist: jsonschema>=4.20
19
+ Requires-Dist: pikepdf>=9
20
+ Requires-Dist: rfc8785>=0.1.4
21
+ Provides-Extra: dev
22
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
23
+ Requires-Dist: mypy>=1.10; extra == 'dev'
24
+ Requires-Dist: numpy>=1.26; extra == 'dev'
25
+ Requires-Dist: pillow>=10; extra == 'dev'
26
+ Requires-Dist: pymupdf>=1.24; extra == 'dev'
27
+ Requires-Dist: pypdfium2>=4; extra == 'dev'
28
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
29
+ Requires-Dist: pytest>=8; extra == 'dev'
30
+ Requires-Dist: ruff>=0.5; extra == 'dev'
31
+ Requires-Dist: types-jsonschema>=4.20; extra == 'dev'
32
+ Provides-Extra: render
33
+ Requires-Dist: pymupdf>=1.24; extra == 'render'
34
+ Description-Content-Type: text/markdown
35
+
36
+ # openom-core
37
+
38
+ The deterministic Python core of the [openOM](../README.md) standard: the PDF/data verbs
39
+ (`embed`, `read`, `inspect`, `extract`, `validate`) and RFC 8785 canonicalization + SHA-256
40
+ integrity. **Zero inference, ever** (CI's `boundary` job enforces it). Byte-parity with `js/`.
41
+
42
+ ```sh
43
+ # From a clone (openom-core is not yet on PyPI):
44
+ pip install -e core # add the [dev] extra only to contribute
45
+ # Once published: pip install openom-core
46
+ ```
47
+
48
+ ```python
49
+ from openom_core.embed import embed, read
50
+ out = embed(pdf_bytes, payload, asserted_date="2026-08-16") # non-destructive
51
+ r = read(out) # r.present / r.hash_valid / r.payload
52
+ ```
53
+
54
+ Entrypoints: `openom_core.{canonical,embed,validate,inspect,images,text,schema,xmp}`.
55
+ Tests: `pytest core -q --cov=openom_core --cov-fail-under=90`. Schema + vectors live in [`/spec`](../spec).
@@ -0,0 +1,20 @@
1
+ # openom-core
2
+
3
+ The deterministic Python core of the [openOM](../README.md) standard: the PDF/data verbs
4
+ (`embed`, `read`, `inspect`, `extract`, `validate`) and RFC 8785 canonicalization + SHA-256
5
+ integrity. **Zero inference, ever** (CI's `boundary` job enforces it). Byte-parity with `js/`.
6
+
7
+ ```sh
8
+ # From a clone (openom-core is not yet on PyPI):
9
+ pip install -e core # add the [dev] extra only to contribute
10
+ # Once published: pip install openom-core
11
+ ```
12
+
13
+ ```python
14
+ from openom_core.embed import embed, read
15
+ out = embed(pdf_bytes, payload, asserted_date="2026-08-16") # non-destructive
16
+ r = read(out) # r.present / r.hash_valid / r.payload
17
+ ```
18
+
19
+ Entrypoints: `openom_core.{canonical,embed,validate,inspect,images,text,schema,xmp}`.
20
+ Tests: `pytest core -q --cov=openom_core --cov-fail-under=90`. Schema + vectors live in [`/spec`](../spec).
@@ -0,0 +1,111 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "openom-core"
7
+ version = "0.1.0"
8
+ description = "openOM deterministic core - embed/read/inspect/validate CRE offering-memorandum payloads. Zero inference, ever."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = "MIT"
12
+ keywords = [
13
+ "openom", "offering-memorandum", "commercial-real-estate", "cre", "real-estate", "proptech",
14
+ "pdf", "pdf-a-3", "factur-x", "json-ld", "json-schema", "open-standard", "data-standard",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 4 - Beta",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Topic :: Office/Business :: Financial",
22
+ "Topic :: Text Processing :: Markup",
23
+ "Typing :: Typed",
24
+ ]
25
+ # The embed/read/validate/canonical core is pikepdf-only, so `openom-core` stays MIT-clean. PyMuPDF
26
+ # (AGPL-3.0-or-commercial) powers only extract/inspect/render + the signed-OM incremental save, so it
27
+ # is an OPTIONAL `[render]` extra - a portal / commercial integrator bundling the core does not inherit
28
+ # AGPL unless they opt into rendering.
29
+ dependencies = [
30
+ "pikepdf>=9",
31
+ "rfc8785>=0.1.4",
32
+ "jsonschema>=4.20",
33
+ ]
34
+
35
+ [project.optional-dependencies]
36
+ render = [
37
+ "PyMuPDF>=1.24", # AGPL-3.0-or-commercial - powers extract/inspect/render + signed-incremental embed
38
+ ]
39
+ dev = [
40
+ "openom-core[render]", # tests exercise extract/inspect/render, so dev pulls the render extra
41
+ "pytest>=8",
42
+ "pytest-cov>=5",
43
+ "mypy>=1.10",
44
+ "ruff>=0.5",
45
+ "numpy>=1.26",
46
+ "pillow>=10",
47
+ "hypothesis>=6.100",
48
+ "types-jsonschema>=4.20",
49
+ "pypdfium2>=4", # independent 2nd PDF renderer - the differential image cross-check (#167)
50
+ ]
51
+
52
+ [project.urls]
53
+ Homepage = "https://openom.app"
54
+ Documentation = "https://openom.app/docs/"
55
+ Repository = "https://github.com/Vervelio-Labs/OpenOM"
56
+
57
+ [tool.hatch.build.targets.wheel]
58
+ packages = ["src/openom_core"]
59
+
60
+ # Ship the JSON Schema INSIDE the package (#149) so a pip-installed openom-core/-mcp can load it via
61
+ # importlib.resources - no reliance on an adjacent /spec dir. It is committed as ordinary package data
62
+ # at src/openom_core/om-0.1.schema.json (NOT a force-include from ../spec, which cannot survive into a
63
+ # self-contained sdist -> `python -m build` would fail building the wheel from the sdist). /spec stays
64
+ # the single source of truth; the in-package copy is drift-locked to it by tests/test_schema_packaged.py.
65
+
66
+ [tool.ruff]
67
+ line-length = 100
68
+ src = ["src", "tests"]
69
+
70
+ [tool.ruff.lint]
71
+ select = ["E", "F", "I", "B", "UP", "W"]
72
+
73
+ [tool.mypy]
74
+ python_version = "3.11"
75
+ strict = true
76
+ files = ["src"]
77
+
78
+ # PyMuPDF ships incomplete type info; treat it as untyped so strict no-untyped-call
79
+ # doesn't fire on its API (we still type our own wrappers around it).
80
+ [[tool.mypy.overrides]]
81
+ module = ["pymupdf", "pymupdf.*"]
82
+ follow_imports = "skip"
83
+ disallow_untyped_calls = false
84
+
85
+ [tool.pytest.ini_options]
86
+ testpaths = ["tests"]
87
+ addopts = "-ra"
88
+ markers = [
89
+ "cross_impl: cross-implementation round-trip ([OM-VEC-002]); needs Node + OPENOM_XIMPL_DIR",
90
+ ]
91
+ # Coverage is enforced in CI (pytest --cov=openom_core --cov-fail-under=90), not in the
92
+ # default addopts, so single-file dev runs stay fast and don't trip the floor.
93
+
94
+ [tool.coverage.run]
95
+ source = ["openom_core"]
96
+ branch = true
97
+
98
+ [tool.coverage.report]
99
+ show_missing = true
100
+ exclude_lines = ["pragma: no cover", "raise NotImplementedError", "if TYPE_CHECKING:"]
101
+
102
+ # Mutation testing (#133) - assertion-STRENGTH, beyond line/branch coverage. Targets the correctness-
103
+ # critical modules where a flipped comparison or wrong rounding would produce a divergent-but-passing
104
+ # standard. Runs on a schedule (not per-push); surviving mutants are tracked as coverage debt.
105
+ [tool.mutmut]
106
+ paths_to_mutate = [
107
+ "src/openom_core/canonical.py",
108
+ "src/openom_core/validate.py",
109
+ "src/openom_core/embed.py",
110
+ ]
111
+ tests_dir = ["tests/"]
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env python
2
+ # SPDX-License-Identifier: MIT
3
+ """Local corpus characterization / classifier-tuning tool (Task 8).
4
+
5
+ Runs ``inspect`` over every PDF under ``OMs/`` (the confidential, git-ignored corpus) and
6
+ prints class, confidence, textCoverage, and page count. Given a labels file
7
+ (JSON ``{relpath: "native"|"hybrid"|"scanned"}``) it also prints a confusion matrix so
8
+ thresholds can be tuned against real ground truth.
9
+
10
+ python core/scripts/characterize_corpus.py [--labels labels.json]
11
+
12
+ Not a CI test: the corpus is not committed, and ground-truth labels require human judgment (Q3).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ from collections import Counter
20
+ from pathlib import Path
21
+
22
+ from openom_core.inspect import inspect
23
+
24
+ ROOT = Path(__file__).resolve().parents[2]
25
+ OMS = ROOT / "OMs"
26
+
27
+
28
+ def main() -> None:
29
+ parser = argparse.ArgumentParser(description=__doc__)
30
+ parser.add_argument("--labels", type=Path, default=None, help="JSON {relpath: class}")
31
+ args = parser.parse_args()
32
+
33
+ if not OMS.exists():
34
+ print(f"corpus not present: {OMS}")
35
+ return
36
+
37
+ labels: dict[str, str] = (
38
+ json.loads(args.labels.read_text(encoding="utf-8")) if args.labels else {}
39
+ )
40
+ dist: Counter[str] = Counter()
41
+ confusion: Counter[tuple[str, str]] = Counter()
42
+ pdfs = sorted(OMS.rglob("*.pdf"))
43
+
44
+ for pdf in pdfs:
45
+ rel = pdf.relative_to(OMS).as_posix()
46
+ try:
47
+ prof = inspect(pdf.read_bytes())
48
+ except Exception as exc: # noqa: BLE001 - tool: report and keep going
49
+ print(f"ERROR {rel}: {exc}")
50
+ continue
51
+ cls = prof["class"]
52
+ dist[cls] += 1
53
+ print(
54
+ f"{cls:8} conf={prof['classConfidence']:.2f} tc={prof['textCoverage']:.2f} "
55
+ f"pages={prof['pages']:>3} {rel}"
56
+ )
57
+ if rel in labels:
58
+ confusion[(labels[rel], cls)] += 1
59
+
60
+ print(f"\nDISTRIBUTION over {len(pdfs)} files: {dict(dist)}")
61
+ if confusion:
62
+ correct = sum(n for (t, p), n in confusion.items() if t == p)
63
+ total = sum(confusion.values())
64
+ print(f"ACCURACY: {correct}/{total}")
65
+ print("CONFUSION (true -> predicted):")
66
+ for (t, p), n in sorted(confusion.items()):
67
+ print(f" {t:8} -> {p:8}: {n}{'' if t == p else ' <-- MISS'}")
68
+
69
+
70
+ if __name__ == "__main__":
71
+ main()
@@ -0,0 +1,87 @@
1
+ #!/usr/bin/env python
2
+ # SPDX-License-Identifier: MIT
3
+ """Generate the cross-language JCS differential-fuzz corpus (#129).
4
+
5
+ The RFC 8785 canonicalization is the anti-fork keystone: if the Python and TS reference impls ever
6
+ serialize the same value to different bytes, a PDF verifies in one client and fails in another.
7
+ This produces a DETERMINISTIC, edge-weighted corpus of accepted payloads + their canonical hashes.
8
+ Both cores canonicalize the SAME corpus and MUST match `expected` byte-for-byte (test_fuzz.py /
9
+ fuzz.test.ts). The generator is seeded (no Date/urandom) so re-running is a no-op - a diff means a
10
+ hash moved. Run after any canonicalization change: python core/scripts/gen_fuzz_corpus.py
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ import random
17
+ from pathlib import Path
18
+
19
+ from openom_core.canonical import CanonicalizationError, canonicalize, hash_bytes
20
+
21
+ OUT = Path(__file__).resolve().parents[2] / "spec" / "vectors" / "fuzz"
22
+ SEED = 20260818 # fixed seed -> reproducible corpus (no Date/urandom)
23
+ COUNT = 600
24
+
25
+ # Values that historically fork JCS number/string serialization - the fuzz weights toward these.
26
+ _NUM_EDGES = [
27
+ 0, -0.0, 1, -1, 2**53 - 1, -(2**53 - 1), 0.1, -0.1, 0.5, 1.5,
28
+ 1e-7, 1.0000001, 9.999999e-7, 0.0000001, 123456.789, 1e-6, 1e-8,
29
+ 3.141592653589793, 2.718281828459045, 100.0, 0.0001, 1234567890, 42,
30
+ ]
31
+ # Non-ASCII given as escapes so the source file stays pure ASCII (avoids encoding surprises).
32
+ _STR_EDGES = [
33
+ "", "a", "A", "z", "é", "café", # e-acute (NFC precomposed)
34
+ "\U0001f600", "\U0001f4a9 tail", # astral / surrogate-pair strings
35
+ "line1\nline2", "tab\there", 'quote"q', "back\\slash", "slash/fwd",
36
+ "中文", "key sort z", "key sort a",
37
+ ]
38
+ _KEY_EDGES = ["a", "b", "A", "z", "é", "\U0001f600key", "10", "2", "中", "Z", "aa"]
39
+
40
+
41
+ def _rand_value(rng: random.Random, depth: int) -> object:
42
+ if depth >= 4:
43
+ kind = rng.choice(["num", "str", "bool", "null"])
44
+ else:
45
+ kind = rng.choices(
46
+ ["num", "str", "bool", "null", "arr", "obj"], weights=[30, 30, 8, 8, 12, 12]
47
+ )[0]
48
+ if kind == "num":
49
+ v = rng.choice(_NUM_EDGES)
50
+ return v if rng.random() < 0.7 else rng.uniform(-1e6, 1e6)
51
+ if kind == "str":
52
+ if rng.random() < 0.8:
53
+ return rng.choice(_STR_EDGES)
54
+ return "".join(rng.choice("abcXYZ é中") for _ in range(rng.randint(0, 8)))
55
+ if kind == "bool":
56
+ return rng.random() < 0.5
57
+ if kind == "null":
58
+ return None
59
+ if kind == "arr":
60
+ return [_rand_value(rng, depth + 1) for _ in range(rng.randint(0, 4))]
61
+ keys = rng.sample(_KEY_EDGES, k=rng.randint(1, min(5, len(_KEY_EDGES))))
62
+ return {k: _rand_value(rng, depth + 1) for k in keys}
63
+
64
+
65
+ def main() -> None:
66
+ OUT.mkdir(parents=True, exist_ok=True)
67
+ rng = random.Random(SEED)
68
+ corpus: list[dict] = []
69
+ while len(corpus) < COUNT:
70
+ keys = rng.sample(_KEY_EDGES, k=rng.randint(1, 5))
71
+ obj = {k: _rand_value(rng, 1) for k in keys}
72
+ try:
73
+ canonicalize(obj) # keep only inputs BOTH impls accept (skip range/NFC-dup rejections)
74
+ except CanonicalizationError:
75
+ continue
76
+ corpus.append(obj)
77
+
78
+ dumped = (json.dumps(o, ensure_ascii=False, sort_keys=False) for o in corpus)
79
+ corpus_txt = "\n".join(dumped) + "\n"
80
+ expected_txt = "\n".join(hash_bytes(canonicalize(o)) for o in corpus) + "\n"
81
+ (OUT / "corpus.jsonl").write_text(corpus_txt, encoding="utf-8", newline="\n")
82
+ (OUT / "expected.jsonl").write_text(expected_txt, encoding="utf-8", newline="\n")
83
+ print(f"wrote {len(corpus)} fuzz vectors + expected hashes -> {OUT}")
84
+
85
+
86
+ if __name__ == "__main__":
87
+ main()