diff --git a/.github/workflows/core-tests.yml b/.github/workflows/core-tests.yml index 30b2d6a..b15ddc7 100644 --- a/.github/workflows/core-tests.yml +++ b/.github/workflows/core-tests.yml @@ -17,7 +17,7 @@ jobs: - name: Install sbt run: | - curl -fsSL "https://github.com/sbt/sbt/releases/download/v1.10.7/sbt-1.10.7.tgz" | sudo tar xz -C /usr/local + curl -fsSL "https://github.com/sbt/sbt/releases/download/v1.11.6/sbt-1.11.6.tgz" | sudo tar xz -C /usr/local echo "/usr/local/sbt/bin" >> "$GITHUB_PATH" - name: Check formatting diff --git a/build.sbt b/build.sbt index 2cc1412..11178b8 100644 --- a/build.sbt +++ b/build.sbt @@ -1,4 +1,4 @@ -val scala3Version = "3.5.2" +val scala3Version = "3.8.2" lazy val root = project .in(file(".")) diff --git a/project/build.properties b/project/build.properties index e88a0d8..5e6884d 100644 --- a/project/build.properties +++ b/project/build.properties @@ -1 +1 @@ -sbt.version=1.10.6 +sbt.version=1.11.6 diff --git a/src/main/scala/sfc/agents/Banking.scala b/src/main/scala/sfc/agents/Banking.scala index 7f4c9f5..e4e487b 100644 --- a/src/main/scala/sfc/agents/Banking.scala +++ b/src/main/scala/sfc/agents/Banking.scala @@ -1,7 +1,6 @@ package sfc.agents import sfc.config.SimParams -import sfc.engine.* import sfc.engine.mechanisms.{Macroprudential, YieldCurve} import sfc.types.* import sfc.util.KahanSum.* diff --git a/src/main/scala/sfc/agents/Household.scala b/src/main/scala/sfc/agents/Household.scala index 72343aa..7380820 100644 --- a/src/main/scala/sfc/agents/Household.scala +++ b/src/main/scala/sfc/agents/Household.scala @@ -690,14 +690,14 @@ object Household: /** Base income, benefit, and updated status for one HH. */ private def computeIncome(hh: State)(using SimParams): (PLN, PLN, HhStatus) = hh.status match - case HhStatus.Employed(firmId, sectorIdx, wage) => + case HhStatus.Employed(firmId, sectorIdx, wage) => (wage, PLN.Zero, hh.status) - case HhStatus.Unemployed(months) => + case HhStatus.Unemployed(months) => val benefit = computeBenefit(months) (benefit, benefit, HhStatus.Unemployed(months + 1)) - case HhStatus.Retraining(monthsLeft, target, cost) => + case HhStatus.Retraining(monthsLeft, _, cost) => (PLN.Zero, PLN.Zero, hh.status) - case HhStatus.Bankrupt => + case HhStatus.Bankrupt => (PLN.Zero, PLN.Zero, HhStatus.Bankrupt) /** Skill decay for long-term unemployed (onset after scarringOnset months). diff --git a/src/main/scala/sfc/agents/Immigration.scala b/src/main/scala/sfc/agents/Immigration.scala index 3998f02..1c9d2a0 100644 --- a/src/main/scala/sfc/agents/Immigration.scala +++ b/src/main/scala/sfc/agents/Immigration.scala @@ -23,7 +23,7 @@ object Immigration: /** Monthly immigration inflow. Exogenous: fixed rate × workingAgePop. * Endogenous: responds to (domesticWage / foreignWage − 1) × elasticity. */ - def computeInflow(workingAgePop: Int, wage: PLN, unempRate: Double, month: Int)(using p: SimParams): Int = + def computeInflow(workingAgePop: Int, wage: PLN, unempRate: Double, @scala.annotation.unused month: Int)(using p: SimParams): Int = if !p.flags.immigration then 0 else if p.flags.immigEndogenous then val wageGap = (wage.toDouble / p.immigration.foreignWage.toDouble - 1.0).max(0.0) diff --git a/src/main/scala/sfc/agents/Nbfi.scala b/src/main/scala/sfc/agents/Nbfi.scala index 7184868..fac1e68 100644 --- a/src/main/scala/sfc/agents/Nbfi.scala +++ b/src/main/scala/sfc/agents/Nbfi.scala @@ -121,16 +121,16 @@ object Nbfi: */ def step( prev: State, - employed: Int, // employed workers - wage: PLN, // average monthly wage - priceLevel: Double, // CPI price level (unused in current spec, kept for interface stability) - unempRate: Ratio, // unemployment rate - bankNplRatio: Ratio, // aggregate bank NPL ratio (tightness signal) - govBondYield: Rate, // government bond yield (annualised) - corpBondYield: Rate, // corporate bond yield (annualised) - equityReturn: Rate, // equity monthly return - depositRate: Rate, // bank deposit rate (TFI opportunity cost) - domesticCons: PLN, // domestic consumption (NBFI credit base) + employed: Int, // employed workers + wage: PLN, // average monthly wage + @scala.annotation.unused priceLevel: Double, // CPI price level (unused in current spec, kept for interface stability) + unempRate: Ratio, // unemployment rate + bankNplRatio: Ratio, // aggregate bank NPL ratio (tightness signal) + govBondYield: Rate, // government bond yield (annualised) + corpBondYield: Rate, // corporate bond yield (annualised) + equityReturn: Rate, // equity monthly return + depositRate: Rate, // bank deposit rate (TFI opportunity cost) + domesticCons: PLN, // domestic consumption (NBFI credit base) )(using p: SimParams): State = // TFI: inflow + investment income + rebalance val netInflow = tfiInflow(employed, wage, equityReturn, govBondYield, depositRate) diff --git a/src/main/scala/sfc/config/SimParams.scala b/src/main/scala/sfc/config/SimParams.scala index 956ded5..a8dd044 100644 --- a/src/main/scala/sfc/config/SimParams.scala +++ b/src/main/scala/sfc/config/SimParams.scala @@ -47,6 +47,7 @@ case class SectorDef( * Polish GDP (~3.5 bln PLN). Do NOT construct SimParams directly with unscaled * values — always start from `defaults` and use `.copy()`. */ +@annotation.nowarn("msg=unused private member") // Scala 3.8 false positive: defaults used via copy() case class SimParams private ( flags: FeatureFlags = FeatureFlags(), pop: PopulationConfig = PopulationConfig(), diff --git a/src/main/scala/sfc/engine/Simulation.scala b/src/main/scala/sfc/engine/Simulation.scala index 5e413da..403bb05 100644 --- a/src/main/scala/sfc/engine/Simulation.scala +++ b/src/main/scala/sfc/engine/Simulation.scala @@ -3,7 +3,6 @@ package sfc.engine import sfc.accounting.* import sfc.agents.* import sfc.config.* -import sfc.types.* import scala.util.Random diff --git a/src/main/scala/sfc/engine/markets/IntermediateMarket.scala b/src/main/scala/sfc/engine/markets/IntermediateMarket.scala index f9f7fdd..bc984ae 100644 --- a/src/main/scala/sfc/engine/markets/IntermediateMarket.scala +++ b/src/main/scala/sfc/engine/markets/IntermediateMarket.scala @@ -1,6 +1,6 @@ package sfc.engine.markets -import sfc.agents.{Firm, TechState} +import sfc.agents.Firm import sfc.config.SimParams import sfc.types.* import sfc.util.KahanSum.* diff --git a/src/main/scala/sfc/engine/markets/LaborMarket.scala b/src/main/scala/sfc/engine/markets/LaborMarket.scala index 7d71c1a..efc6c7a 100644 --- a/src/main/scala/sfc/engine/markets/LaborMarket.scala +++ b/src/main/scala/sfc/engine/markets/LaborMarket.scala @@ -83,7 +83,7 @@ object LaborMarket: households: Vector[Household.State], firms: Vector[Firm.State], marketWage: PLN, - rng: Random, + @scala.annotation.unused rng: Random, )(using p: SimParams): JobSearchResult = val vacancies = computeVacancies(households, firms) if vacancies.isEmpty then JobSearchResult(households, 0) diff --git a/src/main/scala/sfc/engine/mechanisms/Macroprudential.scala b/src/main/scala/sfc/engine/mechanisms/Macroprudential.scala index 8034983..91ed6e4 100644 --- a/src/main/scala/sfc/engine/mechanisms/Macroprudential.scala +++ b/src/main/scala/sfc/engine/mechanisms/Macroprudential.scala @@ -105,7 +105,7 @@ object Macroprudential: private[engine] def withinConcentrationLimitImpl( bankLoans: Double, - bankCapital: Double, + @scala.annotation.unused bankCapital: Double, totalSystemLoans: Double, )(using p: SimParams): Boolean = if totalSystemLoans <= 0 then true diff --git a/src/main/scala/sfc/engine/mechanisms/SectoralMobility.scala b/src/main/scala/sfc/engine/mechanisms/SectoralMobility.scala index 803af91..9310b1a 100644 --- a/src/main/scala/sfc/engine/mechanisms/SectoralMobility.scala +++ b/src/main/scala/sfc/engine/mechanisms/SectoralMobility.scala @@ -83,7 +83,7 @@ object SectoralMobility: matrix: Vector[Vector[Double]], vacancyWeight: Double, rng: Random, - )(using p: SimParams): Int = + )(using @scala.annotation.unused p: SimParams): Int = val scores = gravityScores(from, wages, vacancies, matrix, vacancyWeight) val total = scores.sum if total <= 0.0 then uniformFallback(from, rng) diff --git a/src/main/scala/sfc/montecarlo/McRunner.scala b/src/main/scala/sfc/montecarlo/McRunner.scala index c6d808f..610e754 100644 --- a/src/main/scala/sfc/montecarlo/McRunner.scala +++ b/src/main/scala/sfc/montecarlo/McRunner.scala @@ -20,7 +20,7 @@ object McRunner: /** Run one simulation with given seed. Throws [[Sfc.SfcViolationException]] * on any SFC identity violation. */ - def runSingle(seed: Long, rc: McRunConfig)(using p: SimParams): RunResult = + def runSingle(seed: Long, @scala.annotation.unused rc: McRunConfig)(using p: SimParams): RunResult = val init = WorldInit.initialize(seed) var state = Simulation.SimState(init.world, init.firms, init.households) diff --git a/src/main/scala/sfc/types.scala b/src/main/scala/sfc/types.scala index 33a23c3..a1a55f9 100644 --- a/src/main/scala/sfc/types.scala +++ b/src/main/scala/sfc/types.scala @@ -6,11 +6,9 @@ object types: // === Entity IDs === opaque type BankId = Int object BankId: - inline def apply(i: Int): BankId = i - val NoBank: BankId = -1 - extension (b: BankId) - inline def toInt: Int = b - inline def ==(other: Int): Boolean = b == other + inline def apply(i: Int): BankId = i + val NoBank: BankId = -1 + extension (b: BankId) inline def toInt: Int = b opaque type FirmId = Int object FirmId: diff --git a/src/test/scala/sfc/accounting/SfcSpec.scala b/src/test/scala/sfc/accounting/SfcSpec.scala index 9277096..4753b4f 100644 --- a/src/test/scala/sfc/accounting/SfcSpec.scala +++ b/src/test/scala/sfc/accounting/SfcSpec.scala @@ -109,6 +109,7 @@ class SfcSpec extends AnyFlatSpec with Matchers: ) }.toVector + @annotation.nowarn("msg=unused private member") // defaults used by callers private def makeHouseholds(n: Int, savings: Double = 15000.0, debt: Double = 0.0): Vector[Household.State] = (0 until n).map { i => Household.State( diff --git a/src/test/scala/sfc/agents/BankingSectorPropertySpec.scala b/src/test/scala/sfc/agents/BankingSectorPropertySpec.scala index eae9472..1cff04e 100644 --- a/src/test/scala/sfc/agents/BankingSectorPropertySpec.scala +++ b/src/test/scala/sfc/agents/BankingSectorPropertySpec.scala @@ -20,6 +20,7 @@ class BankingSectorPropertySpec extends AnyFlatSpec with Matchers with ScalaChec private val configs = Banking.DefaultConfigs + @annotation.nowarn("msg=unused private member") // defaults used by callers private def mkBank( id: Int = 0, deposits: PLN = PLN(1e6), diff --git a/src/test/scala/sfc/agents/HouseholdSpec.scala b/src/test/scala/sfc/agents/HouseholdSpec.scala index 5b6d4d2..79dafc1 100644 --- a/src/test/scala/sfc/agents/HouseholdSpec.scala +++ b/src/test/scala/sfc/agents/HouseholdSpec.scala @@ -136,9 +136,9 @@ class HouseholdSpec extends AnyFlatSpec with Matchers: // --- Variable-rate debt service + deposit interest --- "Household.step with bankRates" should "use variable lending rate for debt service" in { - val rng = new Random(42) - val debt = PLN(100000.0) - val hhs = Vector( + val rng = new Random(42) + val debt = PLN(100000.0) + val hhs = Vector( mkHousehold( 0, HhStatus.Employed(FirmId(0), SectorIdx(0), PLN(8000.0)), @@ -155,16 +155,16 @@ class HouseholdSpec extends AnyFlatSpec with Matchers: ), ) // Bank 0: 6% annual lending rate, Bank 1: 10% annual - val br = BankRates( + val br = BankRates( lendingRates = Vector(Rate(0.06), Rate(0.10)), depositRates = Vector(Rate(0.04), Rate(0.04)), ) - val (_, agg, maybePbf) = + val (_, _, maybePbf) = Household.step(hhs, mkWorld(), PLN(8000.0), PLN(4666.0), 0.4, rng, nBanks = 2, bankRates = Some(br)) - val pbf = maybePbf.get + val pbf = maybePbf.get // Expected debt service: debt * (HhBaseAmortRate + lendingRate/12) - val expectedDs0 = debt.toDouble * (p.household.baseAmortRate.toDouble + 0.06 / 12.0) - val expectedDs1 = debt.toDouble * (p.household.baseAmortRate.toDouble + 0.10 / 12.0) + val expectedDs0 = debt.toDouble * (p.household.baseAmortRate.toDouble + 0.06 / 12.0) + val expectedDs1 = debt.toDouble * (p.household.baseAmortRate.toDouble + 0.10 / 12.0) pbf(0).debtService shouldBe PLN(expectedDs0) +- PLN(0.01) pbf(1).debtService shouldBe PLN(expectedDs1) +- PLN(0.01) // Bank 1's higher rate should mean higher debt service @@ -271,25 +271,25 @@ class HouseholdSpec extends AnyFlatSpec with Matchers: // --- Immigration: remittance deduction --- "Household.step" should "not deduct remittances from non-immigrant HH" in { - val rng = new Random(42) - val wage = 8000.0 - val hhs = Vector( + val rng = new Random(42) + val wage = 8000.0 + val hhs = Vector( mkHousehold(0, HhStatus.Employed(FirmId(0), SectorIdx(2), PLN(wage)), savings = PLN(50000.0)) .copy(isImmigrant = false), ) - val (updated, agg, _) = Household.step(hhs, mkWorld(), PLN(wage), PLN(4666.0), 0.4, rng) + val (_, agg, _) = Household.step(hhs, mkWorld(), PLN(wage), PLN(4666.0), 0.4, rng) agg.totalRemittances shouldBe PLN.Zero } it should "not deduct remittances from immigrant HH when disabled" in { - val rng = new Random(42) - val wage = 8000.0 - val hhs = Vector( + val rng = new Random(42) + val wage = 8000.0 + val hhs = Vector( mkHousehold(0, HhStatus.Employed(FirmId(0), SectorIdx(2), PLN(wage)), savings = PLN(50000.0)) .copy(isImmigrant = true), ) // ImmigEnabled is false by default → no remittance deduction - val (updated, agg, _) = Household.step(hhs, mkWorld(), PLN(wage), PLN(4666.0), 0.4, rng) + val (_, agg, _) = Household.step(hhs, mkWorld(), PLN(wage), PLN(4666.0), 0.4, rng) agg.totalRemittances shouldBe PLN.Zero } diff --git a/src/test/scala/sfc/engine/HousingMarketPropertySpec.scala b/src/test/scala/sfc/engine/HousingMarketPropertySpec.scala index ebde16c..881e13b 100644 --- a/src/test/scala/sfc/engine/HousingMarketPropertySpec.scala +++ b/src/test/scala/sfc/engine/HousingMarketPropertySpec.scala @@ -35,6 +35,7 @@ class HousingMarketPropertySpec extends AnyFlatSpec with Matchers with ScalaChec PLN.Zero, ) + @annotation.nowarn("msg=unused private member") // defaults used by callers private def mkFlows( interest: Double = 0.0, principal: Double = 0.0, diff --git a/src/test/scala/sfc/engine/HousingMarketSpec.scala b/src/test/scala/sfc/engine/HousingMarketSpec.scala index de5efd5..5fc21da 100644 --- a/src/test/scala/sfc/engine/HousingMarketSpec.scala +++ b/src/test/scala/sfc/engine/HousingMarketSpec.scala @@ -35,6 +35,7 @@ class HousingMarketSpec extends AnyFlatSpec with Matchers: ): HousingMarket.StepInput = HousingMarket.StepInput(prev, Rate(mortgageRate), Rate(inflation), Rate(incomeGrowth), employed, Rate(prevMortgageRate)) + @annotation.nowarn("msg=unused private member") // defaults used by callers private def mkFlows( interest: Double = 0.0, principal: Double = 0.0, diff --git a/src/test/scala/sfc/engine/IntermediateMarketSpec.scala b/src/test/scala/sfc/engine/IntermediateMarketSpec.scala index 5d72bcc..5e3cdc9 100644 --- a/src/test/scala/sfc/engine/IntermediateMarketSpec.scala +++ b/src/test/scala/sfc/engine/IntermediateMarketSpec.scala @@ -52,6 +52,7 @@ class IntermediateMarketSpec extends AnyFlatSpec with Matchers: greenCapital = PLN.Zero, ) + @annotation.nowarn("msg=unused private member") // default used by callers private def makeFirmsAllSectors(perSector: Int = 10): Vector[Firm.State] = (0 until 6).flatMap { s => (0 until perSector).map(i => makeFirm(s * perSector + i, s)) diff --git a/src/test/scala/sfc/engine/KnfBfgSpec.scala b/src/test/scala/sfc/engine/KnfBfgSpec.scala index 86f51b9..e9ea746 100644 --- a/src/test/scala/sfc/engine/KnfBfgSpec.scala +++ b/src/test/scala/sfc/engine/KnfBfgSpec.scala @@ -15,6 +15,7 @@ class KnfBfgSpec extends AnyFlatSpec with Matchers: given SimParams = SimParams.defaults private val p: SimParams = summon[SimParams] + @annotation.nowarn("msg=unused private member") // defaults used by callers private def mkBank( id: Int = 0, deposits: PLN = PLN(1e6), diff --git a/src/test/scala/sfc/engine/OpenEconomyPropertySpec.scala b/src/test/scala/sfc/engine/OpenEconomyPropertySpec.scala index 29b42d3..381aea8 100644 --- a/src/test/scala/sfc/engine/OpenEconomyPropertySpec.scala +++ b/src/test/scala/sfc/engine/OpenEconomyPropertySpec.scala @@ -19,6 +19,7 @@ class OpenEconomyPropertySpec extends AnyFlatSpec with Matchers with ScalaCheckP private val defaultSectorOutputs = Vector.fill(6)(PLN(1e8)) + @annotation.nowarn("msg=unused private member") // default used by callers private def makeForex(er: Double = p.forex.baseExRate): OpenEconomy.ForexState = OpenEconomy.ForexState(er, PLN(1e8), PLN(1e8), PLN.Zero, PLN(1e7)) diff --git a/src/test/scala/sfc/engine/PhysicalCapitalSpec.scala b/src/test/scala/sfc/engine/PhysicalCapitalSpec.scala index 47985cf..29237f0 100644 --- a/src/test/scala/sfc/engine/PhysicalCapitalSpec.scala +++ b/src/test/scala/sfc/engine/PhysicalCapitalSpec.scala @@ -11,6 +11,7 @@ class PhysicalCapitalSpec extends AnyFlatSpec with Matchers: given SimParams = SimParams.defaults private val p: SimParams = summon[SimParams] + @annotation.nowarn("msg=unused private member") // defaults used by callers private def mkFirm( sector: Int = 1, workers: Int = 10,