@@ -204,11 +204,17 @@ 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+ "capital_gains_losses" : "loss_limited_net_capital_gains" ,
215+ "partnership_and_s_corp_losses" : "tax_unit_partnership_s_corp_income" ,
216+ "rent_and_royalty_net_losses" : "tax_unit_rental_income" ,
217+ }
212218SOI_FILING_STATUS_CONSTRAINTS = {
213219 "Single" : ("==" , "SINGLE" ),
214220 "Head of Household" : ("==" , "HEAD_OF_HOUSEHOLD" ),
@@ -694,6 +700,110 @@ def _get_or_create_national_agi_domain_stratum(
694700 return stratum
695701
696702
703+ def _get_or_create_national_agi_stratum (
704+ session : Session ,
705+ national_filer_stratum_id : int ,
706+ * ,
707+ agi_lower_bound : float ,
708+ agi_upper_bound : float ,
709+ ) -> Stratum :
710+ note = f"National filers, AGI >= { agi_lower_bound } , AGI < { agi_upper_bound } "
711+ stratum = session .exec (
712+ select (Stratum ).where (
713+ Stratum .parent_stratum_id == national_filer_stratum_id ,
714+ Stratum .notes == note ,
715+ )
716+ ).first ()
717+ if stratum :
718+ return stratum
719+
720+ stratum = Stratum (
721+ parent_stratum_id = national_filer_stratum_id ,
722+ notes = note ,
723+ )
724+ stratum .constraints_rel .extend (
725+ [
726+ StratumConstraint (
727+ constraint_variable = "tax_unit_is_filer" ,
728+ operation = "==" ,
729+ value = "1" ,
730+ ),
731+ StratumConstraint (
732+ constraint_variable = "adjusted_gross_income" ,
733+ operation = ">=" ,
734+ value = str (agi_lower_bound ),
735+ ),
736+ StratumConstraint (
737+ constraint_variable = "adjusted_gross_income" ,
738+ operation = "<" ,
739+ value = str (agi_upper_bound ),
740+ ),
741+ ]
742+ )
743+ session .add (stratum )
744+ session .flush ()
745+ return stratum
746+
747+
748+ def _get_or_create_national_taxable_agi_negative_domain_stratum (
749+ session : Session ,
750+ national_filer_stratum_id : int ,
751+ * ,
752+ domain_variable : str ,
753+ agi_lower_bound : float ,
754+ agi_upper_bound : float ,
755+ ) -> Stratum :
756+ note = (
757+ "National taxable filers, AGI >= "
758+ f"{ agi_lower_bound } , AGI < { agi_upper_bound } , { domain_variable } < 0"
759+ )
760+ stratum = session .exec (
761+ select (Stratum ).where (
762+ Stratum .parent_stratum_id == national_filer_stratum_id ,
763+ Stratum .notes == note ,
764+ )
765+ ).first ()
766+ if stratum :
767+ return stratum
768+
769+ stratum = Stratum (
770+ parent_stratum_id = national_filer_stratum_id ,
771+ notes = note ,
772+ )
773+ stratum .constraints_rel .extend (
774+ [
775+ StratumConstraint (
776+ constraint_variable = "tax_unit_is_filer" ,
777+ operation = "==" ,
778+ value = "1" ,
779+ ),
780+ StratumConstraint (
781+ constraint_variable = "income_tax_before_credits" ,
782+ operation = ">" ,
783+ value = "0" ,
784+ ),
785+ StratumConstraint (
786+ constraint_variable = "adjusted_gross_income" ,
787+ operation = ">=" ,
788+ value = str (agi_lower_bound ),
789+ ),
790+ StratumConstraint (
791+ constraint_variable = "adjusted_gross_income" ,
792+ operation = "<" ,
793+ value = str (agi_upper_bound ),
794+ ),
795+ StratumConstraint (
796+ constraint_variable = domain_variable ,
797+ operation = "<" ,
798+ value = "0" ,
799+ ),
800+ ]
801+ )
802+ session .add (stratum )
803+ session .flush ()
804+ return stratum
805+
806+
697807def _get_or_create_national_eitc_agi_child_stratum (
698808 session : Session ,
699809 national_filer_stratum_id : int ,
@@ -1122,6 +1232,86 @@ def load_national_taxable_agi_domain_filing_status_targets(
11221232 )
11231233
11241234
1235+ def load_national_negative_agi_targets (
1236+ session : Session ,
1237+ national_filer_stratum_id : int ,
1238+ target_year : int ,
1239+ ) -> None :
1240+ """Create all-return negative-AGI amount and count targets."""
1241+ soi = get_soi (target_year )
1242+ rows = soi [
1243+ soi ["Variable" ].isin (SOI_NEGATIVE_AGI_TARGET_VARIABLES )
1244+ & (soi ["Filing status" ] == "All" )
1245+ & (soi ["AGI lower bound" ] == - np .inf )
1246+ & (soi ["AGI upper bound" ] == 0 )
1247+ & (~ soi ["Taxable only" ])
1248+ ].copy ()
1249+
1250+ for _ , row in rows .iterrows ():
1251+ source_variable = row ["Variable" ]
1252+ target_variable = SOI_NEGATIVE_AGI_TARGET_VARIABLES [source_variable ]
1253+ stratum = _get_or_create_national_agi_stratum (
1254+ session ,
1255+ national_filer_stratum_id ,
1256+ agi_lower_bound = float (row ["AGI lower bound" ]),
1257+ agi_upper_bound = float (row ["AGI upper bound" ]),
1258+ )
1259+ notes = (
1260+ f"Publication 1304 { row ['SOI table' ]} all-return negative-AGI "
1261+ f"target (source year { int (row ['Year' ])} , row { int (row ['XLSX row' ])} )"
1262+ )
1263+ _upsert_target (
1264+ session ,
1265+ stratum_id = stratum .stratum_id ,
1266+ variable = target_variable ,
1267+ period = int (target_year ),
1268+ value = float (row ["Value" ]),
1269+ source = "IRS SOI" ,
1270+ notes = notes ,
1271+ )
1272+
1273+
1274+ def load_national_taxable_loss_agi_targets (
1275+ session : Session ,
1276+ national_filer_stratum_id : int ,
1277+ target_year : int ,
1278+ ) -> None :
1279+ """Create taxable loss-component targets by AGI band."""
1280+ soi = get_soi (target_year )
1281+ rows = soi [
1282+ soi ["Variable" ].isin (SOI_TAXABLE_LOSS_AGI_TARGET_VARIABLES )
1283+ & (soi ["Filing status" ] == "All" )
1284+ & (soi ["Taxable only" ])
1285+ & (~ soi ["Full population" ])
1286+ & (soi ["Value" ] > 0 )
1287+ ].copy ()
1288+
1289+ for _ , row in rows .iterrows ():
1290+ source_variable = row ["Variable" ]
1291+ target_variable = SOI_TAXABLE_LOSS_AGI_TARGET_VARIABLES [source_variable ]
1292+ stratum = _get_or_create_national_taxable_agi_negative_domain_stratum (
1293+ session ,
1294+ national_filer_stratum_id ,
1295+ domain_variable = target_variable ,
1296+ agi_lower_bound = float (row ["AGI lower bound" ]),
1297+ agi_upper_bound = float (row ["AGI upper bound" ]),
1298+ )
1299+ notes = (
1300+ f"Publication 1304 { row ['SOI table' ]} taxable AGI-band "
1301+ f"{ source_variable } target "
1302+ f"(source year { int (row ['Year' ])} , row { int (row ['XLSX row' ])} )"
1303+ )
1304+ _upsert_target (
1305+ session ,
1306+ stratum_id = stratum .stratum_id ,
1307+ variable = "tax_unit_count" if bool (row ["Count" ]) else target_variable ,
1308+ period = int (target_year ),
1309+ value = (float (row ["Value" ]) if bool (row ["Count" ]) else - float (row ["Value" ])),
1310+ source = "IRS SOI" ,
1311+ notes = notes ,
1312+ )
1313+
1314+
11251315def load_national_workbook_soi_targets (
11261316 session : Session , national_filer_stratum_id : int , target_year : int
11271317) -> None :
@@ -1721,6 +1911,16 @@ def load_soi_data(
17211911 filer_strata ["national" ],
17221912 target_year or national_year ,
17231913 )
1914+ load_national_negative_agi_targets (
1915+ session ,
1916+ filer_strata ["national" ],
1917+ target_year or national_year ,
1918+ )
1919+ load_national_taxable_loss_agi_targets (
1920+ session ,
1921+ filer_strata ["national" ],
1922+ target_year or national_year ,
1923+ )
17241924 load_national_fine_agi_targets (session , filer_strata ["national" ], national_year )
17251925 load_national_ltcg_agi_targets (session , filer_strata ["national" ], national_year )
17261926
0 commit comments