matchlab 0.1.0.dev1__py3-none-any.whl

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 (72) hide show
  1. matchlab/__init__.py +55 -0
  2. matchlab/adapters/__init__.py +24 -0
  3. matchlab/adapters/base.py +446 -0
  4. matchlab/adapters/duckdb.py +659 -0
  5. matchlab/cli.py +177 -0
  6. matchlab/core/__init__.py +1 -0
  7. matchlab/core/dataframes.py +254 -0
  8. matchlab/core/dsu.py +62 -0
  9. matchlab/core/exceptions.py +40 -0
  10. matchlab/core/hash.py +141 -0
  11. matchlab/core/kinds.py +12 -0
  12. matchlab/core/logging.py +190 -0
  13. matchlab/core/resolver_output.py +173 -0
  14. matchlab/core/schemas.py +82 -0
  15. matchlab/core/sql.py +12 -0
  16. matchlab/document.py +329 -0
  17. matchlab/eval/__init__.py +11 -0
  18. matchlab/eval/judgements.py +41 -0
  19. matchlab/eval/metrics.py +244 -0
  20. matchlab/eval/samples.py +415 -0
  21. matchlab/eval/tui/__init__.py +66 -0
  22. matchlab/eval/tui/app.py +418 -0
  23. matchlab/eval/tui/modals.py +84 -0
  24. matchlab/eval/tui/styles.tcss +184 -0
  25. matchlab/eval/tui/widgets/__init__.py +1 -0
  26. matchlab/eval/tui/widgets/assignment.py +72 -0
  27. matchlab/eval/tui/widgets/styling.py +75 -0
  28. matchlab/eval/tui/widgets/table.py +285 -0
  29. matchlab/lineage.py +239 -0
  30. matchlab/locations.py +347 -0
  31. matchlab/models/__init__.py +10 -0
  32. matchlab/models/comparison.py +67 -0
  33. matchlab/models/dedupers/__init__.py +5 -0
  34. matchlab/models/dedupers/base.py +38 -0
  35. matchlab/models/dedupers/naive.py +77 -0
  36. matchlab/models/linkers/__init__.py +9 -0
  37. matchlab/models/linkers/base.py +41 -0
  38. matchlab/models/linkers/deterministic.py +229 -0
  39. matchlab/models/linkers/splinklinker.py +262 -0
  40. matchlab/models/linkers/weighteddeterministic.py +143 -0
  41. matchlab/models/models.py +198 -0
  42. matchlab/progress.py +455 -0
  43. matchlab/recordstep.py +181 -0
  44. matchlab/resolvers/__init__.py +10 -0
  45. matchlab/resolvers/base.py +63 -0
  46. matchlab/resolvers/components.py +66 -0
  47. matchlab/resolvers/resolvers.py +409 -0
  48. matchlab/sources.py +291 -0
  49. matchlab/specs.py +129 -0
  50. matchlab/steps.py +284 -0
  51. matchlab/testkit/__init__.py +104 -0
  52. matchlab/testkit/_generate.py +240 -0
  53. matchlab/testkit/compare.py +178 -0
  54. matchlab/testkit/entities.py +290 -0
  55. matchlab/testkit/features.py +146 -0
  56. matchlab/testkit/linked.py +527 -0
  57. matchlab/testkit/matchers.py +233 -0
  58. matchlab/testkit/models.py +165 -0
  59. matchlab/testkit/sources.py +327 -0
  60. matchlab/transformers/__init__.py +24 -0
  61. matchlab/transformers/base.py +41 -0
  62. matchlab/transformers/clean.py +48 -0
  63. matchlab/transformers/explode.py +67 -0
  64. matchlab/transformers/group.py +56 -0
  65. matchlab/transformers/select.py +31 -0
  66. matchlab/transformers/transform.py +101 -0
  67. matchlab-0.1.0.dev1.dist-info/METADATA +88 -0
  68. matchlab-0.1.0.dev1.dist-info/RECORD +72 -0
  69. matchlab-0.1.0.dev1.dist-info/WHEEL +5 -0
  70. matchlab-0.1.0.dev1.dist-info/entry_points.txt +2 -0
  71. matchlab-0.1.0.dev1.dist-info/licenses/LICENSE +21 -0
  72. matchlab-0.1.0.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,101 @@
1
+ """Transform, a plan node that reshapes one record step with a `Transformer`.
2
+
3
+ `Transform` is to `Transformer` what `Model` is to a `Deduper`/`Linker`, the lazy plan
4
+ node that wraps a serialisable methodology, folds its configuration into a cache key,
5
+ and runs it on collect. Its single input is a `RecordStep`, so transforms chain, each
6
+ its own cached artifact.
7
+ """
8
+
9
+ from typing import ClassVar
10
+
11
+ import polars as pl
12
+
13
+ from matchlab.adapters import Adapter, Fingerprint
14
+ from matchlab.core.kinds import StepKind
15
+ from matchlab.recordstep import IdentifierRead, RecordStep
16
+ from matchlab.specs import TransformSpec
17
+ from matchlab.transformers.base import Transformer
18
+ from matchlab.transformers.clean import Clean
19
+ from matchlab.transformers.explode import Explode
20
+ from matchlab.transformers.group import Group
21
+ from matchlab.transformers.select import Select
22
+
23
+ _TRANSFORMER_CLASSES: dict[str, type[Transformer]] = {}
24
+
25
+
26
+ def add_transformer_class(transformer_class: type[Transformer]) -> None:
27
+ """Register a custom transformer so it can be named in a plan (and a document)."""
28
+ if not issubclass(transformer_class, Transformer):
29
+ raise ValueError("The argument is not a subclass of Transformer.")
30
+ _TRANSFORMER_CLASSES[transformer_class.__name__] = transformer_class
31
+
32
+
33
+ for _builtin in (Select, Clean, Group, Explode):
34
+ add_transformer_class(_builtin)
35
+
36
+
37
+ class Transform(RecordStep):
38
+ """A record step reshaped by one transformer."""
39
+
40
+ kind: ClassVar[StepKind] = StepKind.TRANSFORM
41
+
42
+ def __init__(
43
+ self,
44
+ upstream: RecordStep,
45
+ transformer: Transformer | type[Transformer] | str,
46
+ transformer_settings: dict | None = None,
47
+ ) -> None:
48
+ """Define a transform.
49
+
50
+ Args:
51
+ upstream: The record step to reshape.
52
+ transformer: A `Transformer` instance, or a subclass / its registered name
53
+ to build from `transformer_settings`.
54
+ transformer_settings: The configuration dict, when `transformer` is a class
55
+ or a name. Ignored when it is already an instance.
56
+ """
57
+ if isinstance(transformer, Transformer):
58
+ self.transformer = transformer
59
+ else:
60
+ transformer_class = (
61
+ _TRANSFORMER_CLASSES[transformer]
62
+ if isinstance(transformer, str)
63
+ else transformer
64
+ )
65
+ self.transformer = transformer_class(**(transformer_settings or {}))
66
+
67
+ self.transformer_class = type(self.transformer)
68
+ self._input = upstream
69
+ super().__init__(upstream=(upstream,))
70
+
71
+ # -- Step contract ----------------------------------------------------------------
72
+
73
+ def __str__(self) -> str:
74
+ """A transform is drawn with the transformer implementing it."""
75
+ return f"{self.kind}({self.transformer_class.__name__})"
76
+
77
+ @property
78
+ def spec(self) -> TransformSpec:
79
+ """The serialisable spec for this transform."""
80
+ return TransformSpec(
81
+ transformer_class=self.transformer_class.__name__,
82
+ transformer_settings=self.transformer.model_dump(mode="json"),
83
+ )
84
+
85
+ def _execute(self, adapter: Adapter, fp: Fingerprint) -> None:
86
+ reshaped = self.transformer.apply(self._input._read_cache(adapter))
87
+ adapter.store_transform(fp, reshaped)
88
+
89
+ # -- RecordStep contract ------------------------------------------------------
90
+
91
+ def _read_cache(self, adapter: Adapter) -> pl.DataFrame:
92
+ if self._fp is None: # collect orders upstream first
93
+ raise RuntimeError(
94
+ "This transform has not been collected. Call collect() first."
95
+ )
96
+ return adapter.read_transform(self._fp)
97
+
98
+ @property
99
+ def _identifier_reads(self) -> tuple[IdentifierRead, ...]:
100
+ """A transform reads the same records as its input, so the reads delegate up."""
101
+ return self._input._identifier_reads
@@ -0,0 +1,88 @@
1
+ Metadata-Version: 2.4
2
+ Name: matchlab
3
+ Version: 0.1.0.dev1
4
+ Summary: A local-first library for building, running and evaluating entity resolution pipelines.
5
+ Author: Department for Business and Trade
6
+ Project-URL: Documentation, https://uktrade.github.io/matchlab/
7
+ Project-URL: Repository, https://github.com/uktrade/matchlab.git
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: Intended Audience :: Developers
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
16
+ Requires-Python: <3.14,>=3.11
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Requires-Dist: adbc-driver-manager>=1.9.0
20
+ Requires-Dist: duckdb>=1.1.1
21
+ Requires-Dist: faker>=36.1.1
22
+ Requires-Dist: frozendict>=2.4.6
23
+ Requires-Dist: pandas>=2.3.3
24
+ Requires-Dist: platformdirs>=4.5.0
25
+ Requires-Dist: polars-hash>=0.5.3
26
+ Requires-Dist: polars>=1.32.3
27
+ Requires-Dist: pyarrow>=17.0.0
28
+ Requires-Dist: pydantic>=2.9.2
29
+ Requires-Dist: rich>=13.9.4
30
+ Requires-Dist: splink<4.1.0,>=4.0.5
31
+ Requires-Dist: sqlalchemy>=2.0.35
32
+ Requires-Dist: sqlglot[c]>=30.0.3
33
+ Requires-Dist: textual>=0.80
34
+ Dynamic: license-file
35
+
36
+ # matchlab
37
+
38
+ **A local-first library for building, running and evaluating entity resolution pipelines.**
39
+
40
+ Record matching is a chore. matchlab makes it a pipeline you can build, run, query and measure — on your machine, against your warehouse, with nothing to deploy.
41
+
42
+ ```python
43
+ from matchlab import Source
44
+ from matchlab.models.dedupers import NaiveDeduper
45
+
46
+ companies = Source(
47
+ location=warehouse,
48
+ name="crn",
49
+ extract_transform="select pk, company, town from companies",
50
+ key_field="pk",
51
+ )
52
+
53
+ entities = (
54
+ companies.clean({"name": "lower(crn_company)"})
55
+ .dedupe(model_class=NaiveDeduper, model_settings={"unique_fields": ["name"]})
56
+ .resolve()
57
+ .collect()
58
+ )
59
+
60
+ entities.lookup_key(from_source="crn", to_sources=["dh"], key="a1")
61
+ ```
62
+
63
+ Read the [full documentation](https://uktrade.github.io/matchlab/).
64
+
65
+ ## What it does
66
+
67
+ * **A lazy plan.** `Source(...).dedupe(...).resolve()` builds a tree of steps. Nothing runs until you `collect()`.
68
+ * **Content-addressed caching.** Re-collecting an unchanged plan does no work. Adding a step runs only that step.
69
+ * **Materialised resolver output.** A collected resolver writes a complete `(root, leaf, key, source)` table, so lookups are reads, not re-derivations.
70
+ * **Measurement as a first-class job.** Sample clusters, record judgements, score precision and recall, and compare methodologies on equal terms.
71
+
72
+ ## What it doesn't do
73
+
74
+ No server, no accounts, no permissions, nothing to deploy. If you need a shared, governed matching service, matchlab is not that.
75
+
76
+ ## Installation
77
+
78
+ ```shell
79
+ pip install matchlab
80
+ ```
81
+
82
+ ## Coming from Matchbox?
83
+
84
+ matchlab is the successor to `matchbox-db`, with the server removed and the client API rebuilt. It's a hard break — see the [migration guide](https://uktrade.github.io/matchlab/migration/matchbox-to-matchlab/).
85
+
86
+ ## Development
87
+
88
+ See our full development guide and coding standards on our [contribution guide](https://uktrade.github.io/matchlab/contributing/).
@@ -0,0 +1,72 @@
1
+ matchlab/__init__.py,sha256=GoBEpiGNfnxOxA3KXMWHaqZVjh4HnrATZayGv-wQeS4,1200
2
+ matchlab/cli.py,sha256=Ka-clRCiPnxI5jmehjGCgdNyADcPFcnl3cuPI1rynYg,6209
3
+ matchlab/document.py,sha256=6iWbZsiX9dm2TFHTTtu4WpKf8tjglrY4dKZcqB39Di0,13419
4
+ matchlab/lineage.py,sha256=qJWPGpXVnuH8xQBZ-0L6PZjKg6b74xX9j6koY-N4Org,8870
5
+ matchlab/locations.py,sha256=6zvXWBXaQFBbOhicsILqW0cLoY17e5Yow3xIBEtfwtw,13435
6
+ matchlab/progress.py,sha256=t2PAj0YG2vAHguMFn9J0NL07R2K6wEnAkGELQpFNYNM,21330
7
+ matchlab/recordstep.py,sha256=n2aEpePo4GqWxo2r37N6pJbF-DLGNQpqFHEoxFc-2-I,7380
8
+ matchlab/sources.py,sha256=TdS4c2crd-pqeXATYE80TyG5B1oWgh0PzkSt_2fdpkE,12414
9
+ matchlab/specs.py,sha256=zIWNIZ0YsWqAFh-19JFCYLQSgZwuM6vGoaJgEC5z5uM,4638
10
+ matchlab/steps.py,sha256=91y9Klh25vyxdRtWP4bj-4fXSmX41tLX4sCqChUgSAg,12932
11
+ matchlab/adapters/__init__.py,sha256=QN8tGB0LJtLtxtEaq1aFZD_XHAQiR_e2CUPGoVJj4gg,554
12
+ matchlab/adapters/base.py,sha256=HEIrave447Rm26ZzZicPHgjeyJRJ9wa7DzhedryzANI,18738
13
+ matchlab/adapters/duckdb.py,sha256=tsKlQo_nAJCU4ySHjhnbwL-MmbLVRIG1EPV9E_D-EiQ,28663
14
+ matchlab/core/__init__.py,sha256=ftFTAWUmTIzJh01EkAvmJ8V4p2L33itVQ6t_JGxWzZg,68
15
+ matchlab/core/dataframes.py,sha256=8dSUyjVbC09YFFMnjOdHXqROJtt5SXSYOONxxIl88gY,8018
16
+ matchlab/core/dsu.py,sha256=nhOdQsVZw8AltxH0bBPaCp2YJQ7Y8h2zS7wDle9moVk,1946
17
+ matchlab/core/exceptions.py,sha256=qHnc2s6bFPvUQBmJ9oOk02-9zx-fLUPmGVUG7rblZx0,1305
18
+ matchlab/core/hash.py,sha256=E4jjov9asj-EGJxNTcsJP-J5h6Ju8pLLTQsmjBJqp2w,4714
19
+ matchlab/core/kinds.py,sha256=LKzyFY1bTCIiQRTV0LiALeVWcPp4Ol1h2TvaSN_mV5Y,255
20
+ matchlab/core/logging.py,sha256=LbdeEhyESwTTikD5pgZBKRInE0XRMSylmAZIv6VMOOk,8640
21
+ matchlab/core/resolver_output.py,sha256=chm1CUJP09gZlszLdKHG0_V9WfQu5_DoumLeT85UmU8,7899
22
+ matchlab/core/schemas.py,sha256=I5YjvMU8IsFLURM1AsLOxB7UjoYvVDlno2MQqNKnAZk,2638
23
+ matchlab/core/sql.py,sha256=swcsYb9vgdxIUbwBeMOIY4j1epvRWkr3qjuKbJbP3HA,432
24
+ matchlab/eval/__init__.py,sha256=Lb-Lm1FwHMD-0yB-WONdX58y0G4dDlo-Zz93OQMhTlo,233
25
+ matchlab/eval/judgements.py,sha256=IZgWXH2-ltzlyfbzB8-COgKzNRg851c63Z7oKA4Ce7A,1450
26
+ matchlab/eval/metrics.py,sha256=yQHpjzSBFLsMwMAPDiWNcUrPZx-OkTindY5Mb5BjInk,9747
27
+ matchlab/eval/samples.py,sha256=ZXBvUJzLEG6KV_wTl4Q5BvliuJC4T1LOYwKM1zsUooA,16546
28
+ matchlab/eval/tui/__init__.py,sha256=T77U0fWsMlAQnsUEVah0-tonP1M-4aW92fGExoJgFWg,2528
29
+ matchlab/eval/tui/app.py,sha256=CFw43oO_kct0fDwl8iVKupFEOgPxCWqy6-3pPlJX0sg,15033
30
+ matchlab/eval/tui/modals.py,sha256=WwfH82-_vTtS0G_xCjhig5f1N7BMMr35-WMA0GQPKBk,2692
31
+ matchlab/eval/tui/styles.tcss,sha256=ioZ8trLoH0roS5LZDrXeo3IIzh4sA9Pi8hrEDF0dpBs,3220
32
+ matchlab/eval/tui/widgets/__init__.py,sha256=AJsdcJTQy0LIOegAxSRFjCR-DW5jCodClYdPRKfXJsc,56
33
+ matchlab/eval/tui/widgets/assignment.py,sha256=djYry92TIjSwOxPERyeBdM9imcu1ngd8zrDwHQ2TNQk,2316
34
+ matchlab/eval/tui/widgets/styling.py,sha256=va69ALl74yjdgG4Wf7y0iEd5apJmIUoLf27WOv74K74,2346
35
+ matchlab/eval/tui/widgets/table.py,sha256=gXXDXDgwFXpXrCv1Grw-gryT-RSl441tsTpr-GT3M6I,11043
36
+ matchlab/models/__init__.py,sha256=cOfN9Um4r7XuBkC8dfTPRYefmQruxGm6kw6R9SJWvgw,225
37
+ matchlab/models/comparison.py,sha256=ahx5CjsK-NNLdWiHW6JIqyXJLlQ1MQZ9-_XnrlneehI,2447
38
+ matchlab/models/models.py,sha256=DgiGsqP9ps3c3mexZUzw5T9YwzNg-GusHMIoxGlzvT0,7588
39
+ matchlab/models/dedupers/__init__.py,sha256=J7JZSAnA8C-qyarathk369unktvpBa2njJ2POslVx0k,121
40
+ matchlab/models/dedupers/base.py,sha256=z_7AS-UfsiQXa_e14R-a_mD1aSeZrTtg4jgYpmDnBA0,1246
41
+ matchlab/models/dedupers/naive.py,sha256=aHlSvwY6fbLjH4adEFFVbyVui3azm6gRKQ5amb-fibE,2432
42
+ matchlab/models/linkers/__init__.py,sha256=qbj6hITLFBuONO0_fn3yPknTUAWjzWcFSQzIyi75Rmc,339
43
+ matchlab/models/linkers/base.py,sha256=Xt7ninASXRsuE46FuGqp1kterqG8X9E7BhcifSqnlPM,1366
44
+ matchlab/models/linkers/deterministic.py,sha256=yX1kEdxMmu8HYZst2eHLx-JqOQYM0sFedJLapwI7U0w,8956
45
+ matchlab/models/linkers/splinklinker.py,sha256=jVMClcKBKSXNkLSrJQMF33J4CxdbwXPNilYIAk4vD3E,9949
46
+ matchlab/models/linkers/weighteddeterministic.py,sha256=m8j1oGTbuOuvuALiZlt20dXhMKs8YfisXpekegaqr5A,4584
47
+ matchlab/resolvers/__init__.py,sha256=G1lbpQK_NrKabHBR7rd25ou1KKLQqjmHAcAz8LaXUiY,256
48
+ matchlab/resolvers/base.py,sha256=fYq8_tAeprq_dFVclhIGhb4DQWW8Rq_DRFB-CAg-qWc,2056
49
+ matchlab/resolvers/components.py,sha256=h0EuH6jdQaplXhdWnWk3sk2-QtcKH4y_hH5EG1LxL98,2033
50
+ matchlab/resolvers/resolvers.py,sha256=dmbaqsTfU6-qNksMDj_6QwuApN68f3VT26yluzawhPI,17605
51
+ matchlab/testkit/__init__.py,sha256=7e3ynGwMoPDc8gIILa2FROyzxVMqXJa5bBt7qIQ1vos,4411
52
+ matchlab/testkit/_generate.py,sha256=-ZeCrDJVWqmRRKDVmAtAm-xR61q1fblANDCRNM89vHk,8408
53
+ matchlab/testkit/compare.py,sha256=y_6wYqTNemikn7SMG1BCovgm6_l3hvl5vu5d2dZzj5E,6513
54
+ matchlab/testkit/entities.py,sha256=ho1VtZZIimaIIDFgH9kj3HHjRFluurgR9WMRB74yssI,10188
55
+ matchlab/testkit/features.py,sha256=m-X0ItSwlyvX48hQAI8kQNmMA50s__wGnWsw2kqhLV8,4692
56
+ matchlab/testkit/linked.py,sha256=pc1_LmfrL7Jku5yoOXWlnDosNYCQok4KBqDoN7l1nwU,19882
57
+ matchlab/testkit/matchers.py,sha256=JgobKrMVW8wK-INxH4qBzWqoqJootnpC5SZDQ3lwJBU,9773
58
+ matchlab/testkit/models.py,sha256=Ng0kysLxTuiglqO2bnaXljT_OHOLDi7_DSgNe0Dg4a4,6532
59
+ matchlab/testkit/sources.py,sha256=fUL5SefxnOypHrOKiY7DoXDRIvn4tpd7QIF4nstZ9U8,11250
60
+ matchlab/transformers/__init__.py,sha256=zNTpHycQKe6TRsjRDe9_7rbJpjNiVmbPZreOKtB9dhg,771
61
+ matchlab/transformers/base.py,sha256=tW7oIzbAG4exXFcJ2iKKjTyc9t4AgfXF_TjxMR6SpkU,1483
62
+ matchlab/transformers/clean.py,sha256=NvCfl7-KOL-7Wr0-DpKB40tnXhnfrvQvavMitr9N8eE,1992
63
+ matchlab/transformers/explode.py,sha256=k8Q__0aFdntyGxkffu2KWSJmCVCu7ImvJtRt-32Q5j4,2934
64
+ matchlab/transformers/group.py,sha256=SZ5iR646uuiwHyZusTTGfYcFi0dJAmWyXpw9FnY2mxI,2319
65
+ matchlab/transformers/select.py,sha256=s3NmMgL5IS7ifeIhj5ABQJ46XULW6s_kQKASOq7G0sQ,1125
66
+ matchlab/transformers/transform.py,sha256=ws6yS1Z9EZ4fuPB2zOKcNJXSo2Ku-bkKoTh8QpP_bMY,3916
67
+ matchlab-0.1.0.dev1.dist-info/licenses/LICENSE,sha256=7fhYMrjTXiy7Sf-9lBtZHNc4IjHTcfqmGW0AcjFMJJo,1090
68
+ matchlab-0.1.0.dev1.dist-info/METADATA,sha256=hzYyHGefEbnebD4gstmQZZo2nT9stGl93RIwCwmFcF8,3304
69
+ matchlab-0.1.0.dev1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
70
+ matchlab-0.1.0.dev1.dist-info/entry_points.txt,sha256=giDdKIeNDxGNWrvoVuLA5mtQjwStupSwxDYmrRjM9Rs,47
71
+ matchlab-0.1.0.dev1.dist-info/top_level.txt,sha256=1ZVLg12gZgkOUbN5CfX-KmLTbHGRbZqFEZPqrfRVN-Y,9
72
+ matchlab-0.1.0.dev1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ matchlab = matchlab.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Department for Business and Trade
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 @@
1
+ matchlab