document-ocr 0.0.0-stage → 3.1.3

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 (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +260 -2
  3. package/dist/index.cjs +659 -0
  4. package/dist/index.cjs.map +1 -0
  5. package/dist/index.d.cts +293 -0
  6. package/dist/index.d.ts +293 -0
  7. package/dist/index.js +616 -0
  8. package/dist/index.js.map +1 -0
  9. package/hosting/.dockerignore +17 -0
  10. package/hosting/.gcloudignore +14 -0
  11. package/hosting/FEATURES.md +172 -0
  12. package/hosting/HOSTING.md +268 -0
  13. package/hosting/LICENSE +21 -0
  14. package/hosting/README.md +369 -0
  15. package/hosting/core/__init__.py +0 -0
  16. package/hosting/core/aadhaar_extractor.py +298 -0
  17. package/hosting/core/back_page_extractor.py +492 -0
  18. package/hosting/core/barcodes.py +125 -0
  19. package/hosting/core/card_recovery.py +244 -0
  20. package/hosting/core/document_bundle.py +82 -0
  21. package/hosting/core/document_classifier.py +233 -0
  22. package/hosting/core/document_input.py +135 -0
  23. package/hosting/core/document_registry.py +143 -0
  24. package/hosting/core/driving_licence_extractor.py +510 -0
  25. package/hosting/core/ead_recovery.py +54 -0
  26. package/hosting/core/evidence.py +54 -0
  27. package/hosting/core/form_recovery.py +61 -0
  28. package/hosting/core/jobs.py +232 -0
  29. package/hosting/core/kyc_ocr.py +132 -0
  30. package/hosting/core/kyc_validation.py +411 -0
  31. package/hosting/core/model_setup.py +13 -0
  32. package/hosting/core/mrz_parser.py +336 -0
  33. package/hosting/core/mrz_recovery.py +193 -0
  34. package/hosting/core/npr_extractor.py +410 -0
  35. package/hosting/core/nrega_extractor.py +827 -0
  36. package/hosting/core/ocr_engine.py +207 -0
  37. package/hosting/core/page_classifier.py +137 -0
  38. package/hosting/core/pan_extractor.py +183 -0
  39. package/hosting/core/pipeline.py +712 -0
  40. package/hosting/core/preprocessor.py +297 -0
  41. package/hosting/core/recovery_budget.py +67 -0
  42. package/hosting/core/redaction.py +48 -0
  43. package/hosting/core/review.html +244 -0
  44. package/hosting/core/structured_documents.py +422 -0
  45. package/hosting/core/travel_mrz.py +164 -0
  46. package/hosting/core/travel_recovery.py +187 -0
  47. package/hosting/core/validator.py +396 -0
  48. package/hosting/core/validators.py +179 -0
  49. package/hosting/core/voter_id_extractor.py +363 -0
  50. package/hosting/deploy/cloudflare/deploy.sh +44 -0
  51. package/hosting/deploy/cloudflare/package-lock.json +3088 -0
  52. package/hosting/deploy/cloudflare/package.json +29 -0
  53. package/hosting/deploy/cloudflare/src/gateway.ts +42 -0
  54. package/hosting/deploy/cloudflare/src/index.ts +21 -0
  55. package/hosting/deploy/cloudflare/test/gateway.test.ts +59 -0
  56. package/hosting/deploy/cloudflare/tsconfig.json +12 -0
  57. package/hosting/deploy/cloudflare/worker-configuration.d.ts +16253 -0
  58. package/hosting/deploy/cloudflare/wrangler.jsonc +26 -0
  59. package/hosting/deploy/cloudrun/cloudbuild.yaml +18 -0
  60. package/hosting/deploy/cloudrun/deploy.sh +71 -0
  61. package/hosting/deploy/cloudrun/render_service.py +47 -0
  62. package/hosting/deploy/common.sh +14 -0
  63. package/hosting/deploy/docker/Dockerfile +16 -0
  64. package/hosting/deploy/docker/server.py +313 -0
  65. package/hosting/deploy/lambda/Dockerfile.lambda +11 -0
  66. package/hosting/deploy/lambda/deploy.sh +22 -0
  67. package/hosting/deploy/lambda/handler.py +132 -0
  68. package/hosting/deploy/lambda/template.yaml +59 -0
  69. package/hosting/deploy/local/dev.sh +18 -0
  70. package/hosting/deploy/server/Caddyfile +5 -0
  71. package/hosting/deploy/server/compose.jobs.yaml +36 -0
  72. package/hosting/deploy/server/compose.yaml +38 -0
  73. package/hosting/deploy/server/deploy.sh +23 -0
  74. package/hosting/infra/cloudrun/main.tf +104 -0
  75. package/hosting/infra/cloudrun/terraform.tfvars.example +7 -0
  76. package/hosting/infra/cloudrun/variables.tf +55 -0
  77. package/hosting/infra/lambda/main.tf +63 -0
  78. package/hosting/infra/lambda/terraform.tfvars.example +6 -0
  79. package/hosting/infra/lambda/variables.tf +37 -0
  80. package/hosting/pyproject.toml +65 -0
  81. package/hosting/requirements.lock +134 -0
  82. package/package.json +76 -4
  83. package/python/core/__init__.py +0 -0
  84. package/python/core/aadhaar_extractor.py +298 -0
  85. package/python/core/back_page_extractor.py +492 -0
  86. package/python/core/barcodes.py +125 -0
  87. package/python/core/card_recovery.py +244 -0
  88. package/python/core/document_bundle.py +82 -0
  89. package/python/core/document_classifier.py +233 -0
  90. package/python/core/document_input.py +135 -0
  91. package/python/core/document_registry.py +143 -0
  92. package/python/core/driving_licence_extractor.py +510 -0
  93. package/python/core/ead_recovery.py +54 -0
  94. package/python/core/evidence.py +54 -0
  95. package/python/core/form_recovery.py +61 -0
  96. package/python/core/jobs.py +232 -0
  97. package/python/core/kyc_ocr.py +132 -0
  98. package/python/core/kyc_validation.py +411 -0
  99. package/python/core/model_setup.py +13 -0
  100. package/python/core/mrz_parser.py +336 -0
  101. package/python/core/mrz_recovery.py +193 -0
  102. package/python/core/npr_extractor.py +410 -0
  103. package/python/core/nrega_extractor.py +827 -0
  104. package/python/core/ocr_engine.py +207 -0
  105. package/python/core/page_classifier.py +137 -0
  106. package/python/core/pan_extractor.py +183 -0
  107. package/python/core/pipeline.py +712 -0
  108. package/python/core/preprocessor.py +297 -0
  109. package/python/core/recovery_budget.py +67 -0
  110. package/python/core/redaction.py +48 -0
  111. package/python/core/review.html +244 -0
  112. package/python/core/structured_documents.py +422 -0
  113. package/python/core/travel_mrz.py +164 -0
  114. package/python/core/travel_recovery.py +187 -0
  115. package/python/core/validator.py +396 -0
  116. package/python/core/validators.py +179 -0
  117. package/python/core/voter_id_extractor.py +363 -0
  118. package/python/pyproject.toml +34 -0
  119. package/python/requirements.lock +134 -0
  120. package/python/server.py +313 -0
  121. package/scripts/dococr.mjs +163 -0
  122. package/scripts/postinstall.mjs +173 -0
  123. package/scripts/sync-hosting.mjs +35 -0
  124. package/scripts/sync-python.sh +20 -0
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * postinstall script for document-ocr
5
+ *
6
+ * Sets up a Python virtual environment and installs dependencies
7
+ * required for local document OCR processing.
8
+ *
9
+ * Uses uv to install a compatible Python version (3.14) if the system
10
+ * Python is a different version or missing.
11
+ *
12
+ * Exits 0 even on failure — errors will surface on first scan() call.
13
+ */
14
+
15
+ import { execFileSync } from 'node:child_process'
16
+ import { existsSync, rmSync, writeFileSync } from 'node:fs'
17
+ import { join, dirname } from 'node:path'
18
+ import { fileURLToPath } from 'node:url'
19
+
20
+ const __dirname = dirname(fileURLToPath(import.meta.url))
21
+ const packageDir = join(__dirname, '..')
22
+ const pythonDir = join(packageDir, 'python')
23
+ const venvDir = join(packageDir, '.venv')
24
+ const markerFile = join(venvDir, '.setup-complete')
25
+ const constraints = join(pythonDir, 'requirements.lock')
26
+
27
+ // Python version used by the bundled runtime
28
+ const TARGET_PYTHON = '3.14'
29
+
30
+ function log(msg) {
31
+ process.stderr.write(`[document-ocr] ${msg}\n`)
32
+ }
33
+
34
+ function run(cmd, args, opts = {}) {
35
+ log(`> ${cmd} ${args.join(' ')}`)
36
+ return execFileSync(cmd, args, {
37
+ encoding: 'utf-8',
38
+ stdio: ['pipe', 'pipe', 'pipe'],
39
+ timeout: 600000, // 10 minutes (OCR dependencies are large)
40
+ ...opts,
41
+ })
42
+ }
43
+
44
+ function hasUv() {
45
+ try {
46
+ const version = execFileSync('uv', ['--version'], { encoding: 'utf-8', timeout: 10000 }).trim()
47
+ log(`Found ${version}`)
48
+ return true
49
+ } catch {
50
+ return false
51
+ }
52
+ }
53
+
54
+ function findCompatiblePython() {
55
+ /**
56
+ * Find a Python supported by the bundled runtime (3.14).
57
+ * Check versioned commands first, then generic ones.
58
+ */
59
+ const candidates = ['python3.14', 'python3', 'python']
60
+
61
+ for (const cmd of candidates) {
62
+ try {
63
+ const version = execFileSync(cmd, ['--version'], { encoding: 'utf-8', timeout: 10000 }).trim()
64
+ const match = version.match(/Python (\d+)\.(\d+)/)
65
+ if (match) {
66
+ const major = parseInt(match[1], 10)
67
+ const minor = parseInt(match[2], 10)
68
+ if (major === 3 && minor === 14) {
69
+ log(`Found compatible ${version} via "${cmd}"`)
70
+ return cmd
71
+ }
72
+ }
73
+ } catch { /* not found */ }
74
+ }
75
+ return null
76
+ }
77
+
78
+ async function main() {
79
+ // Skip if already set up
80
+ if (existsSync(markerFile)) {
81
+ log('Python environment already set up.')
82
+ return
83
+ }
84
+
85
+ if (process.env.DOCUMENT_OCR_SKIP_PYTHON === '1' || process.env.PASSPORT_OCR_SKIP_PYTHON === '1') {
86
+ log('Skipping Python setup (DOCUMENT_OCR_SKIP_PYTHON / PASSPORT_OCR_SKIP_PYTHON)')
87
+ return
88
+ }
89
+
90
+ if (!existsSync(pythonDir) || !existsSync(constraints)) {
91
+ log('Warning: bundled Python runtime or dependency lock is missing. Local mode will not work.')
92
+ return
93
+ }
94
+
95
+ const uvAvailable = hasUv()
96
+
97
+ // Strategy 1: Use uv to create venv with a pinned Python version
98
+ // uv can auto-download Python 3.14 even if the system has another version
99
+ if (uvAvailable && !existsSync(venvDir)) {
100
+ log(`Creating virtual environment with Python ${TARGET_PYTHON} via uv...`)
101
+ try {
102
+ run('uv', ['venv', '--python', TARGET_PYTHON, venvDir])
103
+ log('Installing Python dependencies...')
104
+ const venvPython = process.platform === 'win32'
105
+ ? join(venvDir, 'Scripts', 'python.exe')
106
+ : join(venvDir, 'bin', 'python')
107
+ run('uv', ['pip', 'install', '--python', venvPython, '-c', constraints, '-e', pythonDir])
108
+ writeFileSync(markerFile, new Date().toISOString())
109
+ log('Setup complete.')
110
+ return
111
+ } catch (err) {
112
+ log(`uv setup failed: ${err.message?.split('\n')[0]}`)
113
+ log('Trying fallback...')
114
+ // Clean up failed venv
115
+ try { rmSync(venvDir, { recursive: true, force: true }) } catch {}
116
+ }
117
+ }
118
+
119
+ // Strategy 2: Use a compatible system Python directly
120
+ const pythonCmd = findCompatiblePython()
121
+ if (pythonCmd) {
122
+ if (!existsSync(venvDir)) {
123
+ log(`Creating virtual environment with ${pythonCmd}...`)
124
+ try {
125
+ run(pythonCmd, ['-m', 'venv', venvDir])
126
+ } catch (err) {
127
+ log(`Warning: Failed to create venv: ${err.message?.split('\n')[0]}`)
128
+ return
129
+ }
130
+ }
131
+
132
+ const venvPython = process.platform === 'win32'
133
+ ? join(venvDir, 'Scripts', 'python.exe')
134
+ : join(venvDir, 'bin', 'python')
135
+
136
+ if (!existsSync(venvPython)) {
137
+ log(`Warning: venv python not found at ${venvPython}`)
138
+ return
139
+ }
140
+
141
+ log('Installing Python dependencies...')
142
+ try {
143
+ if (uvAvailable) {
144
+ run('uv', ['pip', 'install', '--python', venvPython, '-c', constraints, '-e', pythonDir])
145
+ } else {
146
+ run(venvPython, ['-m', 'pip', 'install', '--upgrade', 'pip'])
147
+ run(venvPython, ['-m', 'pip', 'install', '-c', constraints, '-e', pythonDir])
148
+ }
149
+ } catch (err) {
150
+ log(`Warning: Failed to install dependencies: ${err.message?.split('\n')[0]}`)
151
+ log('Retry with: npm rebuild document-ocr')
152
+ return
153
+ }
154
+
155
+ writeFileSync(markerFile, new Date().toISOString())
156
+ log('Setup complete.')
157
+ return
158
+ }
159
+
160
+ // Strategy 3: Nothing worked
161
+ log('Warning: Could not set up Python environment.')
162
+ log('The bundled runtime requires Python 3.14.')
163
+ if (!uvAvailable) {
164
+ log('Install uv to auto-download a compatible Python:')
165
+ log(' curl -LsSf https://astral.sh/uv/install.sh | sh')
166
+ }
167
+ log('Then retry: npm rebuild document-ocr')
168
+ }
169
+
170
+ main().catch((err) => {
171
+ log(`Warning: Setup failed: ${err.message}`)
172
+ process.exit(0)
173
+ })
@@ -0,0 +1,35 @@
1
+ /** Bundle only canonical source/templates, never local credentials or build output. */
2
+ import { cpSync, existsSync, mkdirSync, readdirSync, rmSync, lstatSync, writeFileSync } from 'node:fs'
3
+ import { dirname, extname, join, resolve } from 'node:path'
4
+ import { fileURLToPath } from 'node:url'
5
+
6
+ const packageDir = resolve(dirname(fileURLToPath(import.meta.url)), '..')
7
+ const repo = resolve(packageDir, '../..')
8
+ const destination = join(packageDir, 'hosting')
9
+ const extensions = new Set(['.py', '.html', '.toml', '.yaml', '.yml', '.json', '.jsonc', '.ts', '.js', '.mjs', '.sh', '.tf', '.hcl', '.md'])
10
+ const names = new Set(['Dockerfile', 'Dockerfile.lambda', 'Caddyfile', 'requirements.lock', 'terraform.tfvars.example', 'LICENSE', '.dockerignore', '.gitignore'])
11
+ const excluded = new Set(['node_modules', '__pycache__', '.terraform', '.wrangler', '.aws-sam', 'dist'])
12
+
13
+ function allowed(source) {
14
+ const name = source.split('/').at(-1)
15
+ if (excluded.has(name) || name.startsWith('.env')) return false
16
+ const details = lstatSync(source)
17
+ if (details.isSymbolicLink()) return false
18
+ if (details.isDirectory()) return !name.startsWith('.')
19
+ return names.has(name) || (!name.startsWith('.') && extensions.has(extname(name)))
20
+ }
21
+
22
+ rmSync(destination, { recursive: true, force: true })
23
+ mkdirSync(destination, { recursive: true })
24
+ for (const name of ['core', 'deploy', 'infra']) {
25
+ if (existsSync(join(repo, name))) cpSync(join(repo, name), join(destination, name), { recursive: true, filter: allowed })
26
+ }
27
+ for (const name of ['pyproject.toml', 'requirements.lock', 'README.md', 'HOSTING.md', 'FEATURES.md', 'LICENSE', '.dockerignore', '.gcloudignore']) {
28
+ if (existsSync(join(repo, name))) cpSync(join(repo, name), join(destination, name))
29
+ }
30
+ // A scaffolding build context must never upload a developer's credentials or data.
31
+ if (!existsSync(join(destination, '.dockerignore'))) {
32
+ writeFileSync(join(destination, '.dockerignore'), '**\n!core/\n!core/**\n!deploy/\n!deploy/docker/\n!deploy/docker/server.py\n!deploy/lambda/\n!deploy/lambda/handler.py\n!requirements.lock\n**/__pycache__\n')
33
+ }
34
+ writeFileSync(join(destination, '.gitignore'), '.env*\n*.pem\n*.key\n*.tfvars\n*.tfstate*\n.venv/\n**/node_modules/\n**/__pycache__/\n**/.terraform/\n**/.wrangler/\n**/.aws-sam/\nbenchmark-data/\nsample-passports/\n')
35
+ console.log(`[sync-hosting] Bundled ${readdirSync(destination).length} source/template entries`)
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env bash
2
+ # Sync canonical Python source into the npm package for bundling.
3
+ set -euo pipefail
4
+
5
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6
+ PACKAGE_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
7
+ REPO_ROOT="$(cd "$PACKAGE_DIR/../.." && pwd)"
8
+
9
+ # Copy core/ (the pipeline)
10
+ rm -rf "$PACKAGE_DIR/python/core"
11
+ cp -r "$REPO_ROOT/core" "$PACKAGE_DIR/python/core"
12
+ find "$PACKAGE_DIR/python" -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
13
+
14
+ # Copy the canonical runtime dependency constraints.
15
+ cp "$REPO_ROOT/requirements.lock" "$PACKAGE_DIR/python/requirements.lock"
16
+
17
+ # Copy server.py
18
+ cp "$REPO_ROOT/deploy/docker/server.py" "$PACKAGE_DIR/python/server.py"
19
+
20
+ echo "[sync-python] Synced core/ and server.py into packages/passport-ocr/python/"