{"_id":"@amirandap/data-review-core","name":"@amirandap/data-review-core","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@amirandap/data-review-core","version":"0.1.0","description":"Framework-agnostic core for LLM extraction + human-in-the-loop review: the shared field/prompt-version vocabulary, the JSON-Schema round trip, OCR token matching, a fetch client with actionable errors, the ParseResult contract, and the date/timezone helpe","license":"MIT","author":{"name":"Alejandro Miranda","url":"https://github.com/amirandap"},"repository":{"type":"git","url":"git+https://github.com/amirandap/shared-pipeline.git","directory":"packages/data-review-core"},"keywords":["calendar-day","data-validation","date","human-in-the-loop","json-schema","llm","ocr","structured-outputs","timezone"],"type":"module","sideEffects":false,"main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./fecha":{"types":"./dist/fecha/index.d.ts","default":"./dist/fecha/index.js"}},"engines":{"node":">=20"},"scripts":{"build":"tsc -b","test":"vitest run","lint":"oxlint"},"devDependencies":{"oxlint":"^1.71.0","typescript":"~6.0.2","vitest":"^4.1.10"},"publishConfig":{"access":"public"},"gitHead":"6415b77ab858a3cfb583f872b8ece4a6bf37734f","_id":"@amirandap/data-review-core@0.1.0","bugs":{"url":"https://github.com/amirandap/shared-pipeline/issues"},"homepage":"https://github.com/amirandap/shared-pipeline#readme","_nodeVersion":"24.16.0","_npmVersion":"11.13.0","dist":{"integrity":"sha512-7TTAtrDJGzXIUW07x8eXII8Nt8l1ema8YKIsgNoMVwxP9r6FaJLDbZJp6Q57oqZS0fw803tX+ABfAbxzom0nZw==","shasum":"f908a52e60f199e57523d890347f7b25b8412f6f","tarball":"https://registry.npmjs.org/@amirandap/data-review-core/-/data-review-core-0.1.0.tgz","fileCount":38,"unpackedSize":124235,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHo6Up2jIDZ3IGga2t7NdM19DFF/DNJ/E/TDB04l4C/WAiAZaFtQYA5drVIAU8WcfaMGJN2oBVY87naYVlnOR+x0oQ=="}]},"_npmUser":{"name":"amirandap","email":"amp@softgrouprd.com"},"directories":{},"maintainers":[{"name":"amirandap","email":"amp@softgrouprd.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/data-review-core_0.1.0_1787240889567_0.9328824010576737"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-20T15:48:09.361Z","0.1.0":"2026-08-20T15:48:09.706Z","modified":"2026-08-20T15:48:09.947Z"},"maintainers":[{"name":"amirandap","email":"amp@softgrouprd.com"}],"description":"Framework-agnostic core for LLM extraction + human-in-the-loop review: the shared field/prompt-version vocabulary, the JSON-Schema round trip, OCR token matching, a fetch client with actionable errors, the ParseResult contract, and the date/timezone helpe","homepage":"https://github.com/amirandap/shared-pipeline#readme","keywords":["calendar-day","data-validation","date","human-in-the-loop","json-schema","llm","ocr","structured-outputs","timezone"],"repository":{"type":"git","url":"git+https://github.com/amirandap/shared-pipeline.git","directory":"packages/data-review-core"},"author":{"name":"Alejandro Miranda","url":"https://github.com/amirandap"},"bugs":{"url":"https://github.com/amirandap/shared-pipeline/issues"},"license":"MIT","readme":"# @amirandap/data-review-core\n\n> Canonical source as of 2026-08-05 — was `@amirandap/data-review-ui/packages/core`,\n> consumed here as a submodule; now the reverse: `@amirandap/data-review-ui` and\n> the sibling backend consume THIS copy as a submodule (see docs/CONSUMING.md in\n> `@amirandap/data-review-ui`, which documents the mechanism this direction still\n> uses). Change it here; port anything genuinely generic upstream is no\n> longer the model — this repo IS upstream now.\n\nThe framework-agnostic contract shared by every extraction/review app.\nPure TypeScript, **zero runtime dependencies** — the same module imports into\na MUI app, a Tailwind/shadcn app, and a plain Node backend.\n\nIt exists because the same four things were written independently in three\nrepos, and drifted:\n\n| | Was in | Now |\n|---|---|---|\n| Field list → LLM schema | `@amirandap/data-review-ui` | `sectionsToJsonSchema` |\n| LLM schema → field list | the WhatsApp consumer (`lib/schemaFields.ts`) | `jsonSchemaToSections`, `parseSchemaFields` |\n| OCR word → form field matching | the sibling backend (`OcrImagePreview.tsx`) | `matchTokensToValues` |\n| Engine result shape + cost | the sibling backend (`services/base.py`) | `ExtractionResult`, `estimateCostUsd` |\n\n## Install\n\nNot published yet — see [`docs/CONSUMING.md`](../../docs/CONSUMING.md) in the\nrepo root. Apps that render with MUI can install `@amirandap/data-review-ui`\ninstead, which bundles this package and re-exports all of it.\n\n## The schema round trip\n\nOne field list drives both the review form and the extraction call:\n\n```ts\nimport { sectionsToJsonSchema, jsonSchemaToSections } from '@amirandap/data-review-core';\n\n// Forward — send this as OpenAI's response_format.\nconst { name, strict, schema } = sectionsToJsonSchema(sections);\n\n// Back — render a form for a pipeline whose schema you did NOT generate.\nconst sections = jsonSchemaToSections(promptVersion.schema_json);\n```\n\nThe reverse direction handles what hand-written schemas actually contain:\n`$ref` into `$defs`, `anyOf: [X, {type:'null'}]` for nullables, arrays of\nobjects. Top-level scalars become one section; each nested object or array\nbecomes its own. Unusable input returns `[]`, never throws — it runs live\nwhile a user types into a schema textarea.\n\n## OCR token matching\n\n```ts\nimport { matchTokensToValues } from '@amirandap/data-review-core';\n\nconst overlayTokens = matchTokensToValues(detectedWords, formValues, { fields });\n// matched → { matchedFieldKey, label }  (render green)\n// unmatched → bare                       (render yellow, click to fill)\n```\n\nMatching ignores punctuation, case and separators (`RD$ 6,180.25` matches a\n`6180.25` box; `809-555-1234` matches `8095551234`), and an ISO date in the\nform also matches its `MM/DD/YYYY` and `MM/DD` printed forms. Values with no\nsignal — empty, `null`, a bare `0` — are skipped, because a lone zero matches\nhalf a utility bill and turns the overlay into noise.\n\n## The result contract\n\nEvery engine — an LLM call, a Tesseract subprocess, a local VLM — returns the\nsame thing:\n\n```ts\nimport { parseExtractionResult, qualityScore, isViable } from '@amirandap/data-review-core';\n\nconst result = parseExtractionResult(stdoutFromAnyEngine);\n// { documentType, isTarget, confidence, data, qcErrors, qcWarnings,\n//   modelVersion, processingMs, costUsd, costMeta, extra }\n\nqualityScore(result, ['nic', 'kwh', 'total_facturado']);          // 0..n\nisViable(result, { identityFields: ['nic'], valueFields: ['kwh'] });\n```\n\nThe wire format is snake_case JSON, identical to what the sibling backend's\nPython engines already print, so no adapter is needed on either side. Unknown keys\nsurvive a `parse` → `extractionResultToWire` round trip in `extra`, so an\nengine-specific field is never silently dropped by passing through a generic\nlayer.\n\n## prompt_versions row adapters\n\n```ts\nimport { promptVersionFromRow, promptVersionToRow } from '@amirandap/data-review-core';\n```\n\nBetween a `prompt_versions` row (see `@amirandap/llm-file-pipeline`'s\n`migrations/0001_prompt_versions.sql`; the sibling backend's MySQL table uses\nthe same column names) and the `PromptVersion` model the review UI renders.\n`promptVersionToRow` deliberately emits no `version`, `is_active` or\n`created_at`: the store assigns the version number, and activation is a\nseparate explicit step so a saved draft never silently becomes the live prompt.\n","readmeFilename":"README.md","_rev":"1-0a0c89f2992e5dd91f1d5c3afd6da6b9"}