diff --git a/compendium_v2/db/model.py b/compendium_v2/db/model.py
index ec029b99f534331932f1d5b1bded7f5e92451486..676727437984fcf4b0160d91060dfcc343c376c8 100644
--- a/compendium_v2/db/model.py
+++ b/compendium_v2/db/model.py
@@ -1,11 +1,14 @@
-import enum
 import logging
-from typing import Any
+from decimal import Decimal
+from enum import Enum
+from typing import Optional
+from typing_extensions import Annotated
 
-from sqlalchemy import Column, Enum, Integer, MetaData, Numeric, String
-from sqlalchemy.orm import relationship, declarative_base
+from sqlalchemy import MetaData, String
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
 from sqlalchemy.schema import ForeignKey
 
+
 logger = logging.getLogger(__name__)
 
 convention = {
@@ -18,37 +21,45 @@ convention = {
 
 metadata_obj = MetaData(naming_convention=convention)
 
-# https://github.com/python/mypy/issues/2477
-base_schema: Any = declarative_base(metadata=metadata_obj)
+str128 = Annotated[str, 128]
+int_pk = Annotated[int, mapped_column(primary_key=True)]
+int_pk_fkNREN = Annotated[int, mapped_column(ForeignKey("nren.id"), primary_key=True)]
+
+
+class Base(DeclarativeBase):
+    metadata = metadata_obj
+    type_annotation_map = {
+        str128: String(128),
+    }
 
 
-class NREN(base_schema):
+class NREN(Base):
     __tablename__ = 'nren'
-    id = Column(Integer, primary_key=True)
-    name = Column(String(128), nullable=False)
+    id: Mapped[int_pk]
+    name: Mapped[str128]
 
 
-class BudgetEntry(base_schema):
+class BudgetEntry(Base):
     __tablename__ = 'budgets'
-    nren_id = Column(Integer, ForeignKey(NREN.id), primary_key=True)
-    nren = relationship(NREN, lazy='joined')
-    year = Column(Integer, primary_key=True)
-    budget = Column(Numeric(asdecimal=False), nullable=False)
+    nren_id: Mapped[int_pk_fkNREN]
+    nren: Mapped[NREN] = relationship(lazy='joined')
+    year: Mapped[int_pk]
+    budget: Mapped[Decimal]
 
 
-class FundingSource(base_schema):
+class FundingSource(Base):
     __tablename__ = 'funding_source'
-    nren_id = Column(Integer, ForeignKey(NREN.id), primary_key=True)
-    nren = relationship(NREN, lazy='joined')
-    year = Column(Integer, primary_key=True)
-    client_institutions = Column(Numeric(asdecimal=False), nullable=False)
-    european_funding = Column(Numeric(asdecimal=False), nullable=False)
-    gov_public_bodies = Column(Numeric(asdecimal=False), nullable=False)
-    commercial = Column(Numeric(asdecimal=False), nullable=False)
-    other = Column(Numeric(asdecimal=False), nullable=False)
+    nren_id: Mapped[int_pk_fkNREN]
+    nren: Mapped[NREN] = relationship(lazy='joined')
+    year: Mapped[int_pk]
+    client_institutions: Mapped[Decimal]
+    european_funding: Mapped[Decimal]
+    gov_public_bodies: Mapped[Decimal]
+    commercial: Mapped[Decimal]
+    other: Mapped[Decimal]
 
 
-class FeeType(enum.Enum):
+class FeeType(Enum):
     flat_fee = "flat_fee"
     usage_based_fee = "usage_based_fee"
     combination = "combination"
@@ -56,45 +67,45 @@ class FeeType(enum.Enum):
     other = "other"
 
 
-class ChargingStructure(base_schema):
+class ChargingStructure(Base):
     __tablename__ = 'charging_structure'
-    nren_id = Column(Integer, ForeignKey(NREN.id), primary_key=True)
-    nren = relationship(NREN, lazy='joined')
-    year = Column(Integer, primary_key=True)
-    fee_type = Column('fee_type', Enum(FeeType, name="fee_type"), nullable=True)
+    nren_id: Mapped[int_pk_fkNREN]
+    nren: Mapped[NREN] = relationship(lazy='joined')
+    year: Mapped[int_pk]
+    fee_type: Mapped[Optional[FeeType]]
 
 
-class NrenStaff(base_schema):
+class NrenStaff(Base):
     __tablename__ = 'nren_staff'
-    nren_id = Column(Integer, ForeignKey(NREN.id), primary_key=True)
-    nren = relationship(NREN, lazy='joined')
-    year = Column(Integer, primary_key=True)
-    permanent_fte = Column(Numeric(asdecimal=False), nullable=False)
-    subcontracted_fte = Column(Numeric(asdecimal=False), nullable=False)
-    technical_fte = Column(Numeric(asdecimal=False), nullable=False)
-    non_technical_fte = Column(Numeric(asdecimal=False), nullable=False)
+    nren_id: Mapped[int_pk_fkNREN]
+    nren: Mapped[NREN] = relationship(lazy='joined')
+    year: Mapped[int_pk]
+    permanent_fte: Mapped[Decimal]
+    subcontracted_fte: Mapped[Decimal]
+    technical_fte: Mapped[Decimal]
+    non_technical_fte: Mapped[Decimal]
 
 
-class ParentOrganization(base_schema):
+class ParentOrganization(Base):
     __tablename__ = 'parent_organization'
-    nren_id = Column(Integer, ForeignKey(NREN.id), primary_key=True)
-    nren = relationship(NREN, lazy='joined')
-    year = Column(Integer, primary_key=True)
-    organization = Column(String(128), nullable=False)
+    nren_id: Mapped[int_pk_fkNREN]
+    nren: Mapped[NREN] = relationship(lazy='joined')
+    year: Mapped[int_pk]
+    organization: Mapped[str128]
 
 
-class SubOrganization(base_schema):
+class SubOrganization(Base):
     __tablename__ = 'sub_organization'
-    nren_id = Column(Integer, ForeignKey(NREN.id), primary_key=True)
-    nren = relationship(NREN, lazy='joined')
-    year = Column(Integer, primary_key=True)
-    organization = Column(String(128), primary_key=True)
-    role = Column(String(128), nullable=False)
+    nren_id: Mapped[int_pk_fkNREN]
+    nren: Mapped[NREN] = relationship(lazy='joined')
+    year: Mapped[int_pk]
+    organization: Mapped[str128] = mapped_column(primary_key=True)
+    role: Mapped[str128]
 
 
-class ECProject(base_schema):
+class ECProject(Base):
     __tablename__ = 'ec_project'
-    nren_id = Column(Integer, ForeignKey(NREN.id), primary_key=True)
-    nren = relationship(NREN, lazy='joined')
-    year = Column(Integer, primary_key=True)
-    project = Column(String(256), primary_key=True)
+    nren_id: Mapped[int_pk_fkNREN]
+    nren: Mapped[NREN] = relationship(NREN, lazy='joined')
+    year: Mapped[int_pk]
+    project: Mapped[str] = mapped_column(String(256), primary_key=True)
diff --git a/compendium_v2/migrations/README b/compendium_v2/migrations/README
deleted file mode 100644
index 0e048441597444a7e2850d6d7c4ce15550f79bda..0000000000000000000000000000000000000000
--- a/compendium_v2/migrations/README
+++ /dev/null
@@ -1 +0,0 @@
-Single-database configuration for Flask.
diff --git a/compendium_v2/routes/funding.py b/compendium_v2/routes/funding.py
index 0e504355593efd1a37c2ee490af810a1732d71a5..ed0e26c8809b0b3f8187af5a8afe25ed2ec16010 100644
--- a/compendium_v2/routes/funding.py
+++ b/compendium_v2/routes/funding.py
@@ -62,7 +62,7 @@ def funding_source_view() -> Any:
     def _extract_data(entry: model.FundingSource):
         return {
             'NREN': entry.nren.name,
-            'YEAR': int(entry.year),
+            'YEAR': entry.year,
             'CLIENT_INSTITUTIONS': float(entry.client_institutions),
             'EUROPEAN_FUNDING': float(entry.european_funding),
             'GOV_PUBLIC_BODIES': float(entry.gov_public_bodies),
diff --git a/compendium_v2/routes/staff.py b/compendium_v2/routes/staff.py
index 0a57d57a87d0486d09d37d9f70fe1abc77d37d72..73e79c4836bb42e2959ae41a22c1b094946e0f29 100644
--- a/compendium_v2/routes/staff.py
+++ b/compendium_v2/routes/staff.py
@@ -61,10 +61,10 @@ def staff_view() -> Any:
         return {
             'nren': entry.nren.name,
             'year': entry.year,
-            'permanent_fte': entry.permanent_fte,
-            'subcontracted_fte': entry.subcontracted_fte,
-            'technical_fte': entry.technical_fte,
-            'non_technical_fte': entry.non_technical_fte
+            'permanent_fte': float(entry.permanent_fte),
+            'subcontracted_fte': float(entry.subcontracted_fte),
+            'technical_fte': float(entry.technical_fte),
+            'non_technical_fte': float(entry.non_technical_fte)
         }
 
     with db.session_scope() as session:
diff --git a/compendium_v2/survey_db/model.py b/compendium_v2/survey_db/model.py
index fc455f23a3024f9d6ef5259faacd6c813f7baaa7..a908aa201aaae8c194614ece4ab067aa1664a43b 100644
--- a/compendium_v2/survey_db/model.py
+++ b/compendium_v2/survey_db/model.py
@@ -1,29 +1,28 @@
 import logging
+from typing import List, Optional
 
-from typing import Any
-
-from sqlalchemy import Column, Integer, String
-from sqlalchemy.orm import declarative_base, relationship
+from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
 from sqlalchemy.schema import ForeignKey
 
 logger = logging.getLogger(__name__)
 
-# https://github.com/python/mypy/issues/2477
-base_schema: Any = declarative_base()
+
+class Base(DeclarativeBase):
+    pass
 
 
-class Budgets(base_schema):
+class Budgets(Base):
     __tablename__ = 'budgets'
-    id = Column(Integer, primary_key=True)
-    budget = Column(String)
-    year = Column(Integer)
-    country_code = Column('country_code', String, ForeignKey('nrens.country_code'))
-    nren = relationship('Nrens', back_populates='budgets')
+    id: Mapped[int] = mapped_column(primary_key=True)
+    budget: Mapped[Optional[str]]
+    year: Mapped[Optional[int]]
+    country_code: Mapped[Optional[str]] = mapped_column(ForeignKey('nrens.country_code'))
+    nren: Mapped[Optional['Nrens']] = relationship(back_populates='budgets')
 
 
-class Nrens(base_schema):
+class Nrens(Base):
     __tablename__ = 'nrens'
-    id = Column(Integer, primary_key=True)
-    abbreviation = Column(String)
-    country_code = Column(String)
-    budgets = relationship('Budgets', back_populates='nren')
+    id: Mapped[int] = mapped_column(primary_key=True)
+    abbreviation: Mapped[Optional[str]]
+    country_code: Mapped[Optional[str]]
+    budgets: Mapped[List['Budgets']] = relationship(back_populates='nren')
diff --git a/test/conftest.py b/test/conftest.py
index a2507e0fea6df752d96c351fa535762ab8831462..e92af2c972c3ee8e104ef7e6947954f44276834e 100644
--- a/test/conftest.py
+++ b/test/conftest.py
@@ -40,7 +40,7 @@ def mocked_survey_db(mocker):
         connect_args={'check_same_thread': False},
         poolclass=StaticPool,
         echo=False)
-    survey_model.base_schema.metadata.create_all(engine)
+    survey_model.Base.metadata.create_all(engine)
     mocker.patch(
         'compendium_v2.survey_db._SESSION_MAKER',
         sessionmaker(bind=engine))
@@ -57,7 +57,7 @@ def mocked_db(mocker):
         connect_args={'check_same_thread': False},
         poolclass=StaticPool,
         echo=False)
-    model.base_schema.metadata.create_all(engine)
+    model.Base.metadata.create_all(engine)
     mocker.patch(
         'compendium_v2.db._SESSION_MAKER',
         sessionmaker(bind=engine))