Skip to content

Commit

Permalink
Merge pull request #386 from jdebacker/ip1
Browse files Browse the repository at this point in the history
Updates to the tax treatment of intangibles
  • Loading branch information
jdebacker authored Apr 22, 2024
2 parents cc4d6a7 + 9b0415a commit 2a1b498
Show file tree
Hide file tree
Showing 13 changed files with 866 additions and 443 deletions.
8 changes: 5 additions & 3 deletions .github/workflows/build_and_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:

steps:
- name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
persist-credentials: false

Expand All @@ -36,10 +36,12 @@ jobs:
run: |
pytest -m 'not needs_puf' --cov=./ --cov-report=xml
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
if: matrix.os == 'ubuntu-latest'
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
file: ./coverage.xml
flags: unittests
name: codecov-umbrella
fail_ci_if_error: true
verbose: true
verbose: true
115 changes: 110 additions & 5 deletions ccc/calcfunctions.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import numpy as np
import pandas as pd
from ccc.constants import TAX_METHODS
from ccc.constants import TAX_METHODS, RE_ASSETS, RE_INDUSTRIES
from ccc.utils import str_modified

pd.set_option("future.no_silent_downcasting", True)
Expand Down Expand Up @@ -140,6 +140,49 @@ def econ(delta, bonus, r, pi):
return z


def income_forecast(Y, delta, bonus, r):
r"""
Makes the calculation for the income forecast method.
The income forecast method involved deducting expenses in relation
to forecasted income over the next 10 years. CCC follows the CBO
methodology (CBO, 2018:
https://www.cbo.gov/system/files/2018-11/54648-Intangible_Assets.pdf)
and approximate this method with the DBSL method, but with a the "b"
factor determined by economic depreciation rates.
.. math::
z = \frac{\beta}{\beta+r}\left[1-e^{-(\beta+r)Y^{*}}\right]+
\frac{e^{-\beta Y^{*}}}{(Y-Y^{*})r}
\left[e^{-rY^{*}}-e^{-rY}\right]
Args:
Y (array_like): asset life in years
delta (array_like): rate of economic depreciation
bonus (array_like): rate of bonus depreciation
r (scalar): discount rate
Returns:
z (array_like): net present value of depreciation deductions for
$1 of investment
"""
b = 10 * delta
beta = b / Y
Y_star = Y * (1 - (1 / b))
z = bonus + (
(1 - bonus)
* (
((beta / (beta + r)) * (1 - np.exp(-1 * (beta + r) * Y_star)))
+ (
(np.exp(-1 * beta * Y_star) / ((Y - Y_star) * r))
* (np.exp(-1 * r * Y_star) - np.exp(-1 * r * Y))
)
)
)
return z


def npv_tax_depr(df, r, pi, land_expensing):
"""
Depending on the method of depreciation, makes calls to either
Expand All @@ -164,6 +207,10 @@ def npv_tax_depr(df, r, pi, land_expensing):
df.loc[idx, "z"] = sl(df.loc[idx, "Y"], df.loc[idx, "bonus"], r)
idx = df["method"] == "Economic"
df.loc[idx, "z"] = econ(df.loc[idx, "delta"], df.loc[idx, "bonus"], r, pi)
idx = df["method"] == "Income Forecast"
df.loc[idx, "z"] = income_forecast(
df.loc[idx, "Y"], df.loc[idx, "delta"], df.loc[idx, "bonus"], r
)
idx = df["method"] == "Expensing"
df.loc[idx, "z"] = 1.0
idx = df["asset_name"] == "Land"
Expand All @@ -175,30 +222,88 @@ def npv_tax_depr(df, r, pi, land_expensing):
return z


def eq_coc(delta, z, w, u, inv_tax_credit, pi, r):
def eq_coc(
delta,
z,
w,
u,
u_d,
inv_tax_credit,
psi,
nu,
pi,
r,
re_credit=None,
asset_code=None,
ind_code=None,
):
r"""
Compute the cost of capital
.. math::
\rho = \frac{(r-\pi+\delta)}{1-u}(1-uz)+w-\delta
\rho = \frac{(r-\pi+\delta)}{1-u}(1-u_dz(1-\psi k) - k\nu)+w-\delta
Args:
delta (array_like): rate of economic depreciation
z (array_like): net present value of depreciation deductions for
$1 of investment
w (scalar): property tax rate
u (scalar): statutory marginal tax rate for the first layer of
u (scalar): marginal tax rate for the first layer of
income taxes
u_d (scalar): marginal tax rate on deductions
inv_tax_credit (scalar): investment tax credit rate
psi (scalar): fraction investment tax credit that affects
depreciable basis of the investment
nu (scalar): NPV of the investment tax credit
pi (scalar): inflation rate
r (scalar): discount rate
re_credit (dict): rate of R&E credit by asset or industry
asset_code (array_like): asset code
ind_code (array_like): industry code
Returns:
rho (array_like): the cost of capital
"""
# Initialize re_credit_rate (only needed if arrays are passed in --
# if not, can include the R&E credit in the inv_tax_credit object)
if isinstance(delta, np.ndarray):
re_credit_rate_ind = np.zeros_like(delta)
re_credit_rate_asset = np.zeros_like(delta)
# Update by R&E credit rate amounts by industry
if (ind_code is not None) and (re_credit is not None):
idx = [
index
for index, element in enumerate(ind_code)
if element in re_credit["By industry"].keys()
]
print("Keys = ", re_credit["By industry"].keys())
print("Ind idx = ", idx)
print("Dict = ", re_credit["By industry"], re_credit)
ind_code_idx = [ind_code[i] for i in idx]
re_credit_rate_ind[idx] = [
re_credit["By industry"][ic] for ic in ind_code_idx
]
# Update by R&E credit rate amounts by asset
if (asset_code is not None) and (re_credit is not None):
idx = [
index
for index, element in enumerate(asset_code)
if element in re_credit["By asset"].keys()
]
asset_code_idx = [asset_code[i] for i in idx]
re_credit_rate_asset[idx] = [
re_credit["By asset"][ac] for ac in asset_code_idx
]
# take the larger of the two R&E credit rates
inv_tax_credit += np.maximum(re_credit_rate_asset, re_credit_rate_ind)
print("RE_credit object =", re_credit)
print("inv_tax_credit object =", inv_tax_credit)
rho = (
((r - pi + delta) / (1 - u)) * (1 - inv_tax_credit - u * z) + w - delta
((r - pi + delta) / (1 - u))
* (1 - inv_tax_credit * nu - u_d * z * (1 - psi * inv_tax_credit))
+ w
- delta
)

return rho
Expand Down
6 changes: 6 additions & 0 deletions ccc/calculator.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,9 +196,15 @@ def calc_base(self):
dfs[t]["z_" + str(f)],
self.__p.property_tax,
self.__p.u[t],
self.__p.u_d[t],
self.__p.inv_tax_credit,
self.__p.psi,
self.__p.nu,
self.__p.inflation_rate,
self.__p.r[t][f],
self.__p.re_credit,
dfs[t]["bea_asset_code"],
dfs[t]["bea_ind_code"],
)
if not self.__p.inventory_expensing:
idx = dfs[t]["asset_name"] == "Inventories"
Expand Down
13 changes: 13 additions & 0 deletions ccc/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,18 @@

OUTPUT_DATA_FORMATS = ["csv", "tex", "excel", "json", "html", None]

# TODO: perhaps make as a dict so that can vary across years?
# And if policy variant, maybe move to default params?
RE_ASSETS = [
"ENS3", # "Credit-eligible own account software",
"RD70", # "Credit-eligible research and development",
"SU60", # "Wind and solar power structures",
]

RE_INDUSTRIES = [
"3340", # "Computer_and_Electronic_Product_Manufacturing",
]

MAJOR_IND_ORDERED = [
"Agriculture, forestry, fishing, and hunting",
"Mining",
Expand Down Expand Up @@ -48,6 +60,7 @@
"SL": 1.0,
"Economic": 1.0,
"Expensing": 1.0,
"Income Forecast": 1.0,
}

MINOR_ASSET_GROUPS = dict.fromkeys(
Expand Down
Loading

0 comments on commit 2a1b498

Please sign in to comment.