@@ -204,11 +204,19 @@ def _skip_coarse_state_agi_person_count_target(geo_type: str, agi_stub: int) ->
204204 "adjusted_gross_income" : "adjusted_gross_income" ,
205205 "count" : "tax_unit_count" ,
206206}
207+ SOI_NEGATIVE_AGI_TARGET_VARIABLES = dict (SOI_TAXABLE_AGI_TARGET_VARIABLES )
207208SOI_TAXABLE_AGI_DOMAIN_TARGET_VARIABLES = {
208209 "employment_income" : "irs_employment_income" ,
209210 "total_pension_income" : "pension_income" ,
210211 "total_social_security" : "social_security" ,
211212}
213+ SOI_TAXABLE_LOSS_AGI_TARGET_VARIABLES = {
214+ "business_net_losses" : "total_self_employment_income" ,
215+ "capital_gains_losses" : "loss_limited_net_capital_gains" ,
216+ "estate_losses" : "estate_income" ,
217+ "partnership_and_s_corp_losses" : "tax_unit_partnership_s_corp_income" ,
218+ "rent_and_royalty_net_losses" : "tax_unit_rental_income" ,
219+ }
212220SOI_FILING_STATUS_CONSTRAINTS = {
213221 "Single" : ("==" , "SINGLE" ),
214222 "Head of Household" : ("==" , "HEAD_OF_HOUSEHOLD" ),
@@ -694,6 +702,110 @@ def _get_or_create_national_agi_domain_stratum(
694702 return stratum
695703
696704
705+ def _get_or_create_national_agi_stratum (
706+ session : Session ,
707+ national_filer_stratum_id : int ,
708+ * ,
709+ agi_lower_bound : float ,
710+ agi_upper_bound : float ,
711+ ) -> Stratum :
712+ note = f"National filers, AGI >= { agi_lower_bound } , AGI < { agi_upper_bound } "
713+ stratum = session .exec (
714+ select (Stratum ).where (
715+ Stratum .parent_stratum_id == national_filer_stratum_id ,
716+ Stratum .notes == note ,
717+ )
718+ ).first ()
719+ if stratum :
720+ return stratum
721+
722+ stratum = Stratum (
723+ parent_stratum_id = national_filer_stratum_id ,
724+ notes = note ,
725+ )
726+ stratum .constraints_rel .extend (
727+ [
728+ StratumConstraint (
729+ constraint_variable = "tax_unit_is_filer" ,
730+ operation = "==" ,
731+ value = "1" ,
732+ ),
733+ StratumConstraint (
734+ constraint_variable = "adjusted_gross_income" ,
735+ operation = ">=" ,
736+ value = str (agi_lower_bound ),
737+ ),
738+ StratumConstraint (
739+ constraint_variable = "adjusted_gross_income" ,
740+ operation = "<" ,
741+ value = str (agi_upper_bound ),
742+ ),
743+ ]
744+ )
745+ session .add (stratum )
746+ session .flush ()
747+ return stratum
748+
749+
750+ def _get_or_create_national_taxable_agi_negative_domain_stratum (
751+ session : Session ,
752+ national_filer_stratum_id : int ,
753+ * ,
754+ domain_variable : str ,
755+ agi_lower_bound : float ,
756+ agi_upper_bound : float ,
757+ ) -> Stratum :
758+ note = (
759+ "National taxable filers, AGI >= "
760+ f"{ agi_lower_bound } , AGI < { agi_upper_bound } , { domain_variable } < 0"
761+ )
762+ stratum = session .exec (
763+ select (Stratum ).where (
764+ Stratum .parent_stratum_id == national_filer_stratum_id ,
765+ Stratum .notes == note ,
766+ )
767+ ).first ()
768+ if stratum :
769+ return stratum
770+
771+ stratum = Stratum (
772+ parent_stratum_id = national_filer_stratum_id ,
773+ notes = note ,
774+ )
775+ stratum .constraints_rel .extend (
776+ [
777+ StratumConstraint (
778+ constraint_variable = "tax_unit_is_filer" ,
779+ operation = "==" ,
780+ value = "1" ,
781+ ),
782+ StratumConstraint (
783+ constraint_variable = "income_tax_before_credits" ,
784+ operation = ">" ,
785+ value = "0" ,
786+ ),
787+ StratumConstraint (
788+ constraint_variable = "adjusted_gross_income" ,
789+ operation = ">=" ,
790+ value = str (agi_lower_bound ),
791+ ),
792+ StratumConstraint (
793+ constraint_variable = "adjusted_gross_income" ,
794+ operation = "<" ,
795+ value = str (agi_upper_bound ),
796+ ),
797+ StratumConstraint (
798+ constraint_variable = domain_variable ,
799+ operation = "<" ,
800+ value = "0" ,
801+ ),
802+ ]
803+ )
804+ session .add (stratum )
805+ session .flush ()
806+ return stratum
807+
808+
697809def _get_or_create_national_eitc_agi_child_stratum (
698810 session : Session ,
699811 national_filer_stratum_id : int ,
@@ -1122,6 +1234,86 @@ def load_national_taxable_agi_domain_filing_status_targets(
11221234 )
11231235
11241236
1237+ def load_national_negative_agi_targets (
1238+ session : Session ,
1239+ national_filer_stratum_id : int ,
1240+ target_year : int ,
1241+ ) -> None :
1242+ """Create all-return negative-AGI amount and count targets."""
1243+ soi = get_soi (target_year )
1244+ rows = soi [
1245+ soi ["Variable" ].isin (SOI_NEGATIVE_AGI_TARGET_VARIABLES )
1246+ & (soi ["Filing status" ] == "All" )
1247+ & (soi ["AGI lower bound" ] == - np .inf )
1248+ & (soi ["AGI upper bound" ] == 0 )
1249+ & (~ soi ["Taxable only" ])
1250+ ].copy ()
1251+
1252+ for _ , row in rows .iterrows ():
1253+ source_variable = row ["Variable" ]
1254+ target_variable = SOI_NEGATIVE_AGI_TARGET_VARIABLES [source_variable ]
1255+ stratum = _get_or_create_national_agi_stratum (
1256+ session ,
1257+ national_filer_stratum_id ,
1258+ agi_lower_bound = float (row ["AGI lower bound" ]),
1259+ agi_upper_bound = float (row ["AGI upper bound" ]),
1260+ )
1261+ notes = (
1262+ f"Publication 1304 { row ['SOI table' ]} all-return negative-AGI "
1263+ f"target (source year { int (row ['Year' ])} , row { int (row ['XLSX row' ])} )"
1264+ )
1265+ _upsert_target (
1266+ session ,
1267+ stratum_id = stratum .stratum_id ,
1268+ variable = target_variable ,
1269+ period = int (target_year ),
1270+ value = float (row ["Value" ]),
1271+ source = "IRS SOI" ,
1272+ notes = notes ,
1273+ )
1274+
1275+
1276+ def load_national_taxable_loss_agi_targets (
1277+ session : Session ,
1278+ national_filer_stratum_id : int ,
1279+ target_year : int ,
1280+ ) -> None :
1281+ """Create taxable loss-component targets by AGI band."""
1282+ soi = get_soi (target_year )
1283+ rows = soi [
1284+ soi ["Variable" ].isin (SOI_TAXABLE_LOSS_AGI_TARGET_VARIABLES )
1285+ & (soi ["Filing status" ] == "All" )
1286+ & (soi ["Taxable only" ])
1287+ & (~ soi ["Full population" ])
1288+ & (soi ["Value" ] > 0 )
1289+ ].copy ()
1290+
1291+ for _ , row in rows .iterrows ():
1292+ source_variable = row ["Variable" ]
1293+ target_variable = SOI_TAXABLE_LOSS_AGI_TARGET_VARIABLES [source_variable ]
1294+ stratum = _get_or_create_national_taxable_agi_negative_domain_stratum (
1295+ session ,
1296+ national_filer_stratum_id ,
1297+ domain_variable = target_variable ,
1298+ agi_lower_bound = float (row ["AGI lower bound" ]),
1299+ agi_upper_bound = float (row ["AGI upper bound" ]),
1300+ )
1301+ notes = (
1302+ f"Publication 1304 { row ['SOI table' ]} taxable AGI-band "
1303+ f"{ source_variable } target "
1304+ f"(source year { int (row ['Year' ])} , row { int (row ['XLSX row' ])} )"
1305+ )
1306+ _upsert_target (
1307+ session ,
1308+ stratum_id = stratum .stratum_id ,
1309+ variable = "tax_unit_count" if bool (row ["Count" ]) else target_variable ,
1310+ period = int (target_year ),
1311+ value = (float (row ["Value" ]) if bool (row ["Count" ]) else - float (row ["Value" ])),
1312+ source = "IRS SOI" ,
1313+ notes = notes ,
1314+ )
1315+
1316+
11251317def load_national_workbook_soi_targets (
11261318 session : Session , national_filer_stratum_id : int , target_year : int
11271319) -> None :
@@ -1721,6 +1913,16 @@ def load_soi_data(
17211913 filer_strata ["national" ],
17221914 target_year or national_year ,
17231915 )
1916+ load_national_negative_agi_targets (
1917+ session ,
1918+ filer_strata ["national" ],
1919+ target_year or national_year ,
1920+ )
1921+ load_national_taxable_loss_agi_targets (
1922+ session ,
1923+ filer_strata ["national" ],
1924+ target_year or national_year ,
1925+ )
17241926 load_national_fine_agi_targets (session , filer_strata ["national" ], national_year )
17251927 load_national_ltcg_agi_targets (session , filer_strata ["national" ], national_year )
17261928
0 commit comments