fieldbench 0.2.1__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Frank Thomas
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,107 @@
1
+ Metadata-Version: 2.4
2
+ Name: fieldbench
3
+ Version: 0.2.1
4
+ Summary: A cross-domain, field-level benchmark for schema-driven document extraction
5
+ Author: Frank Thomas
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/fieldbench/fieldbench
8
+ Project-URL: Corpus, https://github.com/fieldbench/corpus
9
+ Keywords: benchmark,document-extraction,information-extraction,evaluation,nlp
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Science/Research
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: pyyaml>=6
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest>=8; extra == "dev"
21
+ Requires-Dist: ruff>=0.6; extra == "dev"
22
+ Provides-Extra: openai
23
+ Requires-Dist: openai>=1.0; extra == "openai"
24
+ Provides-Extra: anthropic
25
+ Requires-Dist: anthropic>=0.30; extra == "anthropic"
26
+ Provides-Extra: llamaindex
27
+ Requires-Dist: llama-index-core>=0.11; extra == "llamaindex"
28
+ Requires-Dist: llama-index-llms-openai>=0.2; extra == "llamaindex"
29
+ Provides-Extra: langchain
30
+ Requires-Dist: langchain-openai>=0.2; extra == "langchain"
31
+ Dynamic: license-file
32
+
33
+ # FieldBench
34
+
35
+ A cross-domain, field-level benchmark for **schema-driven document extraction** — and the canonical scorer that goes with it.
36
+
37
+ Every document-extraction vendor claims 95%+ accuracy; almost none publish how they measure it. FieldBench makes extraction accuracy **falsifiable and comparable**: a shared corpus, a shared type-aware scorer, and a leaderboard anyone can submit to.
38
+
39
+ > **Status: alpha (v0.1).** The scorer is the first piece to land. The corpus lives at [`fieldbench/corpus`](https://github.com/fieldbench/corpus). Leaderboard and HuggingFace packaging are in progress.
40
+
41
+ ## Install
42
+
43
+ ```bash
44
+ pip install fieldbench # once published; for now: pip install -e .
45
+ ```
46
+
47
+ ## Run a baseline
48
+
49
+ `fieldbench run` drives any extractor over the corpus and writes the prediction files for you. It's extractor-agnostic — the whole integration surface is a `complete(prompt) -> str` callable (see `examples/openai_runner.py`):
50
+
51
+ ```bash
52
+ pip install "fieldbench[openai]"
53
+ OPENAI_API_KEY=... FIELDBENCH_MODEL=gpt-4o-mini \
54
+ fieldbench run --corpus ./corpus --out ./preds/gpt-4o-mini \
55
+ --runner examples.openai_runner:make_runner
56
+ ```
57
+
58
+ Runs are **resumable** (existing predictions are skipped) and **never invent results** — a runner error writes nothing, so that document is scored as a real miss, not dropped.
59
+
60
+ ## Score your system
61
+
62
+ Score prediction files — a flat `{field: value}` JSON named `<doc_id>.json` per document (produced by `fieldbench run` or by your own pipeline):
63
+
64
+ ```bash
65
+ fieldbench score --corpus /path/to/corpus --results /path/to/predictions/
66
+ ```
67
+
68
+ You get overall accuracy, the **all-null floor** (what an empty extractor scores for free), a **real-vs-synthetic** split, a **four-way outcome breakdown**, and a per-category table. `--json` emits the full report; `--category <name>` scopes to one category.
69
+
70
+ ## What makes the scoring type-aware
71
+
72
+ `compare_field` runs **four-way null semantics** first — the thing plain accuracy hides:
73
+
74
+ | Outcome | Meaning |
75
+ |---|---|
76
+ | `correct_absence` | field absent in GT, system correctly returned null |
77
+ | `hallucination` | field absent in GT, system invented a value |
78
+ | `miss` | field present in GT, system returned null |
79
+ | `match` / `wrong_value` | both present → type-aware comparison |
80
+
81
+ For present-vs-present it is tolerant where it should be and strict where it must be: numeric with cent-exact tolerance, date normalization, order-independent array F1 (partial credit), punctuation-insensitive strings, and schema enum-alias folding — but never masking a genuine content difference.
82
+
83
+ ## How to cite
84
+
85
+ ```bibtex
86
+ @misc{fieldbench,
87
+ title = {FieldBench: A Cross-Domain Benchmark for Schema-Driven Document Extraction},
88
+ author = {Thomas, Frank},
89
+ year = {2026},
90
+ note = {https://github.com/fieldbench}
91
+ }
92
+ ```
93
+ <!-- Replaced with the Zenodo DOI on release, and the paper citation once published. -->
94
+
95
+ ## Development
96
+
97
+ ```bash
98
+ pip install -e ".[dev]"
99
+ pytest # golden scorer tests
100
+ ruff check .
101
+ ```
102
+
103
+ The golden tests in `tests/` are the anti-drift contract: they pin the canonical scoring semantics that any conforming implementation (including Koji's) must match.
104
+
105
+ ## License
106
+
107
+ Code: MIT. The corpus is licensed per-source — see [`fieldbench/corpus`](https://github.com/fieldbench/corpus).
@@ -0,0 +1,75 @@
1
+ # FieldBench
2
+
3
+ A cross-domain, field-level benchmark for **schema-driven document extraction** — and the canonical scorer that goes with it.
4
+
5
+ Every document-extraction vendor claims 95%+ accuracy; almost none publish how they measure it. FieldBench makes extraction accuracy **falsifiable and comparable**: a shared corpus, a shared type-aware scorer, and a leaderboard anyone can submit to.
6
+
7
+ > **Status: alpha (v0.1).** The scorer is the first piece to land. The corpus lives at [`fieldbench/corpus`](https://github.com/fieldbench/corpus). Leaderboard and HuggingFace packaging are in progress.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ pip install fieldbench # once published; for now: pip install -e .
13
+ ```
14
+
15
+ ## Run a baseline
16
+
17
+ `fieldbench run` drives any extractor over the corpus and writes the prediction files for you. It's extractor-agnostic — the whole integration surface is a `complete(prompt) -> str` callable (see `examples/openai_runner.py`):
18
+
19
+ ```bash
20
+ pip install "fieldbench[openai]"
21
+ OPENAI_API_KEY=... FIELDBENCH_MODEL=gpt-4o-mini \
22
+ fieldbench run --corpus ./corpus --out ./preds/gpt-4o-mini \
23
+ --runner examples.openai_runner:make_runner
24
+ ```
25
+
26
+ Runs are **resumable** (existing predictions are skipped) and **never invent results** — a runner error writes nothing, so that document is scored as a real miss, not dropped.
27
+
28
+ ## Score your system
29
+
30
+ Score prediction files — a flat `{field: value}` JSON named `<doc_id>.json` per document (produced by `fieldbench run` or by your own pipeline):
31
+
32
+ ```bash
33
+ fieldbench score --corpus /path/to/corpus --results /path/to/predictions/
34
+ ```
35
+
36
+ You get overall accuracy, the **all-null floor** (what an empty extractor scores for free), a **real-vs-synthetic** split, a **four-way outcome breakdown**, and a per-category table. `--json` emits the full report; `--category <name>` scopes to one category.
37
+
38
+ ## What makes the scoring type-aware
39
+
40
+ `compare_field` runs **four-way null semantics** first — the thing plain accuracy hides:
41
+
42
+ | Outcome | Meaning |
43
+ |---|---|
44
+ | `correct_absence` | field absent in GT, system correctly returned null |
45
+ | `hallucination` | field absent in GT, system invented a value |
46
+ | `miss` | field present in GT, system returned null |
47
+ | `match` / `wrong_value` | both present → type-aware comparison |
48
+
49
+ For present-vs-present it is tolerant where it should be and strict where it must be: numeric with cent-exact tolerance, date normalization, order-independent array F1 (partial credit), punctuation-insensitive strings, and schema enum-alias folding — but never masking a genuine content difference.
50
+
51
+ ## How to cite
52
+
53
+ ```bibtex
54
+ @misc{fieldbench,
55
+ title = {FieldBench: A Cross-Domain Benchmark for Schema-Driven Document Extraction},
56
+ author = {Thomas, Frank},
57
+ year = {2026},
58
+ note = {https://github.com/fieldbench}
59
+ }
60
+ ```
61
+ <!-- Replaced with the Zenodo DOI on release, and the paper citation once published. -->
62
+
63
+ ## Development
64
+
65
+ ```bash
66
+ pip install -e ".[dev]"
67
+ pytest # golden scorer tests
68
+ ruff check .
69
+ ```
70
+
71
+ The golden tests in `tests/` are the anti-drift contract: they pin the canonical scoring semantics that any conforming implementation (including Koji's) must match.
72
+
73
+ ## License
74
+
75
+ Code: MIT. The corpus is licensed per-source — see [`fieldbench/corpus`](https://github.com/fieldbench/corpus).
@@ -0,0 +1,42 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "fieldbench"
7
+ version = "0.2.1"
8
+ description = "A cross-domain, field-level benchmark for schema-driven document extraction"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Frank Thomas" }]
13
+ keywords = ["benchmark", "document-extraction", "information-extraction", "evaluation", "nlp"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Science/Research",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Programming Language :: Python :: 3",
19
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
20
+ ]
21
+ dependencies = ["pyyaml>=6"]
22
+
23
+ [project.optional-dependencies]
24
+ dev = ["pytest>=8", "ruff>=0.6"]
25
+ openai = ["openai>=1.0"]
26
+ anthropic = ["anthropic>=0.30"]
27
+ llamaindex = ["llama-index-core>=0.11", "llama-index-llms-openai>=0.2"]
28
+ langchain = ["langchain-openai>=0.2"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://github.com/fieldbench/fieldbench"
32
+ Corpus = "https://github.com/fieldbench/corpus"
33
+
34
+ [project.scripts]
35
+ fieldbench = "fieldbench.cli:main"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["src"]
39
+
40
+ [tool.ruff]
41
+ line-length = 120
42
+ target-version = "py310"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,23 @@
1
+ """FieldBench — a cross-domain, field-level benchmark for schema-driven document extraction."""
2
+
3
+ from .aggregate import DocResult, Report, build_report
4
+ from .corpus import list_documents, score_corpus
5
+ from .run import LLMRunner, Runner, run_corpus
6
+ from .scoring import SCORER_VERSION, FieldResult, compare_field
7
+
8
+ __version__ = "0.2.1"
9
+
10
+ __all__ = [
11
+ "compare_field",
12
+ "FieldResult",
13
+ "SCORER_VERSION",
14
+ "score_corpus",
15
+ "list_documents",
16
+ "build_report",
17
+ "DocResult",
18
+ "Report",
19
+ "run_corpus",
20
+ "Runner",
21
+ "LLMRunner",
22
+ "__version__",
23
+ ]
@@ -0,0 +1,120 @@
1
+ """Aggregate per-field results into a FieldBench report.
2
+
3
+ Reports micro/macro field accuracy, always stratified real vs synthetic, with a
4
+ four-way confusion breakdown (correct-absence / hallucination / miss /
5
+ wrong-value) and the all-null degenerate floor — so every score is read against
6
+ what an empty extractor scores for free.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from collections import Counter, defaultdict
12
+ from dataclasses import dataclass, field
13
+
14
+ from .scoring import SCORER_VERSION, FieldResult, is_empty
15
+
16
+
17
+ @dataclass
18
+ class DocResult:
19
+ stem: str
20
+ category: str
21
+ source: str # "real" | "synthetic" | "unknown"
22
+ fields: list[FieldResult]
23
+
24
+
25
+ @dataclass
26
+ class Stratum:
27
+ docs: int = 0
28
+ fields: int = 0
29
+ passed: int = 0
30
+ weighted: float = 0.0
31
+ four_way: Counter = field(default_factory=Counter)
32
+
33
+ @property
34
+ def accuracy(self) -> float:
35
+ return self.passed / self.fields if self.fields else 0.0
36
+
37
+ @property
38
+ def partial(self) -> float:
39
+ return self.weighted / self.fields if self.fields else 0.0
40
+
41
+ def to_dict(self) -> dict:
42
+ n = self.fields or 1
43
+ return {
44
+ "docs": self.docs,
45
+ "fields": self.fields,
46
+ "passed": self.passed,
47
+ "accuracy": round(self.accuracy, 4),
48
+ "partial_credit": round(self.partial, 4),
49
+ "four_way": {
50
+ bucket: {"count": c, "rate": round(c / n, 4)}
51
+ for bucket, c in sorted(self.four_way.items())
52
+ },
53
+ }
54
+
55
+
56
+ @dataclass
57
+ class Report:
58
+ scorer_version: str = SCORER_VERSION
59
+ mode: str = "unspecified" # which representation the predictions came from
60
+ overall: Stratum = field(default_factory=Stratum)
61
+ by_source: dict[str, Stratum] = field(default_factory=dict)
62
+ by_category: dict[str, Stratum] = field(default_factory=dict)
63
+ four_way: Counter = field(default_factory=Counter)
64
+ all_null_floor: float = 0.0
65
+
66
+ def to_dict(self) -> dict:
67
+ total = sum(self.four_way.values()) or 1
68
+ return {
69
+ "scorer_version": self.scorer_version,
70
+ "mode": self.mode,
71
+ "overall": self.overall.to_dict(),
72
+ "by_source": {k: v.to_dict() for k, v in sorted(self.by_source.items())},
73
+ "by_category": {k: v.to_dict() for k, v in sorted(self.by_category.items())},
74
+ "four_way": {
75
+ bucket: {"count": n, "rate": round(n / total, 4)}
76
+ for bucket, n in sorted(self.four_way.items())
77
+ },
78
+ "all_null_floor": round(self.all_null_floor, 4),
79
+ }
80
+
81
+
82
+ def _add(strata: dict[str, Stratum], key: str, r: FieldResult) -> None:
83
+ s = strata.setdefault(key, Stratum())
84
+ s.fields += 1
85
+ s.passed += int(r.passed)
86
+ s.weighted += r.weighted_score
87
+ s.four_way[r.bucket] += 1
88
+
89
+
90
+ def build_report(docs: list[DocResult], mode: str = "unspecified") -> Report:
91
+ rep = Report(mode=mode)
92
+ empty_expected = 0
93
+ total = 0
94
+ doc_counts_source: dict[str, int] = defaultdict(int)
95
+ doc_counts_cat: dict[str, int] = defaultdict(int)
96
+
97
+ for doc in docs:
98
+ doc_counts_source[doc.source] += 1
99
+ doc_counts_cat[doc.category] += 1
100
+ for r in doc.fields:
101
+ total += 1
102
+ rep.overall.fields += 1
103
+ rep.overall.passed += int(r.passed)
104
+ rep.overall.weighted += r.weighted_score
105
+ rep.four_way[r.bucket] += 1
106
+ _add(rep.by_source, doc.source, r)
107
+ _add(rep.by_category, doc.category, r)
108
+ if is_empty(r.expected):
109
+ empty_expected += 1
110
+
111
+ rep.overall.docs = len(docs)
112
+ for src, n in doc_counts_source.items():
113
+ rep.by_source.setdefault(src, Stratum()).docs = n
114
+ for cat, n in doc_counts_cat.items():
115
+ rep.by_category.setdefault(cat, Stratum()).docs = n
116
+
117
+ # All-null floor: an empty extractor scores exactly the empty-GT fields
118
+ # (they land in `correct_absence`); everything else becomes a miss.
119
+ rep.all_null_floor = empty_expected / total if total else 0.0
120
+ return rep
@@ -0,0 +1,134 @@
1
+ """FieldBench CLI: score prediction files against the corpus."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import importlib
7
+ import json
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ from .aggregate import build_report
12
+ from .corpus import score_corpus
13
+ from .run import run_corpus
14
+
15
+
16
+ def _load_runner(ref: str):
17
+ """Load a runner from `module:attr`. attr is a Runner instance or a
18
+ zero-arg factory that returns one."""
19
+ if ":" not in ref:
20
+ raise ValueError(f"--runner must be 'module:attr', got {ref!r}")
21
+ mod_name, attr = ref.split(":", 1)
22
+ obj = getattr(importlib.import_module(mod_name), attr)
23
+ if hasattr(obj, "extract"):
24
+ return obj
25
+ return obj() # factory
26
+
27
+
28
+ def _print_table(report_dict: dict) -> None:
29
+ o = report_dict["overall"]
30
+ print(f"\nFieldBench (scorer {report_dict['scorer_version']})")
31
+ print("=" * 52)
32
+ print(f"Overall: {o['accuracy']:.1%} ({o['passed']}/{o['fields']} fields, {o['docs']} docs)")
33
+ print(f"All-null floor: {report_dict['all_null_floor']:.1%} <- a `{{}}`-emitting baseline scores this free")
34
+
35
+ print("\nBy source:")
36
+ for src, s in report_dict["by_source"].items():
37
+ print(f" {src:<12} {s['accuracy']:.1%} ({s['fields']} fields, {s['docs']} docs)")
38
+
39
+ print("\nFour-way outcomes:")
40
+ for bucket, v in report_dict["four_way"].items():
41
+ print(f" {bucket:<16} {v['count']:>6} ({v['rate']:.1%})")
42
+
43
+ print("\nBy category:")
44
+ for cat, s in sorted(report_dict["by_category"].items(), key=lambda kv: kv[1]["accuracy"]):
45
+ print(f" {cat:<24} {s['accuracy']:.1%} ({s['fields']} fields)")
46
+ print()
47
+
48
+
49
+ def main(argv: list[str] | None = None) -> int:
50
+ parser = argparse.ArgumentParser(prog="fieldbench")
51
+ sub = parser.add_subparsers(dest="command", required=True)
52
+
53
+ score = sub.add_parser("score", help="Score prediction files against the corpus")
54
+ score.add_argument("--corpus", required=True, type=Path, help="Path to the corpus root")
55
+ score.add_argument("--results", required=True, type=Path, help="Directory of <stem>.json predictions")
56
+ score.add_argument("--category", default=None, help="Score a single category only")
57
+ score.add_argument(
58
+ "--mode", default="unspecified", help="Label for which representation the predictions came from (markdown/source/...)"
59
+ )
60
+ score.add_argument("--fuzzy-threshold", type=float, default=0.0, help="Off (0.0) for the official metric")
61
+ score.add_argument("--json", action="store_true", help="Emit the full report as JSON")
62
+ score.add_argument(
63
+ "--allow-missing",
64
+ action="store_true",
65
+ help="Do not fail when prediction files are missing (they are scored as all-null)",
66
+ )
67
+
68
+ run = sub.add_parser("run", help="Run an extractor over the corpus, writing per-doc predictions")
69
+ run.add_argument("--corpus", required=True, type=Path, help="Path to the corpus root")
70
+ run.add_argument("--out", required=True, type=Path, help="Output dir for <stem>.json predictions")
71
+ run.add_argument("--runner", required=True, help="Runner as 'module:attr' (instance or zero-arg factory)")
72
+ run.add_argument("--mode", default="markdown", help="Which representation to feed (markdown/text/source)")
73
+ run.add_argument("--category", default=None, help="Run a single category only")
74
+ run.add_argument("--limit", type=int, default=None, help="Stop after N docs (smoke testing)")
75
+ run.add_argument("--no-resume", action="store_true", help="Re-run docs even if a prediction file exists")
76
+
77
+ args = parser.parse_args(argv)
78
+
79
+ if args.command == "run":
80
+ if not args.corpus.is_dir():
81
+ print(f"error: corpus not found: {args.corpus}", file=sys.stderr)
82
+ return 2
83
+ try:
84
+ runner = _load_runner(args.runner)
85
+ except (ValueError, ImportError, AttributeError) as exc:
86
+ print(f"error: could not load runner {args.runner!r}: {exc}", file=sys.stderr)
87
+ return 2
88
+ stats = run_corpus(
89
+ args.corpus,
90
+ runner,
91
+ args.out,
92
+ mode=args.mode,
93
+ category=args.category,
94
+ resume=not args.no_resume,
95
+ limit=args.limit,
96
+ on_progress=lambda stem, status: print(f" {stem}: {status}", file=sys.stderr),
97
+ )
98
+ print(
99
+ f"\nwrote {stats.written}, skipped {stats.skipped} (resume), "
100
+ f"errors {stats.errors}, no-representation {stats.no_representation}"
101
+ )
102
+ if stats.error_stems:
103
+ print(f"error docs: {', '.join(stats.error_stems[:10])}"
104
+ f"{'…' if len(stats.error_stems) > 10 else ''}", file=sys.stderr)
105
+ return 1 if (stats.errors or stats.no_representation) else 0
106
+
107
+ if args.command == "score":
108
+ if not args.corpus.is_dir():
109
+ print(f"error: corpus not found: {args.corpus}", file=sys.stderr)
110
+ return 2
111
+ docs, missing = score_corpus(
112
+ args.corpus, args.results, category=args.category, fuzzy_threshold=args.fuzzy_threshold
113
+ )
114
+ if not docs:
115
+ print("error: no scorable documents found", file=sys.stderr)
116
+ return 2
117
+ report = build_report(docs, mode=args.mode).to_dict()
118
+ if args.json:
119
+ print(json.dumps(report, indent=2))
120
+ else:
121
+ _print_table(report)
122
+ if missing and not args.allow_missing:
123
+ print(
124
+ f"error: {missing} prediction file(s) missing — scored as all-null. "
125
+ f"Re-run with complete results, or pass --allow-missing to override.",
126
+ file=sys.stderr,
127
+ )
128
+ return 1
129
+ return 0
130
+ return 2
131
+
132
+
133
+ if __name__ == "__main__":
134
+ raise SystemExit(main())