npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@amirandap/data-review-core

v0.1.0

Published

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

Downloads

240

Readme

@amirandap/data-review-core

Canonical source as of 2026-08-05 — was @amirandap/data-review-ui/packages/core, consumed here as a submodule; now the reverse: @amirandap/data-review-ui and the sibling backend consume THIS copy as a submodule (see docs/CONSUMING.md in @amirandap/data-review-ui, which documents the mechanism this direction still uses). Change it here; port anything genuinely generic upstream is no longer the model — this repo IS upstream now.

The framework-agnostic contract shared by every extraction/review app. Pure TypeScript, zero runtime dependencies — the same module imports into a MUI app, a Tailwind/shadcn app, and a plain Node backend.

It exists because the same four things were written independently in three repos, and drifted:

| | Was in | Now | |---|---|---| | Field list → LLM schema | @amirandap/data-review-ui | sectionsToJsonSchema | | LLM schema → field list | the WhatsApp consumer (lib/schemaFields.ts) | jsonSchemaToSections, parseSchemaFields | | OCR word → form field matching | the sibling backend (OcrImagePreview.tsx) | matchTokensToValues | | Engine result shape + cost | the sibling backend (services/base.py) | ExtractionResult, estimateCostUsd |

Install

Not published yet — see docs/CONSUMING.md in the repo root. Apps that render with MUI can install @amirandap/data-review-ui instead, which bundles this package and re-exports all of it.

The schema round trip

One field list drives both the review form and the extraction call:

import { sectionsToJsonSchema, jsonSchemaToSections } from '@amirandap/data-review-core';

// Forward — send this as OpenAI's response_format.
const { name, strict, schema } = sectionsToJsonSchema(sections);

// Back — render a form for a pipeline whose schema you did NOT generate.
const sections = jsonSchemaToSections(promptVersion.schema_json);

The reverse direction handles what hand-written schemas actually contain: $ref into $defs, anyOf: [X, {type:'null'}] for nullables, arrays of objects. Top-level scalars become one section; each nested object or array becomes its own. Unusable input returns [], never throws — it runs live while a user types into a schema textarea.

OCR token matching

import { matchTokensToValues } from '@amirandap/data-review-core';

const overlayTokens = matchTokensToValues(detectedWords, formValues, { fields });
// matched → { matchedFieldKey, label }  (render green)
// unmatched → bare                       (render yellow, click to fill)

Matching ignores punctuation, case and separators (RD$ 6,180.25 matches a 6180.25 box; 809-555-1234 matches 8095551234), and an ISO date in the form also matches its MM/DD/YYYY and MM/DD printed forms. Values with no signal — empty, null, a bare 0 — are skipped, because a lone zero matches half a utility bill and turns the overlay into noise.

The result contract

Every engine — an LLM call, a Tesseract subprocess, a local VLM — returns the same thing:

import { parseExtractionResult, qualityScore, isViable } from '@amirandap/data-review-core';

const result = parseExtractionResult(stdoutFromAnyEngine);
// { documentType, isTarget, confidence, data, qcErrors, qcWarnings,
//   modelVersion, processingMs, costUsd, costMeta, extra }

qualityScore(result, ['nic', 'kwh', 'total_facturado']);          // 0..n
isViable(result, { identityFields: ['nic'], valueFields: ['kwh'] });

The wire format is snake_case JSON, identical to what the sibling backend's Python engines already print, so no adapter is needed on either side. Unknown keys survive a parseextractionResultToWire round trip in extra, so an engine-specific field is never silently dropped by passing through a generic layer.

prompt_versions row adapters

import { promptVersionFromRow, promptVersionToRow } from '@amirandap/data-review-core';

Between a prompt_versions row (see @amirandap/llm-file-pipeline's migrations/0001_prompt_versions.sql; the sibling backend's MySQL table uses the same column names) and the PromptVersion model the review UI renders. promptVersionToRow deliberately emits no version, is_active or created_at: the store assigns the version number, and activation is a separate explicit step so a saved draft never silently becomes the live prompt.