From 33bbd81619585ca41dd3fbb86c98ded707559d8e Mon Sep 17 00:00:00 2001 From: Chris Wilhelm Date: Wed, 17 May 2023 14:46:09 -0700 Subject: [PATCH] TIMO-ifies heuristic DWP predictor (#248) Same behavior as implementation in SPP, but can be stood up as a TIMO service. --- pyproject.toml | 3 +- src/ai2_internal/config.yaml | 42 +++++++++ src/ai2_internal/dwp_heuristic/README.txt | 4 + src/ai2_internal/dwp_heuristic/__init__.py | 0 .../dwp_heuristic/integration_test.py | 90 +++++++++++++++++++ src/ai2_internal/dwp_heuristic/interface.py | 74 +++++++++++++++ .../dwp_heuristic/test_fixtures/test_doc.json | 1 + 7 files changed, 213 insertions(+), 1 deletion(-) create mode 100644 src/ai2_internal/dwp_heuristic/README.txt create mode 100644 src/ai2_internal/dwp_heuristic/__init__.py create mode 100644 src/ai2_internal/dwp_heuristic/integration_test.py create mode 100644 src/ai2_internal/dwp_heuristic/interface.py create mode 100644 src/ai2_internal/dwp_heuristic/test_fixtures/test_doc.json diff --git a/pyproject.toml b/pyproject.toml index 9e541a52..a1158c0b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = 'mmda' -version = '0.4.9' +version = '0.5.0' description = 'MMDA - multimodal document analysis' authors = [ {name = 'Allen Institute for Artificial Intelligence', email = 'contact@allenai.org'}, @@ -42,6 +42,7 @@ ai2_internal = [ './bibentry_predictor_mmda/data/*', './citation_mentions/data/*', './vila/test_fixtures/*', + './dwp_heuristic/test_fixtures/*', './figure_table_predictors/test_fixtures/*', './figure_table_predictors/test_fixtures.images/*', './shared_test_fixtures/*', diff --git a/src/ai2_internal/config.yaml b/src/ai2_internal/config.yaml index aa82f2c6..6da3320d 100644 --- a/src/ai2_internal/config.yaml +++ b/src/ai2_internal/config.yaml @@ -278,3 +278,45 @@ model_variants: # Any additional sets of dependencies required by the model. # These are the 'extras_require' keys in your setup.py. extras_require: ["figure_table_predictors"] + + dwp-heuristic: + # Class path to pydantic Instance implementation in == + instance: ai2_internal.dwp_heuristic.interface.Instance + + # Class path to pydantic Prediction implementation in == + prediction: ai2_internal.dwp_heuristic.interface.Prediction + + # Class path to Predictor implementation in == + predictor: ai2_internal.dwp_heuristic.interface.Predictor + + # Class path to pydantic PredictorConfig implementation in == + predictor_config: ai2_internal.dwp_heuristic.interface.PredictorConfig + + # One or more bash commands to execute as part of a RUN step in a Dockerfile. + # Leave this unset unless your model has special system requirements beyond + # those in your setup.py. + docker_run_commands: [] + + # Any additional sets of dependencies required by the model. + # These are the 'extras_require' keys in your setup.py. + extras_require: ["heuristic_predictors"] + + # Full S3 path to tar.gz'ed artifacts archive, nullable + artifacts_s3_path: null + + # Version of python required for model runtime, e.g. 3.7, 3.8, 3.9 + python_version: 3.8 + + # Whether this model supports CUDA GPU acceleration + cuda: false + + # One of the versions here: https://gitlab.com/nvidia/container-images/cuda/blob/master/doc/supported-tags.md#ubuntu2004, but less than 11.4.3. + # If cuda=True and cuda_version is unspecified, defaults to 11.4.2. + cuda_version: null + + # Python path to a fn in == that + # returns a unittest.TestCase. Builder function receives a model container + # as its sole argument. + # Used by the TIMO toolchain to validate your model implementation and configuration. + integration_test: ai2_internal.dwp_heuristic.integration_test.TestInterfaceIntegration + diff --git a/src/ai2_internal/dwp_heuristic/README.txt b/src/ai2_internal/dwp_heuristic/README.txt new file mode 100644 index 00000000..02199f64 --- /dev/null +++ b/src/ai2_internal/dwp_heuristic/README.txt @@ -0,0 +1,4 @@ +This directory contains files required internally by the +Semantic Scholar product. + +It can safely be ignored by external consumers of this repository. diff --git a/src/ai2_internal/dwp_heuristic/__init__.py b/src/ai2_internal/dwp_heuristic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/ai2_internal/dwp_heuristic/integration_test.py b/src/ai2_internal/dwp_heuristic/integration_test.py new file mode 100644 index 00000000..2dedf5ad --- /dev/null +++ b/src/ai2_internal/dwp_heuristic/integration_test.py @@ -0,0 +1,90 @@ +""" +Write integration tests for your model interface code here. + +The TestCase class below is supplied a `container` +to each test method. This `container` object is a proxy to the +Dockerized application running your model. It exposes a single method: + +``` +predict_batch(instances: List[Instance]) -> List[Prediction] +``` + +To test your code, create `Instance`s and make normal `TestCase` +assertions against the returned `Prediction`s. + +e.g. + +``` +def test_prediction(self, container): + instances = [Instance(), Instance()] + predictions = container.predict_batch(instances) + + self.assertEqual(len(instances), len(predictions) + + self.assertEqual(predictions[0].field1, "asdf") + self.assertGreatEqual(predictions[1].field2, 2.0) +``` +""" + +import gzip +import json +import logging +import os +import sys +import unittest + +from .interface import Instance, Prediction +from ai2_internal import api +from mmda.types.document import Document + + +FIXTURE_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_fixtures") + + +try: + from timo_interface import with_timo_container +except ImportError as e: + logging.warning(""" + This test can only be run by a TIMO test runner. No tests will run. + You may need to add this file to your project's pytest exclusions. + """) + sys.exit(0) + + +def resolve(file: str) -> str: + return os.path.join(os.path.dirname(__file__), "test_fixtures", file) + + +def read_fixture_doc(filename): + path = resolve(filename) + + with gzip.open(path, "r") as f: + doc_json = json.loads(f.read()) + + doc = Document.from_json(doc_json) + + return doc + + +@with_timo_container +class TestInterfaceIntegration(unittest.TestCase): + def test__predictions(self, container): + doc_file = resolve("test_doc.json") + with open(doc_file) as f: + doc = Document.from_json(json.load(f)) + + tokens = [api.SpanGroup.from_mmda(sg) for sg in doc.tokens] + rows = [api.SpanGroup.from_mmda(sg) for sg in doc.rows] + + instances = [Instance( + symbols=doc.symbols, + tokens=tokens, + rows=rows + )] + + predictions = container.predict_batch(instances) + + prediction = predictions[0] + + self.assertTrue(len(prediction.words) <= len(tokens)) + self.assertTrue(all([w.text is not None for w in prediction.words])) diff --git a/src/ai2_internal/dwp_heuristic/interface.py b/src/ai2_internal/dwp_heuristic/interface.py new file mode 100644 index 00000000..f3f65111 --- /dev/null +++ b/src/ai2_internal/dwp_heuristic/interface.py @@ -0,0 +1,74 @@ +from typing import List + +from pydantic import BaseModel, BaseSettings, Field + +from ai2_internal.api import SpanGroup +from mmda.predictors.heuristic_predictors.dictionary_word_predictor import ( + DictionaryWordPredictor +) +from mmda.types.document import Document + + +class Instance(BaseModel): + """ + Inference input for a single paper. + """ + symbols: str = Field(description="Extracted PDF document text") + tokens: List[SpanGroup] = Field(description="The tokens to coerce into words") + rows: List[SpanGroup] = Field(description="Document rows, used as signal for determining word boundaries") + + +class Prediction(BaseModel): + """ + Inference output for a single paper. + """ + words: List[SpanGroup] = Field(description="Input tokens coerced into words. Includes cleaned-up text.") + + +class PredictorConfig(BaseSettings): + """ + no-op for this model + """ + pass + + +class Predictor: + """ + This class is instantiated at application startup as a singleton, + and is used by the TIMO framework to interface with the underlying + DWP predictor. + """ + + _config: PredictorConfig + _artifacts_dir: str + + def __init__(self, config: PredictorConfig, artifacts_dir: str): + self._config = config + self._artifacts_dir = artifacts_dir + self._load_model() + + def _load_model(self) -> None: + self._predictor = DictionaryWordPredictor("/dev/null") + + def predict_one(self, instance: Instance) -> Prediction: + doc = Document(instance.symbols) + doc.annotate(tokens=[t.to_mmda() for t in instance.tokens]) + doc.annotate(rows=[r.to_mmda() for r in instance.rows]) + words = self._predictor.predict(doc) + + # RE: https://github.com/allenai/scholar/issues/36200 + for word in words: + if word.text: + word.text = word.text.replace("\u0000", "") + + return Prediction( + words=[SpanGroup.from_mmda(w) for w in words] + ) + + def predict_batch(self, instances: List[Instance]) -> List[Prediction]: + """ + Method called by the client application. One or more Instances will + be provided, and the caller expects a corresponding Prediction for + each one. + """ + return [self.predict_one(instance) for instance in instances] diff --git a/src/ai2_internal/dwp_heuristic/test_fixtures/test_doc.json b/src/ai2_internal/dwp_heuristic/test_fixtures/test_doc.json new file mode 100644 index 00000000..d288c4a5 --- /dev/null +++ b/src/ai2_internal/dwp_heuristic/test_fixtures/test_doc.json @@ -0,0 +1 @@ +{"symbols": "Dynamics of stochastic gradient descent for two-layer fraction(-)\nneural networks in the teacher-student setup fraction(-)\nSebastian Goldt1, Madhu S. Advani2, Andrew M. Saxe3\nFlorent Krzakala4, Lenka Zdeborov\u00e11\n1\nInstitut de Physique Th\u00e9orique, CNRS, CEA, Universit\u00e9 Paris-Saclay, Saclay, France\n2\nCenter for Brain Science, Harvard University, Cambridge, MA 02138, USA\n3\nDepartment of Experimental Psychology, University of Oxford, Oxford, United Kingdom\n4\nLaboratoire de Physique Statistique, Sorbonne Universit\u00e9s,\nUniversit\u00e9 Pierre et Marie Curie Paris 6, Ecole Normale Sup\u00e9rieure, 75005 Paris, France\nAbstract\nDeep neural networks achieve stellar generalisation even when they have enough\nparameters to easily \ufb01t all their training data. We study this phenomenon by\nanalysing the dynamics and the performance of over-parameterised two-layer\nneural networks in the teacher-student setup, where one network, the student, is\ntrained on data generated by another network, called the teacher. We show h", "pages": [{"spans": [{"start": 0, "end": 75, "box": {"left": 0.05228758169934641, "top": 0.10000001843434336, "width": 0.7729722155228758, "height": 0.8366561343434346, "page": 0}}], "id": 0}], "tokens": [{"spans": [{"start": 0, "end": 8, "box": {"left": 0.18025165032679738, "top": 0.1277161085858586, "width": 0.1175822952614379, "height": 0.019476063131313087, "page": 0}}], "id": 0}, {"spans": [{"start": 9, "end": 11, "box": {"left": 0.30635727124183004, "top": 0.1277161085858586, "width": 0.02430407728758177, "height": 0.015324295454545445, "page": 0}}], "id": 1}, {"spans": [{"start": 12, "end": 22, "box": {"left": 0.3368217647058823, "top": 0.1277161085858586, "width": 0.11609141666666667, "height": 0.015324295454545445, "page": 0}}], "id": 2}, {"spans": [{"start": 23, "end": 31, "box": {"left": 0.461127091503268, "top": 0.1277161085858586, "width": 0.1007607924836601, "height": 0.019497780303030232, "page": 0}}], "id": 3}, {"spans": [{"start": 32, "end": 39, "box": {"left": 0.5696517320261438, "top": 0.12804213762626265, "width": 0.08832742647058822, "height": 0.014998266414141426, "page": 0}}], "id": 4}, {"spans": [{"start": 40, "end": 43, "box": {"left": 0.6654335947712419, "top": 0.1277161085858586, "width": 0.03454326307189526, "height": 0.015324295454545445, "page": 0}}], "id": 5}, {"spans": [{"start": 44, "end": 53, "box": {"left": 0.7078532352941177, "top": 0.12804215909090913, "width": 0.11263146568627436, "height": 0.019150012626262564, "page": 0}}], "id": 6}, {"spans": [{"start": 54, "end": 65, "box": {"left": 0.17647058823529413, "top": 0.10000001843434336, "width": 0.6470588235294118, "height": 0.005031547222222224, "page": 0}}], "id": 7}, {"spans": [{"start": 66, "end": 72, "box": {"left": 0.23286849673202617, "top": 0.15319998737373736, "width": 0.07690668464052283, "height": 0.014998244949494921, "page": 0}}], "id": 8}, {"spans": [{"start": 73, "end": 81, "box": {"left": 0.3180453267973856, "top": 0.15319998737373736, "width": 0.10928401094771245, "height": 0.014998244949494921, "page": 0}}], "id": 9}, {"spans": [{"start": 82, "end": 84, "box": {"left": 0.435599477124183, "top": 0.15287393686868683, "width": 0.022531908496731978, "height": 0.01502000252525254, "page": 0}}], "id": 10}, {"spans": [{"start": 85, "end": 88, "box": {"left": 0.46620467320261433, "top": 0.15319998737373736, "width": 0.03642806470588239, "height": 0.014998244949494921, "page": 0}}], "id": 11}, {"spans": [{"start": 89, "end": 104, "box": {"left": 0.5107341013071895, "top": 0.15319996590909088, "width": 0.18585332516339859, "height": 0.014998266414141426, "page": 0}}], "id": 12}, {"spans": [{"start": 105, "end": 110, "box": {"left": 0.7043513071895424, "top": 0.15419989015151508, "width": 0.06247610620915034, "height": 0.018150059343434444, "page": 0}}], "id": 13}, {"spans": [{"start": 111, "end": 122, "box": {"left": 0.17647058823529413, "top": 0.19206439393939395, "width": 0.6470588235294118, "height": 0.0006313131313131215, "page": 0}}], "id": 14}, {"spans": [{"start": 123, "end": 132, "box": {"left": 0.30809916666666665, "top": 0.22959448434343432, "width": 0.06609174346405233, "height": 0.008943697474747464, "page": 0}}], "id": 15}, {"spans": [{"start": 133, "end": 140, "box": {"left": 0.3791396405228758, "top": 0.22767546136363637, "width": 0.04934020964052288, "height": 0.012887985606060637, "page": 0}}], "id": 16}, {"spans": [{"start": 141, "end": 146, "box": {"left": 0.4332169607843137, "top": 0.229795765530303, "width": 0.050122327777777864, "height": 0.0086795375, "page": 0}}], "id": 17}, {"spans": [{"start": 147, "end": 149, "box": {"left": 0.4882880718954248, "top": 0.22959448434343432, "width": 0.011899802777777913, "height": 0.008943697474747464, "page": 0}}], "id": 18}, {"spans": [{"start": 150, "end": 158, "box": {"left": 0.5050552287581699, "top": 0.22767546136363637, "width": 0.060489931862745205, "height": 0.012887985606060637, "page": 0}}], "id": 19}, {"spans": [{"start": 159, "end": 165, "box": {"left": 0.5702008986928104, "top": 0.22961970492424236, "width": 0.055380373039215725, "height": 0.008855598106060653, "page": 0}}], "id": 20}, {"spans": [{"start": 166, "end": 168, "box": {"left": 0.6301230392156862, "top": 0.2297958113636363, "width": 0.018557841993464108, "height": 0.008666941161616176, "page": 0}}], "id": 21}, {"spans": [{"start": 169, "end": 174, "box": {"left": 0.6539715032679739, "top": 0.22767549494949496, "width": 0.03732050294117639, "height": 0.010862686868686827, "page": 0}}], "id": 22}, {"spans": [{"start": 175, "end": 182, "box": {"left": 0.3675071568627451, "top": 0.2445596497474748, "width": 0.05096879281045752, "height": 0.008679491666666539, "page": 0}}], "id": 23}, {"spans": [{"start": 183, "end": 193, "box": {"left": 0.4230503267973856, "top": 0.24243935025252522, "width": 0.07476767696078435, "height": 0.012887972979798046, "page": 0}}], "id": 24}, {"spans": [{"start": 194, "end": 199, "box": {"left": 0.5026366013071896, "top": 0.2445596497474748, "width": 0.04382239215686268, "height": 0.008679491666666539, "page": 0}}], "id": 25}, {"spans": [{"start": 200, "end": 210, "box": {"left": 0.5511798692810458, "top": 0.24243935025252522, "width": 0.08009853382352938, "height": 0.010799791161616168, "page": 0}}], "id": 26}, {"spans": [{"start": 211, "end": 212, "box": {"left": 0.21900008169934643, "top": 0.257212943560606, "width": 0.0032248129084967436, "height": 0.005952384722222204, "page": 0}}], "id": 27}, {"spans": [{"start": 213, "end": 221, "box": {"left": 0.22860991830065358, "top": 0.259138225, "width": 0.045857290196078426, "height": 0.008717254797979801, "page": 0}}], "id": 28}, {"spans": [{"start": 222, "end": 224, "box": {"left": 0.27896016339869284, "top": 0.2591382045454545, "width": 0.01460202189542481, "height": 0.008717275252525247, "page": 0}}], "id": 29}, {"spans": [{"start": 225, "end": 233, "box": {"left": 0.2982179248366013, "top": 0.259138225, "width": 0.05902672777777779, "height": 0.011333744696969683, "page": 0}}], "id": 30}, {"spans": [{"start": 234, "end": 244, "box": {"left": 0.3619166339869281, "top": 0.259138225, "width": 0.06980330294117654, "height": 0.011321118434343469, "page": 0}}], "id": 31}, {"spans": [{"start": 245, "end": 250, "box": {"left": 0.437140816993464, "top": 0.25922627058080816, "width": 0.04523862973856213, "height": 0.010277088005050394, "page": 0}}], "id": 32}, {"spans": [{"start": 251, "end": 255, "box": {"left": 0.4878003267973856, "top": 0.25922627058080816, "width": 0.03527606437908504, "height": 0.010277088005050394, "page": 0}}], "id": 33}, {"spans": [{"start": 256, "end": 266, "box": {"left": 0.5282693464052287, "top": 0.259138225, "width": 0.06661261013071895, "height": 0.008767583080808161, "page": 0}}], "id": 34}, {"spans": [{"start": 267, "end": 280, "box": {"left": 0.5995377124183007, "top": 0.259138225, "width": 0.08298908725490195, "height": 0.011333744696969683, "page": 0}}], "id": 35}, {"spans": [{"start": 281, "end": 288, "box": {"left": 0.6881755718954249, "top": 0.259138225, "width": 0.04483168529411763, "height": 0.011333744696969683, "page": 0}}], "id": 36}, {"spans": [{"start": 289, "end": 295, "box": {"left": 0.7381676470588235, "top": 0.2594023851010101, "width": 0.04377360686274501, "height": 0.008453094696969643, "page": 0}}], "id": 37}, {"spans": [{"start": 296, "end": 297, "box": {"left": 0.24812771241830067, "top": 0.2718795597222223, "width": 0.005070818627450957, "height": 0.005952384722222204, "page": 0}}], "id": 38}, {"spans": [{"start": 298, "end": 304, "box": {"left": 0.25882334967320264, "top": 0.2738928867424243, "width": 0.042975944444444414, "height": 0.0086795375, "page": 0}}], "id": 39}, {"spans": [{"start": 305, "end": 308, "box": {"left": 0.30616199346405226, "top": 0.27380485378787883, "width": 0.01868801960784311, "height": 0.00871724217171721, "page": 0}}], "id": 40}, {"spans": [{"start": 309, "end": 314, "box": {"left": 0.3291638725490196, "top": 0.27380485378787883, "width": 0.0356505212418301, "height": 0.00871724217171721, "page": 0}}], "id": 41}, {"spans": [{"start": 315, "end": 323, "box": {"left": 0.3698119281045752, "top": 0.27380485378787883, "width": 0.05311758398692806, "height": 0.010365095707070615, "page": 0}}], "id": 42}, {"spans": [{"start": 324, "end": 331, "box": {"left": 0.4282039215686274, "top": 0.2738048207070708, "width": 0.05246638284313726, "height": 0.008767603535353441, "page": 0}}], "id": 43}, {"spans": [{"start": 332, "end": 343, "box": {"left": 0.4851143790849673, "top": 0.27380485378787883, "width": 0.06996601535947716, "height": 0.011333782575757512, "page": 0}}], "id": 44}, {"spans": [{"start": 344, "end": 354, "box": {"left": 0.5605013071895425, "top": 0.2738048207070708, "width": 0.07506127679738561, "height": 0.011333815656565549, "page": 0}}], "id": 45}, {"spans": [{"start": 355, "end": 357, "box": {"left": 0.6407229901960785, "top": 0.27391806527777784, "width": 0.025769290849673165, "height": 0.008478273106060585, "page": 0}}], "id": 46}, {"spans": [{"start": 358, "end": 364, "box": {"left": 0.671213137254902, "top": 0.2738928867424243, "width": 0.04348049248366015, "height": 0.010277062752525157, "page": 0}}], "id": 47}, {"spans": [{"start": 365, "end": 368, "box": {"left": 0.7198866013071895, "top": 0.2738928867424243, "width": 0.03206916013071892, "height": 0.0086795375, "page": 0}}], "id": 48}, {"spans": [{"start": 369, "end": 370, "box": {"left": 0.2035258431372549, "top": 0.2865462222222222, "width": 0.004432693464052284, "height": 0.006075659090909069, "page": 0}}], "id": 49}, {"spans": [{"start": 371, "end": 381, "box": {"left": 0.21387803921568627, "top": 0.2887369431818182, "width": 0.0766078294117647, "height": 0.011056958333333367, "page": 0}}], "id": 50}, {"spans": [{"start": 382, "end": 384, "box": {"left": 0.29501135620915037, "top": 0.2884727830808081, "width": 0.013902048366012998, "height": 0.00871724217171721, "page": 0}}], "id": 51}, {"spans": [{"start": 385, "end": 397, "box": {"left": 0.31236450980392155, "top": 0.2884727830808081, "width": 0.08717274313725493, "height": 0.011321118434343469, "page": 0}}], "id": 52}, {"spans": [{"start": 398, "end": 409, "box": {"left": 0.40420928104575166, "top": 0.2884727830808081, "width": 0.07782859705882356, "height": 0.011333782575757678, "page": 0}}], "id": 53}, {"spans": [{"start": 410, "end": 420, "box": {"left": 0.48723083333333334, "top": 0.2884727830808081, "width": 0.06744282467320256, "height": 0.011333782575757623, "page": 0}}], "id": 54}, {"spans": [{"start": 421, "end": 423, "box": {"left": 0.5596224183006536, "top": 0.2884727830808081, "width": 0.013902048366013164, "height": 0.00871724217171721, "page": 0}}], "id": 55}, {"spans": [{"start": 424, "end": 431, "box": {"left": 0.5773336928104575, "top": 0.28847275000000006, "width": 0.049633858496732075, "height": 0.010365128787878819, "page": 0}}], "id": 56}, {"spans": [{"start": 432, "end": 439, "box": {"left": 0.6324860784313725, "top": 0.28847275000000006, "width": 0.049633858496732075, "height": 0.010365128787878819, "page": 0}}], "id": 57}, {"spans": [{"start": 440, "end": 446, "box": {"left": 0.687312908496732, "top": 0.28847275000000006, "width": 0.043936350163398696, "height": 0.008767616161616087, "page": 0}}], "id": 58}, {"spans": [{"start": 447, "end": 454, "box": {"left": 0.7360189052287582, "top": 0.28847275000000006, "width": 0.060898763071895345, "height": 0.011333815656565716, "page": 0}}], "id": 59}, {"spans": [{"start": 455, "end": 456, "box": {"left": 0.30282290849673205, "top": 0.30121415568181814, "width": 0.005241745098039208, "height": 0.005952384722222204, "page": 0}}], "id": 60}, {"spans": [{"start": 457, "end": 468, "box": {"left": 0.3134632352941176, "top": 0.3031394118686868, "width": 0.07541948921568631, "height": 0.00871728005050515, "page": 0}}], "id": 61}, {"spans": [{"start": 469, "end": 471, "box": {"left": 0.3937175163398693, "top": 0.30313941666666666, "width": 0.014602038235294157, "height": 0.008717275252525247, "page": 0}}], "id": 62}, {"spans": [{"start": 472, "end": 480, "box": {"left": 0.41297529411764705, "top": 0.3031394118686868, "width": 0.059026695098039206, "height": 0.011333744696969683, "page": 0}}], "id": 63}, {"spans": [{"start": 481, "end": 493, "box": {"left": 0.47708099673202614, "top": 0.3031394118686868, "width": 0.07123577026143796, "height": 0.011321105808080878, "page": 0}}], "id": 64}, {"spans": [{"start": 494, "end": 502, "box": {"left": 0.5539655555555556, "top": 0.30313941666666666, "width": 0.06138711993464052, "height": 0.008767565656565723, "page": 0}}], "id": 65}, {"spans": [{"start": 503, "end": 515, "box": {"left": 0.6199758660130719, "top": 0.3031394118686868, "width": 0.07644500228758178, "height": 0.010365133585858666, "page": 0}}], "id": 66}, {"spans": [{"start": 516, "end": 526, "box": {"left": 0.21213630718954246, "top": 0.31691340176767685, "width": 0.06661269183006541, "height": 0.00876757045454546, "page": 0}}], "id": 67}, {"spans": [{"start": 527, "end": 533, "box": {"left": 0.28340473856209153, "top": 0.31691340176767685, "width": 0.03828765915032678, "height": 0.008717280050504983, "page": 0}}], "id": 68}, {"spans": [{"start": 534, "end": 536, "box": {"left": 0.3264946241830065, "top": 0.318221627020202, "width": 0.011362584313725499, "height": 0.007409054797979819, "page": 0}}], "id": 69}, {"spans": [{"start": 537, "end": 542, "box": {"left": 0.3421059640522876, "top": 0.31691340176767685, "width": 0.038352789869281034, "height": 0.008717280050504983, "page": 0}}], "id": 70}, {"spans": [{"start": 543, "end": 548, "box": {"left": 0.3853098039215686, "top": 0.31691340176767685, "width": 0.03539004477124191, "height": 0.008767570454545404, "page": 0}}], "id": 71}, {"spans": [{"start": 549, "end": 554, "box": {"left": 0.4253555555555556, "top": 0.31691340176767685, "width": 0.03138544444444441, "height": 0.008717280050504983, "page": 0}}], "id": 72}, {"spans": [{"start": 555, "end": 557, "box": {"left": 0.46203160130718957, "top": 0.3169008022727273, "width": 0.010760231045751634, "height": 0.010377733080808083, "page": 0}}], "id": 73}, {"spans": [{"start": 558, "end": 563, "box": {"left": 0.4779522385620915, "top": 0.31691340176767685, "width": 0.03654582908496723, "height": 0.008717280050504983, "page": 0}}], "id": 74}, {"spans": [{"start": 564, "end": 571, "box": {"left": 0.5190886928104576, "top": 0.31691340176767685, "width": 0.056438443464052135, "height": 0.008729780050504954, "page": 0}}], "id": 75}, {"spans": [{"start": 572, "end": 583, "box": {"left": 0.5806061437908496, "top": 0.31691340176767685, "width": 0.07301019836601319, "height": 0.011321105808080711, "page": 0}}], "id": 76}, {"spans": [{"start": 584, "end": 589, "box": {"left": 0.658906977124183, "top": 0.31685048611111116, "width": 0.039362002450980316, "height": 0.008830486111111091, "page": 0}}], "id": 77}, {"spans": [{"start": 590, "end": 596, "box": {"left": 0.7036083986928104, "top": 0.31691340176767685, "width": 0.03522722450980398, "height": 0.010365133585858555, "page": 0}}], "id": 78}, {"spans": [{"start": 597, "end": 603, "box": {"left": 0.7439960130718954, "top": 0.31717756186868695, "width": 0.04377359052287577, "height": 0.00845311994949488, "page": 0}}], "id": 79}, {"spans": [{"start": 604, "end": 612, "box": {"left": 0.46383258169934644, "top": 0.367086845959596, "width": 0.07249302679738562, "height": 0.010626828282828238, "page": 0}}], "id": 80}, {"spans": [{"start": 613, "end": 617, "box": {"left": 0.23533745098039216, "top": 0.4002067664141415, "width": 0.0338345370915033, "height": 0.011056996212121084, "page": 0}}], "id": 81}, {"spans": [{"start": 618, "end": 624, "box": {"left": 0.27397506535947713, "top": 0.39994260631313133, "width": 0.04035888284313721, "height": 0.008717280050505094, "page": 0}}], "id": 82}, {"spans": [{"start": 625, "end": 633, "box": {"left": 0.318989477124183, "top": 0.39994260631313133, "width": 0.05899735980392157, "height": 0.00876757045454546, "page": 0}}], "id": 83}, {"spans": [{"start": 634, "end": 641, "box": {"left": 0.383314477124183, "top": 0.39994260631313133, "width": 0.04847320669934646, "height": 0.00876757045454546, "page": 0}}], "id": 84}, {"spans": [{"start": 642, "end": 649, "box": {"left": 0.4370006045751634, "top": 0.39994260631313133, "width": 0.03926055767973857, "height": 0.008717280050505094, "page": 0}}], "id": 85}, {"spans": [{"start": 650, "end": 664, "box": {"left": 0.48073637254901963, "top": 0.39994260631313133, "width": 0.09125822924836596, "height": 0.011333694191919208, "page": 0}}], "id": 86}, {"spans": [{"start": 665, "end": 669, "box": {"left": 0.576699297385621, "top": 0.4027477323232324, "width": 0.02965440571895417, "height": 0.005962444444444381, "page": 0}}], "id": 87}, {"spans": [{"start": 670, "end": 674, "box": {"left": 0.6109928758169935, "top": 0.39994260631313133, "width": 0.034916448202614414, "height": 0.00876757045454546, "page": 0}}], "id": 88}, {"spans": [{"start": 675, "end": 679, "box": {"left": 0.6504173529411765, "top": 0.39994260631313133, "width": 0.027359407679738434, "height": 0.011333694191919153, "page": 0}}], "id": 89}, {"spans": [{"start": 680, "end": 684, "box": {"left": 0.6823831699346405, "top": 0.39994260631313133, "width": 0.0299002491830066, "height": 0.00876757045454546, "page": 0}}], "id": 90}, {"spans": [{"start": 685, "end": 691, "box": {"left": 0.7170700980392157, "top": 0.39994260631313133, "width": 0.04763725065359481, "height": 0.011333694191919208, "page": 0}}], "id": 91}, {"spans": [{"start": 692, "end": 702, "box": {"left": 0.2351581862745098, "top": 0.4150248214646465, "width": 0.07207934640522878, "height": 0.010012905808080785, "page": 0}}], "id": 92}, {"spans": [{"start": 703, "end": 705, "box": {"left": 0.31417816993464054, "top": 0.4150248214646465, "width": 0.012204177450980402, "height": 0.007409054797979819, "page": 0}}], "id": 93}, {"spans": [{"start": 706, "end": 712, "box": {"left": 0.3333395588235294, "top": 0.4137165962121212, "width": 0.03790768578431375, "height": 0.011333694191919208, "page": 0}}], "id": 94}, {"spans": [{"start": 713, "end": 715, "box": {"left": 0.3782210784313726, "top": 0.4137165962121212, "width": 0.013349906045751592, "height": 0.008717280050505094, "page": 0}}], "id": 95}, {"spans": [{"start": 716, "end": 719, "box": {"left": 0.39821271241830064, "top": 0.4137165962121212, "width": 0.015641242973856218, "height": 0.008717280050505039, "page": 0}}], "id": 96}, {"spans": [{"start": 720, "end": 725, "box": {"left": 0.42046251633986925, "top": 0.4137165962121212, "width": 0.030253073856209234, "height": 0.008717280050505094, "page": 0}}], "id": 97}, {"spans": [{"start": 726, "end": 734, "box": {"left": 0.4569422385620915, "top": 0.4137165962121212, "width": 0.05094210539215693, "height": 0.011333694191919264, "page": 0}}], "id": 98}, {"spans": [{"start": 735, "end": 740, "box": {"left": 0.5148747712418301, "top": 0.413716601010101, "width": 0.030219894771241762, "height": 0.00872983838383845, "page": 0}}], "id": 99}, {"spans": [{"start": 741, "end": 743, "box": {"left": 0.5572988562091503, "top": 0.41398074532828283, "width": 0.021319982026143935, "height": 0.00846569406565656, "page": 0}}], "id": 100}, {"spans": [{"start": 744, "end": 749, "box": {"left": 0.5858583333333334, "top": 0.413716601010101, "width": 0.03470307794117644, "height": 0.011333689393939417, "page": 0}}], "id": 101}, {"spans": [{"start": 750, "end": 754, "box": {"left": 0.6272529411764706, "top": 0.4137165962121212, "width": 0.02309660130718949, "height": 0.008717280050505094, "page": 0}}], "id": 102}, {"spans": [{"start": 755, "end": 765, "box": {"left": 0.6571573529411765, "top": 0.4137165962121212, "width": 0.08544599460784308, "height": 0.01132113106060606, "page": 0}}], "id": 103}, {"spans": [{"start": 766, "end": 768, "box": {"left": 0.7489461928104575, "top": 0.413716601010101, "width": 0.01613945049019616, "height": 0.011333689393939417, "page": 0}}], "id": 104}, {"spans": [{"start": 769, "end": 778, "box": {"left": 0.23568952614379082, "top": 0.42749058611111107, "width": 0.0625319256535948, "height": 0.011333732070707148, "page": 0}}], "id": 105}, {"spans": [{"start": 779, "end": 782, "box": {"left": 0.30514549019607845, "top": 0.42749058611111107, "width": 0.019742547385620923, "height": 0.008717305303030387, "page": 0}}], "id": 106}, {"spans": [{"start": 783, "end": 791, "box": {"left": 0.3318950653594771, "top": 0.42749058611111107, "width": 0.06251531045751635, "height": 0.011333732070707092, "page": 0}}], "id": 107}, {"spans": [{"start": 792, "end": 795, "box": {"left": 0.4019155555555555, "top": 0.42749061616161615, "width": 0.023212858333333364, "height": 0.008717275252525247, "page": 0}}], "id": 108}, {"spans": [{"start": 796, "end": 799, "box": {"left": 0.4317037581699346, "top": 0.42749058611111107, "width": 0.01974254738562098, "height": 0.008717305303030387, "page": 0}}], "id": 109}, {"spans": [{"start": 800, "end": 811, "box": {"left": 0.45808803921568625, "top": 0.42749058611111107, "width": 0.08348676307189556, "height": 0.011321156313131353, "page": 0}}], "id": 110}, {"spans": [{"start": 812, "end": 814, "box": {"left": 0.5485984477124183, "top": 0.42749058611111107, "width": 0.014180101633986975, "height": 0.008717305303030332, "page": 0}}], "id": 111}, {"spans": [{"start": 815, "end": 833, "box": {"left": 0.5686399183006536, "top": 0.42749058611111107, "width": 0.126724410620915, "height": 0.011321156313131353, "page": 0}}], "id": 112}, {"spans": [{"start": 834, "end": 843, "box": {"left": 0.7019562581699347, "top": 0.42749058611111107, "width": 0.06327911960784305, "height": 0.011333732070707092, "page": 0}}], "id": 113}, {"spans": [{"start": 844, "end": 850, "box": {"left": 0.23534081699346407, "top": 0.441264576010101, "width": 0.04087987042483657, "height": 0.008717305303030332, "page": 0}}], "id": 114}, {"spans": [{"start": 851, "end": 859, "box": {"left": 0.2811521568627451, "top": 0.441264576010101, "width": 0.05975901960784319, "height": 0.008767595707070752, "page": 0}}], "id": 115}, {"spans": [{"start": 860, "end": 862, "box": {"left": 0.3461747222222222, "top": 0.441264576010101, "width": 0.012403445588235307, "height": 0.008591484595959653, "page": 0}}], "id": 116}, {"spans": [{"start": 863, "end": 866, "box": {"left": 0.36337684640522877, "top": 0.441264576010101, "width": 0.019742547385620923, "height": 0.008717305303030332, "page": 0}}], "id": 117}, {"spans": [{"start": 867, "end": 882, "box": {"left": 0.38798446078431376, "top": 0.441264576010101, "width": 0.10090457924836604, "height": 0.008717305303030332, "page": 0}}], "id": 118}, {"spans": [{"start": 883, "end": 889, "box": {"left": 0.4940363562091504, "top": 0.4425728265151515, "width": 0.03744280620915025, "height": 0.010012905808080785, "page": 0}}], "id": 119}, {"spans": [{"start": 890, "end": 895, "box": {"left": 0.5371246732026144, "top": 0.441264576010101, "width": 0.039883658496732144, "height": 0.008767595707070752, "page": 0}}], "id": 120}, {"spans": [{"start": 896, "end": 899, "box": {"left": 0.5821391013071896, "top": 0.4440697020202021, "width": 0.023163070261437935, "height": 0.005912179292929198, "page": 0}}], "id": 121}, {"spans": [{"start": 900, "end": 908, "box": {"left": 0.6102170751633987, "top": 0.441264576010101, "width": 0.05721854150326788, "height": 0.010365133585858555, "page": 0}}], "id": 122}, {"spans": [{"start": 909, "end": 912, "box": {"left": 0.6729483006535948, "top": 0.441264576010101, "width": 0.01974254738562098, "height": 0.008717305303030332, "page": 0}}], "id": 123}, {"spans": [{"start": 913, "end": 921, "box": {"left": 0.6981868790849673, "top": 0.441264606060606, "width": 0.050360992483660105, "height": 0.010365103535353526, "page": 0}}], "id": 124}, {"spans": [{"start": 922, "end": 924, "box": {"left": 0.7541103431372549, "top": 0.441264576010101, "width": 0.010128611111111074, "height": 0.008717305303030276, "page": 0}}], "id": 125}], "rows": [{"spans": [{"start": 0, "end": 65, "box": {"left": 0.17647058823529413, "top": 0.10000001843434336, "width": 0.6470588235294118, "height": 0.047213870454545476, "page": 0}}], "id": 0}, {"spans": [{"start": 66, "end": 122, "box": {"left": 0.17647058823529413, "top": 0.15287393686868683, "width": 0.6470588235294118, "height": 0.03982177020202024, "page": 0}}], "id": 1}, {"spans": [{"start": 123, "end": 174, "box": {"left": 0.30809916666666665, "top": 0.22767546136363637, "width": 0.3831928395424837, "height": 0.012887985606060637, "page": 0}}], "id": 2}, {"spans": [{"start": 175, "end": 210, "box": {"left": 0.3675071568627451, "top": 0.24243935025252522, "width": 0.26377124624183007, "height": 0.012887972979798046, "page": 0}}], "id": 3}, {"spans": [{"start": 211, "end": 212, "box": {"left": 0.21900008169934643, "top": 0.257212943560606, "width": 0.0032248129084967436, "height": 0.005952384722222204, "page": 0}}], "id": 4}, {"spans": [{"start": 213, "end": 295, "box": {"left": 0.22860991830065358, "top": 0.2591382045454545, "width": 0.553331335620915, "height": 0.011333765151515185, "page": 0}}], "id": 5}, {"spans": [{"start": 296, "end": 297, "box": {"left": 0.24812771241830067, "top": 0.2718795597222223, "width": 0.005070818627450957, "height": 0.005952384722222204, "page": 0}}], "id": 6}, {"spans": [{"start": 298, "end": 368, "box": {"left": 0.25882334967320264, "top": 0.2738048207070708, "width": 0.4931324117647058, "height": 0.011333815656565549, "page": 0}}], "id": 7}, {"spans": [{"start": 369, "end": 370, "box": {"left": 0.2035258431372549, "top": 0.2865462222222222, "width": 0.004432693464052284, "height": 0.006075659090909069, "page": 0}}], "id": 8}, {"spans": [{"start": 371, "end": 454, "box": {"left": 0.21387803921568627, "top": 0.28847275000000006, "width": 0.5830396290849673, "height": 0.011333815656565716, "page": 0}}], "id": 9}, {"spans": [{"start": 455, "end": 456, "box": {"left": 0.30282290849673205, "top": 0.30121415568181814, "width": 0.005241745098039208, "height": 0.005952384722222204, "page": 0}}], "id": 10}, {"spans": [{"start": 457, "end": 515, "box": {"left": 0.3134632352941176, "top": 0.3031394118686868, "width": 0.382957633006536, "height": 0.011333744696969683, "page": 0}}], "id": 11}, {"spans": [{"start": 516, "end": 603, "box": {"left": 0.21213630718954246, "top": 0.31685048611111116, "width": 0.5756332964052288, "height": 0.011384021464646399, "page": 0}}], "id": 12}, {"spans": [{"start": 604, "end": 612, "box": {"left": 0.46383258169934644, "top": 0.367086845959596, "width": 0.07249302679738562, "height": 0.010626828282828238, "page": 0}}], "id": 13}, {"spans": [{"start": 613, "end": 691, "box": {"left": 0.23533745098039216, "top": 0.39994260631313133, "width": 0.5293698977124184, "height": 0.011333694191919208, "page": 0}}], "id": 14}, {"spans": [{"start": 692, "end": 768, "box": {"left": 0.2351581862745098, "top": 0.4137165962121212, "width": 0.5299274570261439, "height": 0.011333694191919264, "page": 0}}], "id": 15}, {"spans": [{"start": 769, "end": 843, "box": {"left": 0.23568952614379082, "top": 0.42749058611111107, "width": 0.5295458516339869, "height": 0.011333732070707148, "page": 0}}], "id": 16}, {"spans": [{"start": 844, "end": 924, "box": {"left": 0.23534081699346407, "top": 0.441264576010101, "width": 0.5288981372549019, "height": 0.011321156313131298, "page": 0}}], "id": 17}]} \ No newline at end of file