structured-data-extraction 0.1.0__tar.gz → 0.2.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 (42) hide show
  1. {structured_data_extraction-0.1.0/src/structured_data_extraction.egg-info → structured_data_extraction-0.2.0}/PKG-INFO +61 -2
  2. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/README.md +60 -1
  3. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/pyproject.toml +1 -1
  4. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/extract.py +20 -11
  5. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/main.py +24 -7
  6. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0/src/structured_data_extraction.egg-info}/PKG-INFO +61 -2
  7. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/LICENSE +0 -0
  8. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/setup.cfg +0 -0
  9. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/__init__.py +0 -0
  10. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/classify.py +0 -0
  11. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/config.py +0 -0
  12. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/convert.py +0 -0
  13. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/embed.py +0 -0
  14. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/features.py +0 -0
  15. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/filter_records.py +0 -0
  16. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/matching.py +0 -0
  17. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/models/section_classifier/docling_cats.joblib +0 -0
  18. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/models/section_classifier/rf_classifier.joblib +0 -0
  19. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/models/section_classifier/tfidf_char_vectorizer.joblib +0 -0
  20. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/models/section_classifier/tfidf_vectorizer.joblib +0 -0
  21. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/paths.py +0 -0
  22. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/rater.py +0 -0
  23. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/records.py +0 -0
  24. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/search.py +0 -0
  25. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/tables.py +0 -0
  26. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/templates/extraction.j2 +0 -0
  27. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/templates/schema_discovery.j2 +0 -0
  28. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction/vocab.py +0 -0
  29. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction.egg-info/SOURCES.txt +0 -0
  30. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction.egg-info/dependency_links.txt +0 -0
  31. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction.egg-info/entry_points.txt +0 -0
  32. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction.egg-info/requires.txt +0 -0
  33. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/src/structured_data_extraction.egg-info/top_level.txt +0 -0
  34. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_convert.py +0 -0
  35. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_embed.py +0 -0
  36. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_extract.py +0 -0
  37. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_features.py +0 -0
  38. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_matching.py +0 -0
  39. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_rater.py +0 -0
  40. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_records.py +0 -0
  41. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_search.py +0 -0
  42. {structured_data_extraction-0.1.0 → structured_data_extraction-0.2.0}/tests/test_tables.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structured-data-extraction
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Pipeline for extracting structured data (tables, sections, key/value fields) from PDF documents using docling, embeddings, and LLMs.
5
5
  Author-email: Dimitrios Avellas <dimitrisavellas8@gmail.com>
6
6
  License-Expression: MIT
@@ -140,6 +140,7 @@ Notes:
140
140
  ## Usage
141
141
 
142
142
  ```bash
143
+ CLI:
143
144
  # one PDF
144
145
  structured-extract pdf/1167fc.pdf --keys gain CMRR "supply voltage" #when key is more than one word then it should be inside ""
145
146
 
@@ -148,7 +149,65 @@ structured-extract pdf/ --keys revenue "net income"
148
149
 
149
150
  ```
150
151
 
151
- Wrap multi-word keys in quotes. If you omit `--keys`, it prompts for a comma-separated list. A full run writes per-model extractions to `output/<stem>_extraction_<model>.json`, then fuses them into `fused.json` (every fact, sorted by confidence) plus `rating_report.json`, and wraps `fused.json` into the provenance-stamped `records.json`.
152
+ ```python file
153
+ Script:
154
+ from pathlib import Path
155
+ from structured_data_extraction.main import process_single_pdf, process_batch, finalize_outputs
156
+
157
+ path = Path("pdf/example.pdf")
158
+ # OR dir = Path("pdf_dir")
159
+ keys = ["key_1", "key_2", "key_3"]
160
+
161
+ results = process_single_pdf(path, keys)
162
+ # OR process_batch(pdf_dir, user_keys)
163
+
164
+
165
+
166
+ finalize_outputs(levels=["all"])
167
+
168
+ print("Done. See output/fused.json and output/records.json")
169
+ ```
170
+
171
+ Wrap multi-word keys in quotes. If you omit `--keys`, it prompts for a comma-separated list.
172
+
173
+ ### Custom schema (skip schema discovery)
174
+
175
+ Pass `--schema` with a JSON file to bypass the schema-discovery LLM call entirely. The file must contain `document_type` and `schema` keys, using the same format as `intermediate/*_schema.json`. Keys are derived from the schema, so `--keys` is optional.
176
+
177
+ Use `"fixed"` on any sub-field to constrain the LLM to a single value — the field becomes a `Literal` type in the Pydantic model, so the model can only emit that exact value. This lets you slice a table down to specific variants, grades, or qualifiers:
178
+
179
+ ```bash
180
+ structured-extract pdf/AD840.pdf --schema my_schema.json
181
+ ```
182
+
183
+ ```json
184
+ {
185
+ "document_type": "operational amplifier datasheet",
186
+ "schema": {
187
+ "quiescent_current_s_typ": {
188
+ "type": "list",
189
+ "description": "Quiescent current typical value for the AD840S variant",
190
+ "fields": {
191
+ "differentiator": {
192
+ "type": "string",
193
+ "description": "Product variant",
194
+ "fixed": "AD840S"
195
+ },
196
+ "qualifier": {
197
+ "type": "string",
198
+ "description": "Column label: min, typ, or max",
199
+ "fixed": "Typ"
200
+ },
201
+ "condition": { "type": "string", "description": "Operating condition" },
202
+ "value": { "type": "number", "description": "Quiescent current value", "unit": "mA" },
203
+ "metadata": { "type": "object", "description": "Source and provenance" }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ ```
209
+
210
+ A full run writes per-model extractions to `output/<stem>_extraction_<model>.json`, then fuses them into `fused.json` (every fact, sorted by confidence) plus `rating_report.json`, and wraps `fused.json` into the provenance-stamped `records.json`.
152
211
 
153
212
  A multi-model run then ends by printing the confidence-tier histogram and offering to keep a subset of tiers — pass `--levels` (e.g. `--levels 1.0 0.75`, or `all`) to choose up front, or omit it for an interactive prompt. Single-model runs skip this step, since every record is confidence 1.0.
154
213
 
@@ -108,6 +108,7 @@ Notes:
108
108
  ## Usage
109
109
 
110
110
  ```bash
111
+ CLI:
111
112
  # one PDF
112
113
  structured-extract pdf/1167fc.pdf --keys gain CMRR "supply voltage" #when key is more than one word then it should be inside ""
113
114
 
@@ -116,7 +117,65 @@ structured-extract pdf/ --keys revenue "net income"
116
117
 
117
118
  ```
118
119
 
119
- Wrap multi-word keys in quotes. If you omit `--keys`, it prompts for a comma-separated list. A full run writes per-model extractions to `output/<stem>_extraction_<model>.json`, then fuses them into `fused.json` (every fact, sorted by confidence) plus `rating_report.json`, and wraps `fused.json` into the provenance-stamped `records.json`.
120
+ ```python file
121
+ Script:
122
+ from pathlib import Path
123
+ from structured_data_extraction.main import process_single_pdf, process_batch, finalize_outputs
124
+
125
+ path = Path("pdf/example.pdf")
126
+ # OR dir = Path("pdf_dir")
127
+ keys = ["key_1", "key_2", "key_3"]
128
+
129
+ results = process_single_pdf(path, keys)
130
+ # OR process_batch(pdf_dir, user_keys)
131
+
132
+
133
+
134
+ finalize_outputs(levels=["all"])
135
+
136
+ print("Done. See output/fused.json and output/records.json")
137
+ ```
138
+
139
+ Wrap multi-word keys in quotes. If you omit `--keys`, it prompts for a comma-separated list.
140
+
141
+ ### Custom schema (skip schema discovery)
142
+
143
+ Pass `--schema` with a JSON file to bypass the schema-discovery LLM call entirely. The file must contain `document_type` and `schema` keys, using the same format as `intermediate/*_schema.json`. Keys are derived from the schema, so `--keys` is optional.
144
+
145
+ Use `"fixed"` on any sub-field to constrain the LLM to a single value — the field becomes a `Literal` type in the Pydantic model, so the model can only emit that exact value. This lets you slice a table down to specific variants, grades, or qualifiers:
146
+
147
+ ```bash
148
+ structured-extract pdf/AD840.pdf --schema my_schema.json
149
+ ```
150
+
151
+ ```json
152
+ {
153
+ "document_type": "operational amplifier datasheet",
154
+ "schema": {
155
+ "quiescent_current_s_typ": {
156
+ "type": "list",
157
+ "description": "Quiescent current typical value for the AD840S variant",
158
+ "fields": {
159
+ "differentiator": {
160
+ "type": "string",
161
+ "description": "Product variant",
162
+ "fixed": "AD840S"
163
+ },
164
+ "qualifier": {
165
+ "type": "string",
166
+ "description": "Column label: min, typ, or max",
167
+ "fixed": "Typ"
168
+ },
169
+ "condition": { "type": "string", "description": "Operating condition" },
170
+ "value": { "type": "number", "description": "Quiescent current value", "unit": "mA" },
171
+ "metadata": { "type": "object", "description": "Source and provenance" }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ ```
177
+
178
+ A full run writes per-model extractions to `output/<stem>_extraction_<model>.json`, then fuses them into `fused.json` (every fact, sorted by confidence) plus `rating_report.json`, and wraps `fused.json` into the provenance-stamped `records.json`.
120
179
 
121
180
  A multi-model run then ends by printing the confidence-tier histogram and offering to keep a subset of tiers — pass `--levels` (e.g. `--levels 1.0 0.75`, or `all`) to choose up front, or omit it for an interactive prompt. Single-model runs skip this step, since every record is confidence 1.0.
122
181
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "structured-data-extraction"
7
- version = "0.1.0"
7
+ version = "0.2.0"
8
8
  description = "Pipeline for extracting structured data (tables, sections, key/value fields) from PDF documents using docling, embeddings, and LLMs."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -23,13 +23,13 @@ import os
23
23
  from concurrent.futures import ThreadPoolExecutor, as_completed
24
24
  from datetime import datetime, timezone
25
25
  from pathlib import Path
26
- from typing import Any, List, Optional
26
+ from typing import Any, List, Literal, Optional
27
27
  import instructor
28
28
  from jinja2 import Environment, ChoiceLoader, FileSystemLoader, PackageLoader
29
29
  from openai import OpenAI
30
30
  from pydantic import BaseModel, Field, create_model
31
31
  from . import config, matching
32
- from .paths import inter, read_json, cached
32
+ from .paths import inter, read_json, write_json, cached
33
33
  from .tables import strip_code_fence
34
34
 
35
35
  logger = logging.getLogger(__name__)
@@ -110,10 +110,15 @@ def _build_extraction_model(schema, user_keys):
110
110
  for sub_name, sub_def in fields_spec.items():
111
111
  if sub_name == "metadata":
112
112
  continue
113
- field_type = type_map.get(sub_def.get("type", "string"), str)
114
113
  sub_desc = sub_def.get("description", "")
115
- default = "" if field_type is str else None
116
- sub_fields[sub_name] = (field_type, Field(default=default, description=sub_desc))
114
+ fixed = sub_def.get("fixed")
115
+ if fixed is not None:
116
+ field_type = Literal[fixed]
117
+ sub_fields[sub_name] = (field_type, Field(default=fixed, description=sub_desc))
118
+ else:
119
+ field_type = type_map.get(sub_def.get("type", "string"), str)
120
+ default = "" if field_type is str else None
121
+ sub_fields[sub_name] = (field_type, Field(default=default, description=sub_desc))
117
122
  if sub_def.get("unit") and not has_unit_field:
118
123
  sub_fields[f"{sub_name}_unit"] = (str, Field(default="", description=f"Unit for {sub_name}"))
119
124
 
@@ -368,7 +373,7 @@ def _run_one_model(model_name, stem, out_dir, unique, vocab, user_keys, schema,
368
373
  return extraction
369
374
 
370
375
 
371
- def extract_parallel(stem, user_keys):
376
+ def extract_parallel(stem, user_keys, user_schema=None):
372
377
  """Run every configured extraction model over one document in parallel.
373
378
 
374
379
  Stamps run provenance (run.json), discovers the schema once for all models, then fans
@@ -387,11 +392,15 @@ def extract_parallel(stem, user_keys):
387
392
 
388
393
  md_text, vocab, unique = _prepare_sections(stem)
389
394
 
390
- # schema discovery once for all models — cached to intermediate/<stem>_schema.json
391
- disc = cached(inter(stem, "schema.json"),
392
- lambda: dict(zip(("document_type", "schema"),
393
- discover_schema(md_text, unique, vocab, user_keys))))
394
- doc_type, schema = disc["document_type"], disc["schema"]
395
+ if user_schema is not None:
396
+ doc_type, schema = user_schema["document_type"], user_schema["schema"]
397
+ write_json(inter(stem, "schema.json"), user_schema)
398
+ logger.info("Using user-supplied schema (skipped schema discovery)")
399
+ else:
400
+ disc = cached(inter(stem, "schema.json"),
401
+ lambda: dict(zip(("document_type", "schema"),
402
+ discover_schema(md_text, unique, vocab, user_keys))))
403
+ doc_type, schema = disc["document_type"], disc["schema"]
395
404
  logger.info(f"Schema: document_type='{doc_type}'")
396
405
 
397
406
  results = []
@@ -49,7 +49,7 @@ def _invalidate_stale_key_caches(stem, user_keys):
49
49
  p.unlink()
50
50
 
51
51
 
52
- def process_single_pdf(pdf_path, user_keys):
52
+ def process_single_pdf(pdf_path, user_keys, user_schema=None):
53
53
  """Run the full extraction pipeline on one PDF.
54
54
 
55
55
  Executes every stage in order: convert → vocab → classify → tables →
@@ -150,7 +150,7 @@ def process_single_pdf(pdf_path, user_keys):
150
150
  refine_matched_tables(str(tables_file), matched_idxs, pdf_path=str(pdf_path))
151
151
 
152
152
  with timed("extract"):
153
- results = extract_parallel(stem, user_keys)
153
+ results = extract_parallel(stem, user_keys, user_schema=user_schema)
154
154
 
155
155
  _log_run_summary(stem, timings, results, matches)
156
156
  logger.info(f"=== Done with {pdf_path.name} ===")
@@ -233,7 +233,7 @@ def _offer_filter(records_path, levels):
233
233
  filter_records.run(records_path, levels=levels)
234
234
 
235
235
 
236
- def process_batch(pdf_dir, user_keys):
236
+ def process_batch(pdf_dir, user_keys, user_schema=None):
237
237
  pdf_dir = Path(pdf_dir)
238
238
  if not pdf_dir.exists():
239
239
  logger.error(f"Directory not found: {pdf_dir}")
@@ -248,7 +248,7 @@ def process_batch(pdf_dir, user_keys):
248
248
  results = []
249
249
  for pdf in pdfs:
250
250
  try:
251
- result = process_single_pdf(str(pdf), user_keys)
251
+ result = process_single_pdf(str(pdf), user_keys, user_schema=user_schema)
252
252
  if result:
253
253
  results.append(result)
254
254
  except Exception as e:
@@ -280,6 +280,9 @@ def main():
280
280
  parser.add_argument("--levels", nargs="+", metavar="LVL",
281
281
  help="confidence tiers to keep (e.g. 1.0 0.75, or 'all'); "
282
282
  "omit to be prompted after extraction")
283
+ parser.add_argument("--schema", metavar="FILE",
284
+ help="Path to a JSON schema file (same format as intermediate/*_schema.json); "
285
+ "skips schema discovery. Keys are derived from the schema, so --keys is optional")
283
286
  parser.add_argument("--dump-templates", metavar="DIR",
284
287
  help="Write the built-in prompt templates to DIR for editing, then exit; "
285
288
  "point TEMPLATES_DIR at that folder to use your edits")
@@ -293,8 +296,22 @@ def main():
293
296
  parser.error("a PDF file or directory is required (unless using --dump-templates)")
294
297
 
295
298
  config.FORCE = args.force
299
+
300
+ user_schema = None
301
+ if args.schema:
302
+ schema_path = Path(args.schema)
303
+ if not schema_path.exists():
304
+ print(f"Schema file not found: {schema_path}")
305
+ sys.exit(1)
306
+ user_schema = json.loads(schema_path.read_text(encoding="utf-8"))
307
+ if not isinstance(user_schema, dict) or "schema" not in user_schema or "document_type" not in user_schema:
308
+ print("Schema file must contain 'document_type' and 'schema' keys")
309
+ sys.exit(1)
310
+
296
311
  user_keys = args.keys
297
- if not user_keys:
312
+ if not user_keys and user_schema:
313
+ user_keys = list(user_schema["schema"].keys())
314
+ elif not user_keys:
298
315
  print("Enter keys to extract (comma-separated):")
299
316
  raw = input("> ").strip()
300
317
  user_keys = [k.strip() for k in raw.split(",") if k.strip()]
@@ -308,7 +325,7 @@ def main():
308
325
  config.OUTPUT_DIR = args.output
309
326
 
310
327
  if Path(args.path).is_dir():
311
- results = process_batch(args.path, user_keys)
328
+ results = process_batch(args.path, user_keys, user_schema=user_schema)
312
329
  if results:
313
330
  finalize_outputs(args.levels)
314
331
  print(f"Extracted from {len(results)} files → {config.OUTPUT_DIR}")
@@ -316,7 +333,7 @@ def main():
316
333
  print("No documents produced any extraction.")
317
334
  sys.exit(1)
318
335
  else:
319
- result = process_single_pdf(args.path, user_keys)
336
+ result = process_single_pdf(args.path, user_keys, user_schema=user_schema)
320
337
  if result:
321
338
  finalize_outputs(args.levels)
322
339
  print(json.dumps(result, indent=2, ensure_ascii=False)) #document them well
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: structured-data-extraction
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: Pipeline for extracting structured data (tables, sections, key/value fields) from PDF documents using docling, embeddings, and LLMs.
5
5
  Author-email: Dimitrios Avellas <dimitrisavellas8@gmail.com>
6
6
  License-Expression: MIT
@@ -140,6 +140,7 @@ Notes:
140
140
  ## Usage
141
141
 
142
142
  ```bash
143
+ CLI:
143
144
  # one PDF
144
145
  structured-extract pdf/1167fc.pdf --keys gain CMRR "supply voltage" #when key is more than one word then it should be inside ""
145
146
 
@@ -148,7 +149,65 @@ structured-extract pdf/ --keys revenue "net income"
148
149
 
149
150
  ```
150
151
 
151
- Wrap multi-word keys in quotes. If you omit `--keys`, it prompts for a comma-separated list. A full run writes per-model extractions to `output/<stem>_extraction_<model>.json`, then fuses them into `fused.json` (every fact, sorted by confidence) plus `rating_report.json`, and wraps `fused.json` into the provenance-stamped `records.json`.
152
+ ```python file
153
+ Script:
154
+ from pathlib import Path
155
+ from structured_data_extraction.main import process_single_pdf, process_batch, finalize_outputs
156
+
157
+ path = Path("pdf/example.pdf")
158
+ # OR dir = Path("pdf_dir")
159
+ keys = ["key_1", "key_2", "key_3"]
160
+
161
+ results = process_single_pdf(path, keys)
162
+ # OR process_batch(pdf_dir, user_keys)
163
+
164
+
165
+
166
+ finalize_outputs(levels=["all"])
167
+
168
+ print("Done. See output/fused.json and output/records.json")
169
+ ```
170
+
171
+ Wrap multi-word keys in quotes. If you omit `--keys`, it prompts for a comma-separated list.
172
+
173
+ ### Custom schema (skip schema discovery)
174
+
175
+ Pass `--schema` with a JSON file to bypass the schema-discovery LLM call entirely. The file must contain `document_type` and `schema` keys, using the same format as `intermediate/*_schema.json`. Keys are derived from the schema, so `--keys` is optional.
176
+
177
+ Use `"fixed"` on any sub-field to constrain the LLM to a single value — the field becomes a `Literal` type in the Pydantic model, so the model can only emit that exact value. This lets you slice a table down to specific variants, grades, or qualifiers:
178
+
179
+ ```bash
180
+ structured-extract pdf/AD840.pdf --schema my_schema.json
181
+ ```
182
+
183
+ ```json
184
+ {
185
+ "document_type": "operational amplifier datasheet",
186
+ "schema": {
187
+ "quiescent_current_s_typ": {
188
+ "type": "list",
189
+ "description": "Quiescent current typical value for the AD840S variant",
190
+ "fields": {
191
+ "differentiator": {
192
+ "type": "string",
193
+ "description": "Product variant",
194
+ "fixed": "AD840S"
195
+ },
196
+ "qualifier": {
197
+ "type": "string",
198
+ "description": "Column label: min, typ, or max",
199
+ "fixed": "Typ"
200
+ },
201
+ "condition": { "type": "string", "description": "Operating condition" },
202
+ "value": { "type": "number", "description": "Quiescent current value", "unit": "mA" },
203
+ "metadata": { "type": "object", "description": "Source and provenance" }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ ```
209
+
210
+ A full run writes per-model extractions to `output/<stem>_extraction_<model>.json`, then fuses them into `fused.json` (every fact, sorted by confidence) plus `rating_report.json`, and wraps `fused.json` into the provenance-stamped `records.json`.
152
211
 
153
212
  A multi-model run then ends by printing the confidence-tier histogram and offering to keep a subset of tiers — pass `--levels` (e.g. `--levels 1.0 0.75`, or `all`) to choose up front, or omit it for an interactive prompt. Single-model runs skip this step, since every record is confidence 1.0.
154
213