diff --git a/bin/make_cal.py b/bin/make_cal.py
deleted file mode 100755
index d6ab58fdece8071e6d6407ec43ff6d516b44b8d7..0000000000000000000000000000000000000000
--- a/bin/make_cal.py
+++ /dev/null
@@ -1,169 +0,0 @@
-#! /usr/bin/env python
-
-"""
-Usage:
-    python make_cal.py OBSERVATORY YEAR
-"""
-from __future__ import print_function
-
-from datetime import datetime
-import itertools
-import json
-import os
-import sys
-import urllib.request
-
-
-############################################################################
-# CONFIGURATION
-
-# format used to output files
-# "{OBSERVATORY}" and "{YEAR}" are replaced with argument values
-FILENAME_FORMAT = "./{OBSERVATORY}{YEAR}WebAbsMaster.cal"
-
-# url for observation web service
-SERVICE_URL = "https://geomag.usgs.gov/baselines/observation.json.php"
-
-############################################################################
-# DO NOT EDIT BELOW THIS LINE
-
-
-# parse observatory and year arguments
-if len(sys.argv) != 3:
-    cmd = sys.argv[0]
-    print("Usage:   {} OBSERVATORY YEAR".format(cmd), file=sys.stderr)
-    print("Example: {} BOU 2016".format(cmd), file=sys.stderr)
-    sys.exit(1)
-
-OBSERVATORY = sys.argv[1]
-YEAR = int(sys.argv[2])
-
-
-# request observations from service
-url = (
-    SERVICE_URL
-    + "?"
-    + "&".join(
-        [
-            "observatory=" + OBSERVATORY,
-            "starttime=" + str(YEAR) + "-01-01",
-            "endtime=" + str(YEAR + 1) + "-01-01",
-        ]
-    )
-)
-
-try:
-    print(f"Loading data from web service\n\t{url}", file=sys.stderr)
-    response = urllib.request.urlopen(
-        url,
-        # allow environment certificate bundle override
-        cafile=os.environ.get("SSL_CERT_FILE"),
-    )
-    data = response.read()
-    observations = json.loads(data)
-except Exception as e:
-    print(f"Error loading data ({e})", file=sys.stderr)
-    sys.exit(1)
-
-
-# extract all valid cal values
-cals = []
-for observation in observations["data"]:
-    for reading in observation["readings"]:
-        for channel in ["H", "D", "Z"]:
-            cal = reading[channel]
-            if (
-                not cal["absolute"]
-                or not cal["baseline"]
-                or not cal["end"]
-                or not cal["start"]
-                or not cal["valid"]
-            ):
-                # not a valid cal value
-                continue
-            # convert D values from degrees to minutes
-            multiplier = 60 if channel == "D" else 1
-            absolute = cal["absolute"] * multiplier
-            baseline = cal["baseline"] * multiplier
-            end = datetime.utcfromtimestamp(cal["end"])
-            start = datetime.utcfromtimestamp(cal["start"])
-            cals.append(
-                {
-                    "absolute": absolute,
-                    "baseline": baseline,
-                    "channel": channel,
-                    "end": end,
-                    "start": start,
-                }
-            )
-
-
-# format calfile
-CAL_HEADER_FORMAT = "--{date:%Y %m %d} ({channel})"
-CAL_LINE_FORMAT = "{start:%H%M}-{end:%H%M} c{baseline:9.1f}{absolute:9.1f}"
-
-calfile = []
-# output by date in order
-cals = sorted(cals, key=lambda c: c["start"])
-# group by date
-for date, cals in itertools.groupby(cals, key=lambda c: c["start"].date()):
-    # convert group to list so it can be reused
-    cals = list(cals)
-    # within each day, order by H, then D, then Z
-    for channel in ["H", "D", "Z"]:
-        channel_cals = [c for c in cals if c["channel"] == channel]
-        if not channel_cals:
-            # no matching values
-            continue
-        # add channel header
-        calfile.append(CAL_HEADER_FORMAT.format(channel=channel, date=date))
-        calfile.extend([CAL_LINE_FORMAT.format(**c) for c in channel_cals])
-calfile.append("")
-
-
-# write calfile
-filename = FILENAME_FORMAT.format(OBSERVATORY=OBSERVATORY, YEAR=YEAR)
-print("Writing cal file to {}".format(filename), file=sys.stderr)
-with open(filename, "wb", -1) as f:
-    f.write(os.linesep.join(calfile).encode())
-
-
-"""
-CAL format example:
-- ordered by date
-- within date, order by H, then D, then Z component
-- component values order by start time
-- D component values in minutes.
-
-
---2015 03 30 (H)
-2140-2143 c    175.0  12531.3
-2152-2156 c    174.9  12533.3
-2205-2210 c    174.8  12533.1
-2220-2223 c    174.9  12520.7
---2015 03 30 (D)
-2133-2137 c   1128.3   1118.5
-2145-2149 c   1128.4   1116.4
-2159-2203 c   1128.3   1113.1
-2212-2216 c   1128.4   1113.5
---2015 03 30 (Z)
-2140-2143 c    -52.9  55403.4
-2152-2156 c    -52.8  55403.8
-2205-2210 c    -52.8  55404.0
-2220-2223 c    -52.8  55410.5
---2015 07 27 (H)
-2146-2151 c    173.5  12542.5
-2204-2210 c    173.8  12542.5
-2225-2229 c    173.8  12547.2
-2240-2246 c    173.6  12538.7
---2015 07 27 (D)
-2137-2142 c   1127.8   1109.2
-2154-2158 c   1128.3   1106.3
-2213-2220 c   1128.0   1106.3
-2232-2237 c   1128.3   1104.7
---2015 07 27 (Z)
-2146-2151 c    -53.9  55382.7
-2204-2210 c    -54.0  55382.5
-2225-2229 c    -54.1  55383.7
-2240-2246 c    -54.1  55389.0
-"""
diff --git a/geomagio/processing/make_cal.py b/geomagio/processing/make_cal.py
new file mode 100755
index 0000000000000000000000000000000000000000..c0d48381ccab3f05b2bfee3ae7f3ffdd69f46412
--- /dev/null
+++ b/geomagio/processing/make_cal.py
@@ -0,0 +1,66 @@
+#! /usr/bin/env python
+
+"""
+Usage:
+    python make_cal.py OBSERVATORY YEAR
+"""
+from __future__ import print_function
+
+from obspy import UTCDateTime
+import typer
+
+from .magproc import write_cal_file
+
+
+def main():
+    typer.run(make_cal)
+
+
+def make_cal(observatory: str, year: int):
+    write_cal_file(
+        starttime=UTCDateTime(f"{year}-01-01"),
+        endtime=UTCDateTime(f"{year+1}-01-01"),
+        observatory=observatory,
+        template="file://./{OBSERVATORY}{YEAR}WebAbsMaster.cal",
+    )
+
+
+"""
+CAL format example:
+- ordered by date
+- within date, order by H, then D, then Z component
+- component values order by start time
+- D component values in minutes.
+
+
+--2015 03 30 (H)
+2140-2143 c    175.0  12531.3
+2152-2156 c    174.9  12533.3
+2205-2210 c    174.8  12533.1
+2220-2223 c    174.9  12520.7
+--2015 03 30 (D)
+2133-2137 c   1128.3   1118.5
+2145-2149 c   1128.4   1116.4
+2159-2203 c   1128.3   1113.1
+2212-2216 c   1128.4   1113.5
+--2015 03 30 (Z)
+2140-2143 c    -52.9  55403.4
+2152-2156 c    -52.8  55403.8
+2205-2210 c    -52.8  55404.0
+2220-2223 c    -52.8  55410.5
+--2015 07 27 (H)
+2146-2151 c    173.5  12542.5
+2204-2210 c    173.8  12542.5
+2225-2229 c    173.8  12547.2
+2240-2246 c    173.6  12538.7
+--2015 07 27 (D)
+2137-2142 c   1127.8   1109.2
+2154-2158 c   1128.3   1106.3
+2213-2220 c   1128.0   1106.3
+2232-2237 c   1128.3   1104.7
+--2015 07 27 (Z)
+2146-2151 c    -53.9  55382.7
+2204-2210 c    -54.0  55382.5
+2225-2229 c    -54.1  55383.7
+2240-2246 c    -54.1  55389.0
+"""
diff --git a/poetry.lock b/poetry.lock
index 99e3d0d1174f233d754ca808937322ff8d1828aa..6500b05d3802db5881852ebcc9c84d70efd7d5fc 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -228,7 +228,7 @@ six = "*"
 name = "data-science-types"
 version = "0.2.23"
 description = "Type stubs for Python machine learning libraries"
-category = "main"
+category = "dev"
 optional = false
 python-versions = ">=3.6"
 
@@ -477,7 +477,7 @@ python-dateutil = ">=2.7"
 name = "mypy"
 version = "0.910"
 description = "Optional static typing for Python"
-category = "main"
+category = "dev"
 optional = false
 python-versions = ">=3.5"
 
@@ -495,17 +495,17 @@ python2 = ["typed-ast (>=1.4.0,<1.5.0)"]
 name = "mypy-extensions"
 version = "0.4.3"
 description = "Experimental type system extensions for programs checked with the mypy typechecker."
-category = "main"
+category = "dev"
 optional = false
 python-versions = "*"
 
 [[package]]
 name = "numpy"
-version = "1.21.1"
+version = "1.21.2"
 description = "NumPy is the fundamental package for array computing with Python."
 category = "main"
 optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.7,<3.11"
 
 [[package]]
 name = "obspy"
@@ -545,7 +545,7 @@ et-xmlfile = "*"
 name = "openpyxl-stubs"
 version = "0.1.19"
 description = "Type stubs for openpyxl"
-category = "main"
+category = "dev"
 optional = false
 python-versions = "*"
 
@@ -826,7 +826,7 @@ pymysql = ["pymysql (<1)", "pymysql"]
 name = "sqlalchemy-stubs"
 version = "0.4"
 description = "SQLAlchemy stubs and mypy plugin"
-category = "main"
+category = "dev"
 optional = false
 python-versions = "*"
 
@@ -860,7 +860,7 @@ full = ["aiofiles", "graphene", "itsdangerous", "jinja2", "python-multipart", "p
 name = "toml"
 version = "0.10.2"
 description = "Python Library for Tom's Obvious, Minimal Language"
-category = "main"
+category = "dev"
 optional = false
 python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
 
@@ -876,7 +876,7 @@ python-versions = ">=3.6"
 name = "typed-ast"
 version = "1.4.3"
 description = "a fork of Python 2 and 3 ast modules with type comment support"
-category = "main"
+category = "dev"
 optional = false
 python-versions = "*"
 
@@ -901,7 +901,7 @@ doc = ["mkdocs (>=1.1.2,<2.0.0)", "mkdocs-material (>=5.4.0,<6.0.0)", "markdown-
 name = "types-python-dateutil"
 version = "0.1.6"
 description = "Typing stubs for python-dateutil"
-category = "main"
+category = "dev"
 optional = false
 python-versions = "*"
 
@@ -909,7 +909,7 @@ python-versions = "*"
 name = "types-requests"
 version = "2.25.6"
 description = "Typing stubs for requests"
-category = "main"
+category = "dev"
 optional = false
 python-versions = "*"
 
@@ -1002,7 +1002,7 @@ testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytes
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.7,<3.10"
-content-hash = "4780e7fcf0314de2c66305a113801662230761a7049ac6fdd5939c2925db489a"
+content-hash = "cb0b5ea4ad27b8dafe86ad166bda6bf4f1e1c55e18e04273577315dbe38038a3"
 
 [metadata.files]
 aiomysql = [
@@ -1172,8 +1172,10 @@ cryptography = [
     {file = "cryptography-3.4.7-cp36-abi3-win_amd64.whl", hash = "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca"},
     {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2010_x86_64.whl", hash = "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873"},
     {file = "cryptography-3.4.7-pp36-pypy36_pp73-manylinux2014_x86_64.whl", hash = "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d"},
+    {file = "cryptography-3.4.7-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586"},
     {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2010_x86_64.whl", hash = "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177"},
     {file = "cryptography-3.4.7-pp37-pypy37_pp73-manylinux2014_x86_64.whl", hash = "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9"},
+    {file = "cryptography-3.4.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3"},
     {file = "cryptography-3.4.7.tar.gz", hash = "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713"},
 ]
 cycler = [
@@ -1449,34 +1451,36 @@ mypy-extensions = [
     {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"},
 ]
 numpy = [
-    {file = "numpy-1.21.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:38e8648f9449a549a7dfe8d8755a5979b45b3538520d1e735637ef28e8c2dc50"},
-    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:fd7d7409fa643a91d0a05c7554dd68aa9c9bb16e186f6ccfe40d6e003156e33a"},
-    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a75b4498b1e93d8b700282dc8e655b8bd559c0904b3910b144646dbbbc03e062"},
-    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1412aa0aec3e00bc23fbb8664d76552b4efde98fb71f60737c83efbac24112f1"},
-    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:e46ceaff65609b5399163de5893d8f2a82d3c77d5e56d976c8b5fb01faa6b671"},
-    {file = "numpy-1.21.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:c6a2324085dd52f96498419ba95b5777e40b6bcbc20088fddb9e8cbb58885e8e"},
-    {file = "numpy-1.21.1-cp37-cp37m-win32.whl", hash = "sha256:73101b2a1fef16602696d133db402a7e7586654682244344b8329cdcbbb82172"},
-    {file = "numpy-1.21.1-cp37-cp37m-win_amd64.whl", hash = "sha256:7a708a79c9a9d26904d1cca8d383bf869edf6f8e7650d85dbc77b041e8c5a0f8"},
-    {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:95b995d0c413f5d0428b3f880e8fe1660ff9396dcd1f9eedbc311f37b5652e16"},
-    {file = "numpy-1.21.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:635e6bd31c9fb3d475c8f44a089569070d10a9ef18ed13738b03049280281267"},
-    {file = "numpy-1.21.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4a3d5fb89bfe21be2ef47c0614b9c9c707b7362386c9a3ff1feae63e0267ccb6"},
-    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a326af80e86d0e9ce92bcc1e65c8ff88297de4fa14ee936cb2293d414c9ec63"},
-    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:791492091744b0fe390a6ce85cc1bf5149968ac7d5f0477288f78c89b385d9af"},
-    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0318c465786c1f63ac05d7c4dbcecd4d2d7e13f0959b01b534ea1e92202235c5"},
-    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9a513bd9c1551894ee3d31369f9b07460ef223694098cf27d399513415855b68"},
-    {file = "numpy-1.21.1-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:91c6f5fc58df1e0a3cc0c3a717bb3308ff850abdaa6d2d802573ee2b11f674a8"},
-    {file = "numpy-1.21.1-cp38-cp38-win32.whl", hash = "sha256:978010b68e17150db8765355d1ccdd450f9fc916824e8c4e35ee620590e234cd"},
-    {file = "numpy-1.21.1-cp38-cp38-win_amd64.whl", hash = "sha256:9749a40a5b22333467f02fe11edc98f022133ee1bfa8ab99bda5e5437b831214"},
-    {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:d7a4aeac3b94af92a9373d6e77b37691b86411f9745190d2c351f410ab3a791f"},
-    {file = "numpy-1.21.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d9e7912a56108aba9b31df688a4c4f5cb0d9d3787386b87d504762b6754fbb1b"},
-    {file = "numpy-1.21.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:25b40b98ebdd272bc3020935427a4530b7d60dfbe1ab9381a39147834e985eac"},
-    {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8a92c5aea763d14ba9d6475803fc7904bda7decc2a0a68153f587ad82941fec1"},
-    {file = "numpy-1.21.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:05a0f648eb28bae4bcb204e6fd14603de2908de982e761a2fc78efe0f19e96e1"},
-    {file = "numpy-1.21.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f01f28075a92eede918b965e86e8f0ba7b7797a95aa8d35e1cc8821f5fc3ad6a"},
-    {file = "numpy-1.21.1-cp39-cp39-win32.whl", hash = "sha256:88c0b89ad1cc24a5efbb99ff9ab5db0f9a86e9cc50240177a571fbe9c2860ac2"},
-    {file = "numpy-1.21.1-cp39-cp39-win_amd64.whl", hash = "sha256:01721eefe70544d548425a07c80be8377096a54118070b8a62476866d5208e33"},
-    {file = "numpy-1.21.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2d4d1de6e6fb3d28781c73fbde702ac97f03d79e4ffd6598b880b2d95d62ead4"},
-    {file = "numpy-1.21.1.zip", hash = "sha256:dff4af63638afcc57a3dfb9e4b26d434a7a602d225b42d746ea7fe2edf1342fd"},
+    {file = "numpy-1.21.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:52a664323273c08f3b473548bf87c8145b7513afd63e4ebba8496ecd3853df13"},
+    {file = "numpy-1.21.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a7b9db0a2941434cd930dacaafe0fc9da8f3d6157f9d12f761bbde93f46218"},
+    {file = "numpy-1.21.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f2dc79c093f6c5113718d3d90c283f11463d77daa4e83aeeac088ec6a0bda52"},
+    {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:a55e4d81c4260386f71d22294795c87609164e22b28ba0d435850fbdf82fc0c5"},
+    {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:426a00b68b0d21f2deb2ace3c6d677e611ad5a612d2c76494e24a562a930c254"},
+    {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:298156f4d3d46815eaf0fcf0a03f9625fc7631692bd1ad851517ab93c3168fc6"},
+    {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:09858463db6dd9f78b2a1a05c93f3b33d4f65975771e90d2cf7aadb7c2f66edf"},
+    {file = "numpy-1.21.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:805459ad8baaf815883d0d6f86e45b3b0b67d823a8f3fa39b1ed9c45eaf5edf1"},
+    {file = "numpy-1.21.2-cp37-cp37m-win32.whl", hash = "sha256:f545c082eeb09ae678dd451a1b1dbf17babd8a0d7adea02897a76e639afca310"},
+    {file = "numpy-1.21.2-cp37-cp37m-win_amd64.whl", hash = "sha256:b160b9a99ecc6559d9e6d461b95c8eec21461b332f80267ad2c10394b9503496"},
+    {file = "numpy-1.21.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a5109345f5ce7ddb3840f5970de71c34a0ff7fceb133c9441283bb8250f532a3"},
+    {file = "numpy-1.21.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:209666ce9d4a817e8a4597cd475b71b4878a85fa4b8db41d79fdb4fdee01dde2"},
+    {file = "numpy-1.21.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:c01b59b33c7c3ba90744f2c695be571a3bd40ab2ba7f3d169ffa6db3cfba614f"},
+    {file = "numpy-1.21.2-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:e42029e184008a5fd3d819323345e25e2337b0ac7f5c135b7623308530209d57"},
+    {file = "numpy-1.21.2-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:7fdc7689daf3b845934d67cb221ba8d250fdca20ac0334fea32f7091b93f00d3"},
+    {file = "numpy-1.21.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:550564024dc5ceee9421a86fc0fb378aa9d222d4d0f858f6669eff7410c89bef"},
+    {file = "numpy-1.21.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:bf75d5825ef47aa51d669b03ce635ecb84d69311e05eccea083f31c7570c9931"},
+    {file = "numpy-1.21.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a9da45b748caad72ea4a4ed57e9cd382089f33c5ec330a804eb420a496fa760f"},
+    {file = "numpy-1.21.2-cp38-cp38-win32.whl", hash = "sha256:e167b9805de54367dcb2043519382be541117503ce99e3291cc9b41ca0a83557"},
+    {file = "numpy-1.21.2-cp38-cp38-win_amd64.whl", hash = "sha256:466e682264b14982012887e90346d33435c984b7fead7b85e634903795c8fdb0"},
+    {file = "numpy-1.21.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:dd0e3651d210068d13e18503d75aaa45656eef51ef0b261f891788589db2cc38"},
+    {file = "numpy-1.21.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:92a0ab128b07799dd5b9077a9af075a63467d03ebac6f8a93e6440abfea4120d"},
+    {file = "numpy-1.21.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fde50062d67d805bc96f1a9ecc0d37bfc2a8f02b937d2c50824d186aa91f2419"},
+    {file = "numpy-1.21.2-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:640c1ccfd56724f2955c237b6ccce2e5b8607c3bc1cc51d3933b8c48d1da3723"},
+    {file = "numpy-1.21.2-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5de64950137f3a50b76ce93556db392e8f1f954c2d8207f78a92d1f79aa9f737"},
+    {file = "numpy-1.21.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b342064e647d099ca765f19672696ad50c953cac95b566af1492fd142283580f"},
+    {file = "numpy-1.21.2-cp39-cp39-win32.whl", hash = "sha256:30fc68307c0155d2a75ad19844224be0f2c6f06572d958db4e2053f816b859ad"},
+    {file = "numpy-1.21.2-cp39-cp39-win_amd64.whl", hash = "sha256:b5e8590b9245803c849e09bae070a8e1ff444f45e3f0bed558dd722119eea724"},
+    {file = "numpy-1.21.2-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d96a6a7d74af56feb11e9a443150216578ea07b7450f7c05df40eec90af7f4a7"},
+    {file = "numpy-1.21.2.zip", hash = "sha256:423216d8afc5923b15df86037c6053bf030d15cc9e3224206ef868c2d63dd6dc"},
 ]
 obspy = [
     {file = "obspy-1.2.2-cp27-cp27m-win32.whl", hash = "sha256:86e8e891a10258b1f37aa31122c69f62ad22f8d0a86a19a4da28c9efe686f541"},
diff --git a/pyproject.toml b/pyproject.toml
index 21c3f8c693b8343a6f6c6bcd0d2ae044852764c6..fabac221aa34ab63097eea0ebd536af9fb9e9e23 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -63,4 +63,5 @@ generate-matrix = "geomagio.processing.affine_matrix:main"
 geomag-metadata = "geomagio.metadata.main:main"
 geomag-py = "geomagio.Controller:main"
 magproc-prepfiles = "geomagio.processing.magproc:main"
+make-cal = "geomagio.processing.make_cal:main"
 obsrio-filter = "geomagio.processing.obsrio:main"