Skip to content
This repository was archived by the owner on Mar 12, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/core-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
val scala3Version = "3.5.2"
val scala3Version = "3.8.2"

lazy val root = project
.in(file("."))
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.10.6
sbt.version=1.11.6
1 change: 0 additions & 1 deletion src/main/scala/sfc/agents/Banking.scala
Original file line number Diff line number Diff line change
@@ -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.*
Expand Down
8 changes: 4 additions & 4 deletions src/main/scala/sfc/agents/Household.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/sfc/agents/Immigration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
20 changes: 10 additions & 10 deletions src/main/scala/sfc/agents/Nbfi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions src/main/scala/sfc/config/SimParams.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down
1 change: 0 additions & 1 deletion src/main/scala/sfc/engine/Simulation.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package sfc.engine
import sfc.accounting.*
import sfc.agents.*
import sfc.config.*
import sfc.types.*

import scala.util.Random

Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/sfc/engine/markets/IntermediateMarket.scala
Original file line number Diff line number Diff line change
@@ -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.*
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/sfc/engine/markets/LaborMarket.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/sfc/engine/mechanisms/Macroprudential.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion src/main/scala/sfc/montecarlo/McRunner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
8 changes: 3 additions & 5 deletions src/main/scala/sfc/types.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
1 change: 1 addition & 0 deletions src/test/scala/sfc/accounting/SfcSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions src/test/scala/sfc/agents/BankingSectorPropertySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
32 changes: 16 additions & 16 deletions src/test/scala/sfc/agents/HouseholdSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)),
Expand All @@ -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
Expand Down Expand Up @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions src/test/scala/sfc/engine/HousingMarketPropertySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/test/scala/sfc/engine/HousingMarketSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
1 change: 1 addition & 0 deletions src/test/scala/sfc/engine/IntermediateMarketSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
1 change: 1 addition & 0 deletions src/test/scala/sfc/engine/KnfBfgSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
1 change: 1 addition & 0 deletions src/test/scala/sfc/engine/OpenEconomyPropertySpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
1 change: 1 addition & 0 deletions src/test/scala/sfc/engine/PhysicalCapitalSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Loading