Repository: PSLmodels/Tax-Calculator

Issue:
Help needed with error after including indexed parameters in the baseline
I'm trying to model some CTC provisions relative to TCJA permanence (relying on the ext.json changes), and I'm getting an error when trying to index the ACTC values in my baseline.

Specifically, it's these lines of code in my baseline that are problematic:

_"ACTC_c": {"2026": 1776.67},
"ACTC_c-indexed": {"2026": True},
"ACTC_c": {"2032": 2000.00},
"ACTC_c-indexed": {"2032": False},_

The error I'm receiving is:

_TypeError: '<' not supported between instances of 'int' and 'str'_

When I remove the specifications to index the ACTC, the error goes away.

**Any suggestions on how to solve this? Any help would be greatly appreciated!**

(Also, as this is my first Github post, please let me know if I'm not posting this in the correct format).



Here is the full specification for the baseline and reform:

_baseline = {}

tcja_baseline = {

    "II_rt1": {"2026": 0.10},
    "II_brk1": {"2026": [12250.0, 24500.0, 12250.0, 17500.0, 24500.0]},
    "PT_rt1": {"2026": 0.10},
    "PT_brk1": {"2026": [12250.0, 24500.0, 12250.0, 17500.0, 24500.0]},
    "II_rt2": {"2026": 0.12},
    "II_brk2": {"2026": [49750.0, 99500.0, 49750.0, 66600.0, 99500.0]},
    "PT_rt2": {"2026": 0.12},
    "PT_brk2": {"2026": [49750.0, 99500.0, 49750.0, 66600.0, 99500.0]},
    "II_rt3": {"2026": 0.22},
    "II_brk3": {"2026": [106100.0, 212200.0, 106100.0, 106100.0, 212200.0]},
    "PT_rt3": {"2026": 0.22},
    "PT_brk3": {"2026": [106100.0, 212200.0, 106100.0, 106100.0, 212200.0]},
    "II_rt4": {"2026": 0.24},
    "II_brk4": {"2026": [202550.0, 405050.0, 202550.0, 202550.0, 405050.0]},
    "PT_rt4": {"2026": 0.24},
    "PT_brk4": {"2026": [202550.0, 405050.0, 202550.0, 202550.0, 405050.0]},
    "II_rt5": {"2026": 0.32},
    "II_brk5": {"2026": [257200.0, 514400.0, 257200.0, 257200.0, 514400.0]},
    "PT_rt5": {"2026": 0.32},
    "PT_brk5": {"2026": [257200.0, 514400.0, 257200.0, 257200.0, 514400.0]},
    "II_rt6": {"2026": 0.35},
    "II_brk6": {"2026": [642950.0, 771550.0, 385760.0, 642950.0, 771550.0]},
    "PT_rt6": {"2026": 0.35},
    "PT_brk6": {"2026": [642950.0, 771550.0, 385760.0, 642950.0, 771550.0]},
    "II_rt7": {"2026": 0.37},
    "II_brk7": {"2026": [9e+99, 9e+99, 9e+99, 9e+99, 9e+99]},
    "PT_rt7": {"2026": 0.37},
    "PT_brk7": {"2026": [9e+99, 9e+99, 9e+99, 9e+99, 9e+99]},
    "CTC_c": {"2026": 2000.00},
    "ACTC_c": {"2026": 1776.67},
    "ACTC_c-indexed": {"2026": True},
    "ACTC_c": {"2032": 2000.00},
    "ACTC_c-indexed": {"2032": False},
    "ODC_c": {"2026": 500.00},
    "CTC_ps": {"2026": [200000.0, 400000.0, 200000.0, 200000.0, 400000.0]},
    "ACTC_Income_thd": {"2026": 2500.00},
    "AMT_em": {"2026": [90400.0, 140700.0, 70300.0, 90400.0, 140700.0]},
    "AMT_em_ps": {"2026": [642950.0, 1285950.0, 642950.0, 642950.0, 1285950.0]},
    "AMT_em_pe": {"2026": 940953.35},
    "STD": {"2026": [15450.0, 30850.0, 15450.0, 23150.0, 30850.0]},
    "ID_AllTaxes_c": {"2026": [10000.0, 10000.0, 5000.0, 10000.0, 10000.0]},
    "ID_Charity_crt_cash": {"2026": 0.60},
    "ID_Casualty_hc": {"2026": 1.00},
    "ID_Miscellaneous_hc": {"2026": 1.00},
    "ID_ps": {"2026": [9e+99, 9e+99, 9e+99, 9e+99, 9e+99]},
    "ID_prt": {"2026": 0.00},
    "ID_crt": {"2026": 1.00},
    "II_em": {"2026": 0.00},
    "II_em_ps": {"2026": [9e+99, 9e+99, 9e+99, 9e+99, 9e+99]},
    "PT_qbid_rt": {"2026": 0.20},
    "PT_qbid_taxinc_thd": {"2026": [202550.0, 405050.0, 202550.0, 405050.0, 202550.0]},
    "PT_qbid_taxinc_gap": {"2026": [50000.0, 100000.0, 50000.0, 50000.0, 100000.0]},
    "PT_qbid_w2_wages_rt": {"2026": 0.50},
    "PT_qbid_alt_w2_wages_rt": {"2026": 0.25},
    "PT_qbid_alt_property_rt": {"2026": 0.025},
    "ALD_BusinessLosses_c": {"2029": [342904.94, 685809.88, 342904.94, 342904.94, 685809.88]}
}

allreforms = {
    
    "CTC_c": {"2025": 2500.00,
              "2026": 2500.00,
              "2027": 2500.00,
              "2028": 2500.00,
              "2029": 2000.00},
    "CTC_c-indexed": {"2029": True},
    "ACTC_c": {"2026": 1776.67},
    "ACTC_c-indexed": {"2026": True}    

}_


And here is the full error:

---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Cell In[32], line 1
----> 1 run_calc(
      2     # Define Year Range
      3     yr_range = range(2025, 2035), # 
      4     # Create Reform Lists
      5     refs_policy = [tcja_baseline, reform1, reform2], # reform
      6     refs_base = [baseline, tcja_baseline, reform1, reform2], # baseline
      7     # Set Records
      8     rec = recs_tmd,
      9     # Create Index Labels
     10     df = pd.DataFrame(index=["TCJA", "CTC Changes", "ACTC Changes"])
     11 )

Cell In[28], line 11, in run_calc(yr_range, refs_policy, refs_base, rec, df)
      9 calc_base = Calculator(pol_base, rec)
     10 calc_base.advance_to_year(year)
---> 11 pol_ref.implement_reform(ref_policy)
     12 calc_ref=Calculator(pol_ref, rec)
     13 calc_ref.advance_to_year(year)

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/taxcalc/policy.py:150, in Policy.implement_reform(self, reform, print_warnings, raise_errors)
    145 """
    146 Implement reform using Tax-Calculator syled reforms/adjustments. Users
    147 may also use the adjust method with ParamTools styled reforms.
    148 """
    149 # need to do conversion:
--> 150 return self._update(reform, print_warnings, raise_errors)

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/taxcalc/parameters.py:679, in Parameters._update(self, revision, print_warnings, raise_errors)
    671         msg = (
    672             f"{param} must be a year:value dictionary "
    673             f"if you are not using the new adjust method."
    674         )
    675         raise pt.ValidationError(
    676             {"errors": {"schema": msg}},
    677             None
    678         )
--> 679 return self.adjust(
    680     new_params,
    681     print_warnings=print_warnings,
    682     raise_errors=raise_errors
    683 )

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/taxcalc/parameters.py:183, in Parameters.adjust(self, params_or_path, print_warnings, raise_errors, **kwargs)
    174 try:
    175     # Wrap all updates in adjust_with_indexing in a transaction and
    176     # defer related-parameter validation until all intermediate updates
    177     # are complete.
    178     with self.transaction(
    179         defer_validation=True,
    180         raise_errors=True,
    181         ignore_warnings=kwargs["ignore_warnings"],
    182     ):
--> 183         return self.adjust_with_indexing(
    184             params_or_path, raise_errors=True, **kwargs
    185         )
    186 except pt.ValidationError as ve:
    187     if self.errors and raise_errors:

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/taxcalc/parameters.py:425, in Parameters.adjust_with_indexing(self, params_or_path, **kwargs)
    423 if base_param in params:
    424     min_index_change_year = min(indexed_changes.keys())
--> 425     vos = self.sel[params[base_param]]["year"].lt(
    426         min_index_change_year, strict=False
    427     )
    429     if list(vos):
    430         min_adj_year = min(vos, key=lambda vo: vo["year"])[
    431             "year"
    432         ]

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/paramtools/values.py:84, in ValueBase.lt(self, value, strict)
     83 def lt(self, value, strict=True):
---> 84     return self.cmp_attr.lt(strict, **{self.label: value})

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/paramtools/values.py:361, in Values.lt(self, strict, **labels)
    350 def lt(self, strict=True, **labels):
    351     """
    352     Returns values that have label values less than the label value:
    353 
   (...)    358 
    359     """
--> 361     return self._cmp("lt", strict, **labels)

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/paramtools/values.py:282, in Values._cmp(self, op, strict, **labels)
    279 elif skl is None and not strict:
    280     return QueryResult(self, list(self.index))
--> 282 skl_result = getattr(self.skls[label], op)(value)
    283 if not strict:
    284     match_index = skl_result.index if skl_result else []

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/paramtools/sorted_key_list.py:84, in SortedKeyList.lt(self, value)
     81 def lt(self, value):
     82     key = self.keyfunc(value)
     83     irange = list(
---> 84         self.sorted_key_list_2.irange_key(
     85             max_key=key, inclusive=(True, False)
     86         )
     87     )
     88     if irange:
     89         return SortedKeyListResult(irange)

File ~/anaconda3/envs/fga-taxcalc-dev/lib/python3.12/site-packages/sortedcontainers/sortedlist.py:2223, in SortedKeyList.irange_key(self, min_key, max_key, inclusive, reverse)
   2221         max_idx = bisect_right(_keys[max_pos], max_key)
   2222 else:
-> 2223     max_pos = bisect_left(_maxes, max_key)
   2225     if max_pos == len(_maxes):
   2226         max_pos -= 1

TypeError: '<' not supported between instances of 'int' and 'str'

Hints from issue comments:
@martinholmer: @hdublois,  Sorry you're having difficulty using Tax-Calculator.

We have no way to help you without seeing the Python code that generates the error.
Please show the full Python script and the error traceback.

Also, please describe in words the CTC-related revision (to the basic TCJA extension) you want to analyze.@hdublois: @martinholmer Apologies! I've attached screenshots of the Jupyter notebook script below, including the error traceback.

All I'm attempting to do is change the CTC values to a fixed value for 2025 - 2029, and then index them to inflation. However, the error appears to be associated with the baseline rather than the reform. When I remove the indexed ACTC parameter from the TCJA permanence baseline, the error goes away--but I want to preserve that change, not get rid of it.



<img width="449" alt="Image" src="https://github.com/user-attachments/assets/a4838bb1-1d6a-42d6-b3b8-1c65e86fce3f" />

<img width="486" alt="Image" src="https://github.com/user-attachments/assets/01fb91a6-d261-4e57-b44a-461d73232aa5" />

<img width="444" alt="Image" src="https://github.com/user-attachments/assets/f0b140cb-6651-4ede-9728-1744d4e57508" />


<img width="337" alt="Image" src="https://github.com/user-attachments/assets/c8db9fbe-f916-4218-b0bc-5b70a3e182ed" />


<img width="688" alt="Image" src="https://github.com/user-attachments/assets/b3443ba8-5e11-4c92-b8ab-9e2b439f8319" />

<img width="761" alt="Image" src="https://github.com/user-attachments/assets/db26b890-eb02-45df-93f9-bbbcda9355d1" />

<img width="706" alt="Image" src="https://github.com/user-attachments/assets/fcd37b88-31ed-44fb-a21e-55c4ad4c2902" />

<img width="759" alt="Image" src="https://github.com/user-attachments/assets/aca1ade1-4244-473d-b715-dafd69f06a4d" />@martinholmer: @hdublois, Thanks for the additional detail, which was very helpful.

I'm confused by your specification of the Reform Lists:

<img width="575" alt="Image" src="https://github.com/user-attachments/assets/395c7287-b927-471d-bc41-27b463ebbf63" />

The **reform** specification in `refs_policy` makes sense, but `refs_base` is confusing.   First, a trivial point: the `baseline` (which you have set to `{}`) is redundant.  But more confusing is the third item in the list: `allreforms`.  Adding that third item makes the baseline policy and the reform policy identical.  Is that really what you want to analyze?

@martinholmer: @hdublois,  Your description is:
```
All I'm attempting to do is change the CTC values to a fixed value for 2025-2029,
and then index them to inflation.  However, the error appears to be associated 
with the baseline rather than the reform.  When I remove the indexed ACTC 
parameter from the TCJA permanence baseline, the error goes away -- but I want
to preserve that change, not get rid of it.
```

I've changed, in pending PR #2904, how Tax-Calculator handles the interaction between the `ACTC_c` and `CTC_c` parameters, and therefore, also changed the specification of the `ext.json` reform.

Using the changes in PR #2904, I've analyzed your reform (as I understand it) using the Tax-Calculator CLI tool, `tc`:
```
% cat ctc.json 
{
    "CTC_c": {
        "2026": 2500.00,
        "2027": 2500.00,
        "2028": 2500.00,
        "2029": 2000.00
    },
    "CTC_c-indexed": {"2029": true}
}

% tc ../tmd.csv 2026 --numyears 10 --baseline ext.json --reform ext.json+ctc.json --exact --params --tables
Read input data for 2021; input data were extrapolated to 2026
Write baseline policy parameter values to file tmd-26-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-26-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-26-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2027
Write baseline policy parameter values to file tmd-27-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-27-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-27-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2028
Write baseline policy parameter values to file tmd-28-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-28-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-28-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2029
Write baseline policy parameter values to file tmd-29-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-29-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-29-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2030
Write baseline policy parameter values to file tmd-30-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-30-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-30-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2031
Write baseline policy parameter values to file tmd-31-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-31-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-31-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2032
Write baseline policy parameter values to file tmd-32-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-32-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-32-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2033
Write baseline policy parameter values to file tmd-33-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-33-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-33-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2034
Write baseline policy parameter values to file tmd-34-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-34-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-34-ext-ext+ctc-#-tables.text
Advance input data and  policy to 2035
Write baseline policy parameter values to file tmd-35-ext-ext+ctc-#-params.bas
Write reform policy parameter values to file tmd-35-ext-ext+ctc-#-params.ref
Write tabular output to file tmd-35-ext-ext+ctc-#-tables.text
Execution time is 59.5 seconds

% grep CTC_c tmd-??-ext-ext+ctc-#-params.bas | grep -v under6
tmd-26-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-26-ext-ext+ctc-#-params.bas:ACTC_c 1776.67
tmd-27-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-27-ext-ext+ctc-#-params.bas:ACTC_c 1818.24
tmd-28-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-28-ext-ext+ctc-#-params.bas:ACTC_c 1858.06
tmd-29-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-29-ext-ext+ctc-#-params.bas:ACTC_c 1898.57
tmd-30-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-30-ext-ext+ctc-#-params.bas:ACTC_c 1940.53
tmd-31-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-31-ext-ext+ctc-#-params.bas:ACTC_c 1983.8
tmd-32-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-32-ext-ext+ctc-#-params.bas:ACTC_c 2028.24
tmd-33-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-33-ext-ext+ctc-#-params.bas:ACTC_c 2074.28
tmd-34-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-34-ext-ext+ctc-#-params.bas:ACTC_c 2120.95
tmd-35-ext-ext+ctc-#-params.bas:CTC_c 2000.0
tmd-35-ext-ext+ctc-#-params.bas:ACTC_c 2168.67

% grep CTC_c tmd-??-ext-ext+ctc-#-params.ref | grep -v under6
tmd-26-ext-ext+ctc-#-params.ref:CTC_c 2500.0
tmd-26-ext-ext+ctc-#-params.ref:ACTC_c 1776.67
tmd-27-ext-ext+ctc-#-params.ref:CTC_c 2500.0
tmd-27-ext-ext+ctc-#-params.ref:ACTC_c 1818.24
tmd-28-ext-ext+ctc-#-params.ref:CTC_c 2500.0
tmd-28-ext-ext+ctc-#-params.ref:ACTC_c 1858.06
tmd-29-ext-ext+ctc-#-params.ref:CTC_c 2000.0
tmd-29-ext-ext+ctc-#-params.ref:ACTC_c 1898.57
tmd-30-ext-ext+ctc-#-params.ref:CTC_c 2044.2
tmd-30-ext-ext+ctc-#-params.ref:ACTC_c 1940.53
tmd-31-ext-ext+ctc-#-params.ref:CTC_c 2089.79
tmd-31-ext-ext+ctc-#-params.ref:ACTC_c 1983.8
tmd-32-ext-ext+ctc-#-params.ref:CTC_c 2136.6
tmd-32-ext-ext+ctc-#-params.ref:ACTC_c 2028.24
tmd-33-ext-ext+ctc-#-params.ref:CTC_c 2185.1
tmd-33-ext-ext+ctc-#-params.ref:ACTC_c 2074.28
tmd-34-ext-ext+ctc-#-params.ref:CTC_c 2234.26
tmd-34-ext-ext+ctc-#-params.ref:ACTC_c 2120.95
tmd-35-ext-ext+ctc-#-params.ref:CTC_c 2284.53
tmd-35-ext-ext+ctc-#-params.ref:ACTC_c 2168.67
```

Are those the baseline and reform parameter values that you want to analyze?

If so, then the change in aggregate income tax liability over the ten-year budget-window is:
```
% tail -1 tmd-??-ext-ext+ctc-#-tables.text | awk '$1~/A/{n++;c+=$4}END{print n,c}'                        
10 -135.2
```

@martinholmer: @hdublois,  Thank you for reporting the child-tax-credit-related problem in Tax-Calculator 4.6.1.

As mentioned above, the merge of pull request #2904 fixes this problem.  You can continue your CTC analysis by downloading the source code and building the `taxcalc` package on your computer, or you can wait for the release of Tax-Calculator 4.6.2, which I hope will be available in the next few days (after pull request #2905 is merged).

If you have any other problems using Tax-Calculator, please raise another issue.@hdublois: Fixed! Thanks @martinholmer !

Solution patch:
diff --git a/taxcalc/calcfunctions.py b/taxcalc/calcfunctions.py
index 79587a95..a7069606 100644
--- a/taxcalc/calcfunctions.py
+++ b/taxcalc/calcfunctions.py
@@ -3190,7 +3190,7 @@ def NonrefundableCredits(c05800, e07240, e07260, e07300, e07400,
 @iterate_jit(nopython=True)
 def AdditionalCTC(codtc_limited, ACTC_c, n24, earned, ACTC_Income_thd,
                   ACTC_rt, nu06, ACTC_rt_bonus_under6family, ACTC_ChildNum,
-                  CTC_is_refundable, CTC_include17,
+                  CTC_is_refundable, CTC_include17, CTC_c,
                   age_head, age_spouse, MARS, nu18,
                   ptax_was, c03260, e09800, c59660, e11200,
                   c11070):
@@ -3251,7 +3251,7 @@ def AdditionalCTC(codtc_limited, ACTC_c, n24, earned, ACTC_Income_thd,
             childnum = n24 + max(0, nu18 - tu18 - su18 - n24)
         else:
             childnum = n24
-        line4 = ACTC_c * childnum
+        line4 = min(ACTC_c, CTC_c) * childnum
     c11070 = 0.  # line15
     if line3 > 0. and line4 > 0.:
         line5 = min(line3, line4)
diff --git a/taxcalc/policy_current_law.json b/taxcalc/policy_current_law.json
index deadd633..9ef73c53 100644
--- a/taxcalc/policy_current_law.json
+++ b/taxcalc/policy_current_law.json
@@ -21819,7 +21819,7 @@
         "validators": {
             "range": {
                 "min": 0,
-                "max": "CTC_c"
+                "max": 9e+99
             }
         },
         "compatible_data": {
diff --git a/taxcalc/reforms/ext.json b/taxcalc/reforms/ext.json
index 69c368de..fb064865 100644
--- a/taxcalc/reforms/ext.json
+++ b/taxcalc/reforms/ext.json
@@ -34,8 +34,6 @@
     "CTC_c": {"2026": 2000.00},
     "ACTC_c": {"2026": 1776.67},
     "ACTC_c-indexed": {"2026": true},
-    "ACTC_c": {"2032": 2000.00},
-    "ACTC_c-indexed": {"2032": false},
     "ODC_c": {"2026": 500.00},
     "CTC_ps": {"2026": [200000.0, 400000.0, 200000.0, 200000.0, 400000.0]},
     "ACTC_Income_thd": {"2026": 2500.00},
diff --git a/taxcalc/taxcalcio.py b/taxcalc/taxcalcio.py
index cb92fde4..795375f2 100644
--- a/taxcalc/taxcalcio.py
+++ b/taxcalc/taxcalcio.py
@@ -18,7 +18,7 @@ from taxcalc.consumption import Consumption
 from taxcalc.growdiff import GrowDiff
 from taxcalc.growfactors import GrowFactors
 from taxcalc.calculator import Calculator
-from taxcalc.utils import (delete_file, write_graph_file,
+from taxcalc.utils import (json_to_dict, delete_file, write_graph_file,
                            add_quantile_table_row_variable,
                            unweighted_sum, weighted_sum)
 
@@ -133,7 +133,17 @@ class TaxCalcIO():
                     msg = f'{fname} does not end in .json'
                     self.errmsg += f'ERROR: BASELINE file name {msg}\n'
                 # check existence of BASELINE file
-                if not os.path.isfile(bas):
+                if os.path.isfile(bas):
+                    # check validity of JSON text
+                    with open(bas, 'r', encoding='utf-8') as jfile:
+                        json_text = jfile.read()
+                        try:
+                            _ = json_to_dict(json_text)
+                        except ValueError as valerr:  # pragma: no cover
+                            msg = f'{bas} contains invalid JSON'
+                            self.errmsg += f'ERROR: BASELINE file {msg}\n'
+                            self.errmsg += f'{valerr}'
+                else:
                     msg = f'{bas} could not be found'
                     self.errmsg += f'ERROR: BASELINE file {msg}\n'
                 # add fname to list of basnames used in output file names
@@ -167,7 +177,17 @@ class TaxCalcIO():
                     msg = f'{fname} does not end in .json'
                     self.errmsg += f'ERROR: REFORM file name {msg}\n'
                 # check existence of REFORM file
-                if not os.path.isfile(rfm):
+                if os.path.isfile(rfm):
+                    # check validity of JSON text
+                    with open(rfm, 'r', encoding='utf-8') as jfile:
+                        json_text = jfile.read()
+                        try:
+                            _ = json_to_dict(json_text)
+                        except ValueError as valerr:  # pragma: no cover
+                            msg = f'{rfm} contains invalid JSON'
+                            self.errmsg += f'ERROR: REFORM file {msg}\n'
+                            self.errmsg += f'{valerr}'
+                else:
                     msg = f'{rfm} could not be found'
                     self.errmsg += f'ERROR: REFORM file {msg}\n'
                 # add fname to list of refnames used in output file names
diff --git a/taxcalc/utils.py b/taxcalc/utils.py
index 73cc6f1b..3e85fe9f 100644
--- a/taxcalc/utils.py
+++ b/taxcalc/utils.py
@@ -8,6 +8,7 @@ PUBLIC low-level utility functions for Tax-Calculator.
 # pylint: disable=too-many-lines
 
 import os
+import re
 import math
 import json
 import copy
@@ -1574,25 +1575,45 @@ def bootstrap_se_ci(data, seed, num_samples, statistic, alpha):
     return bsest
 
 
-def json_to_dict(json_text):
+def json_to_dict(jsontext):
     """
     Convert specified JSON text into an ordered Python dictionary.
 
     Parameters
     ----------
-    json_text: string
-        JSON text.
+    jsontext: string
+        JSON text that may contain comments, which will be removed
 
     Raises
     ------
     ValueError:
-        if json_text contains a JSON syntax error.
+        if jsontext contains a JSON syntax error after comments are removed
 
     Returns
     -------
     dictionary: collections.OrderedDict
-        JSON data expressed as an ordered Python dictionary.
+        JSON data expressed as an ordered Python dictionary
     """
+    def remove_comments(string):
+        """
+        Remove single and multiline comments from JSON.
+        Logic follows https://stackoverflow.com/a/18381470/9100772
+        """
+        def _replacer(match):
+            # if the 2nd group (capturing comments) is not None,
+            # it means we have captured a non-quoted (real) comment string.
+            if match.group(2) is not None:
+                return "\n"  # preserve line numbers
+            # otherwise, we will return the 1st group
+            return match.group(1)  # captured quoted-string
+        # begin main remove_comments function logic
+        pattern = r"(\".*?\"|\'.*?\')|(/\*.*?\*/|//[^\r\n]*$)"
+        # first group captures quoted strings (double or single)
+        # second group captures comments (//single-line or /* multi-line */)
+        regex = re.compile(pattern, re.MULTILINE | re.DOTALL)
+        return regex.sub(_replacer, string)
+    # begin main json_to_dict function logic
+    json_text = remove_comments(jsontext)
     try:
         ordered_dict = json.loads(json_text,
                                   object_pairs_hook=collections.OrderedDict)
@@ -1610,5 +1631,6 @@ def json_to_dict(json_text):
             linenum += 1
             msg += f'{linenum:04d}{line}\n'
         msg += bline + '\n'
+        msg += 'If still puzzled, try using JSONLint online.\n'
         raise ValueError(msg) from valerr
     return ordered_dict


Test patch:
diff --git a/taxcalc/tests/test_policy.py b/taxcalc/tests/test_policy.py
index 678b6891..70e947c3 100644
--- a/taxcalc/tests/test_policy.py
+++ b/taxcalc/tests/test_policy.py
@@ -811,19 +811,6 @@ def test_get_index_rate():
     assert pol.wage_growth_rates() == pol._wage_growth_rates
 
 
-def test_reform_with_bad_ctc_levels():
-    """
-    Implement a reform with _ACTC > _CTC_c values.
-    """
-    pol = Policy()
-    child_credit_reform = {
-        'CTC_c': {2020: 2200},
-        'ACTC_c': {2020: 2500}
-    }
-    with pytest.raises(pt.ValidationError):
-        pol.implement_reform(child_credit_reform)
-
-
 def test_reform_with_removed_parameter(monkeypatch):
     """
     Try to use removed parameter in a reform.
diff --git a/taxcalc/tests/test_reforms.py b/taxcalc/tests/test_reforms.py
index d8e1a639..78f27449 100644
--- a/taxcalc/tests/test_reforms.py
+++ b/taxcalc/tests/test_reforms.py
@@ -386,4 +386,4 @@ def test_ext_reform(tests_path):
     iitax_ext = calc_ext.array('iitax')
     rdiff = iitax_ext - iitax_end
     weighted_sum_rdiff = (rdiff * calc_end.array('s006')).sum() * 1.0e-9
-    assert np.allclose([weighted_sum_rdiff], [-205.769], rtol=0.0, atol=0.01)
+    assert np.allclose([weighted_sum_rdiff], [-214.393], rtol=0.0, atol=0.01)
diff --git a/taxcalc/tests/test_taxcalcio.py b/taxcalc/tests/test_taxcalcio.py
index 01a585ea..0ee3c36c 100644
--- a/taxcalc/tests/test_taxcalcio.py
+++ b/taxcalc/tests/test_taxcalcio.py
@@ -153,6 +153,24 @@ def fixture_errorreformfile():
             pass  # sometimes we can't remove a generated temporary file
 
 
+@pytest.fixture(scope='session', name='ereformfile')
+def fixture_ereformfile():
+    """
+    Temporary reform file with .json extension.
+    """
+    contents = '{"II_em": {"2022": 1000},}'
+    with tempfile.NamedTemporaryFile(
+            suffix='.json', mode='a', delete=False
+    ) as rfile:
+        rfile.write(contents)
+    yield rfile
+    if os.path.isfile(rfile.name):
+        try:
+            os.remove(rfile.name)
+        except OSError:
+            pass  # sometimes we can't remove a generated temporary file
+
+
 @pytest.fixture(scope='session', name='errorassumpfile')
 def fixture_errorassumpfile():
     """
@@ -249,6 +267,7 @@ def fixture_assumpfile2():
      'no-dot-json-filename'),
     ([], [], [], [],),
     ('no-exist.csv', 'no-exist.json', 'no-exist.json', 'no-exist.json'),
+    ('cps.csv', 'ereformfile', 'ereformfile', 'no-exist.json'),
 ])
 def test_ctor_errors(input_data, baseline, reform, assump):
     """