diff --git a/bin/generate_inventory_reports b/bin/generate_inventory_reports
index c0dfe0db54..3fff5d7d07 100755
--- a/bin/generate_inventory_reports
+++ b/bin/generate_inventory_reports
@@ -6,6 +6,7 @@ import sys
 bin_dir = os.path.split(__file__)[0]
 package_dir = os.path.join(bin_dir, "..")
 sys.path.append(os.path.abspath(package_dir))
+
 from core.scripts import GenerateInventoryReports
 
 GenerateInventoryReports().run()
diff --git a/core/scripts.py b/core/scripts.py
index ebb302e4ff..d9ba67ebac 100644
--- a/core/scripts.py
+++ b/core/scripts.py
@@ -26,7 +26,6 @@
 from core.lane import Lane
 from core.metadata_layer import TimestampData
 from core.model import (
-    Admin,
     BaseCoverageRecord,
     Collection,
     Contributor,
@@ -2770,31 +2769,6 @@ def suppress_work(self, library: Library, identifier: Identifier) -> None:
 class GenerateInventoryReports(Script):
     """Generate inventory reports from queued report tasks"""
 
-    HEADER = [
-        "title",
-        "author",
-        "isbn",
-        "other_identifier",
-        "language",
-        "genre",
-        "publisher",
-        "audience",
-        "format",
-        "library",
-        "collection",
-        "license_duration_in_days",
-        "license_expiration_date",
-        "initial_loan_count",
-        "consumed_loans",
-        "remaining_loans",
-        "allowed_concurrent_users",
-        "max_loan_duration_in_days",
-        "active_holds_for_library",
-        "active_loans_for_library",
-        "active_holds_for_collection",
-        "active_loans_for_collection",
-    ]
-
     DATA_SOURCES = [
         "Palace Marketplace",
         "BiblioBoard",
@@ -2817,12 +2791,6 @@ def parse_command_line(
         parser = cls.arg_parser(_db)
         return parser.parse_known_args(cmd_args)[0]
 
-    def load_admin(self, admin_id: int) -> Admin:
-        admin = self._db.query(Admin).filter(Admin.id == admin_id).first()
-        if not admin:
-            raise ValueError(f"Unknown Admin: id = {id}")
-        return admin
-
     def do_run(self, cmd_args: list[str] | None = None) -> None:
         parsed = self.parse_command_line(self._db, cmd_args=cmd_args)
 
@@ -2835,8 +2803,6 @@ def do_run(self, cmd_args: list[str] | None = None) -> None:
 
     def process_task(self, task: AsyncTask):
         data = InventoryReportTaskData(**task.data)
-
-        admin = self.load_admin(data.admin_id)
         files = []
         try:
             current_time = datetime.datetime.now()
@@ -2844,7 +2810,8 @@ def process_task(self, task: AsyncTask):
             attachments = {}
 
             for data_source_name in self.DATA_SOURCES:
-                prefix = f"palace-inventory-report-{data_source_name}-{date_str}"
+                formatted_ds_name = data_source_name.lower().replace(" ", "_")
+                prefix = f"palace-inventory-report-{formatted_ds_name}-{date_str}"
                 suffix = ".csv"
                 with tempfile.NamedTemporaryFile(
                     "w",
@@ -2888,75 +2855,75 @@ def generate_report(self, data_source_name: str, library_id: int, output_file):
         writer.writerows(rows)
 
     def inventory_report_query(self) -> str:
-        return """select lp.id as license_pool_id,
-           e.title,
-           e.author,
-           i.identifier,
-           e.language,
-           e.publisher,
-           e.medium as format,
-           ic.name collection_name,
-           DATE_PART('day', l.expires::date) - DATE_PART('day',lp.availability_time::date) as license_duration_days,
-           l.expires license_expiration_date,
-           l.checkouts_available initial_loan_count,
-           (l.checkouts_available-l.checkouts_left) consumed_loans,
-           l.checkouts_left remaining_loans,
-           l.terms_concurrency allowed_concurrent_users,
-           coalesce(lib_holds.active_hold_count, 0) library_active_hold_count,
-
-           coalesce(lib_loans.active_loan_count, 0) library_active_loan_count,
-           CASE WHEN collection_sharing.is_shared_collection THEN lp.patrons_in_hold_queue
-                ELSE -1
-           END shared_hold_queue,
-           CASE WHEN collection_sharing.is_shared_collection THEN lp.licenses_reserved
-                ELSE -1
-           END shared_hold_queue
-    from datasources d,
-         collections c,
-         integration_configurations ic,
-         integration_library_configurations il,
-         libraries lib,
-         editions e,
-         identifiers i,
-         (select ic.parent_id,
-                  count(ic.parent_id) > 1 is_shared_collection
-          from integration_library_configurations ic,
-               integration_configurations i,
-               collections c
-          where c.integration_configuration_id = i.id  and
-                i.id = ic.parent_id group by ic.parent_id) collection_sharing,
-         licensepools lp left outer join licenses l on lp.id = l.license_pool_id
-             left outer join (select h.license_pool_id,
-                                     p.library_id,
-                                     count(h.id) active_hold_count
-                              from holds h,
-                                   patrons p,
-                                   libraries l
-                              where p.id = h.patron_id and
-                                    p.library_id = l.id and
-                                    l.id = :library_id
-                              group by p.library_id, h.license_pool_id) lib_holds on lp.id = lib_holds.license_pool_id
-             left outer join (select ln.license_pool_id,
-                                     p.library_id,
-                                     count(ln.id) active_loan_count
-                              from loans ln,
-                                   patrons p,
-                                   libraries l
-                              where p.id = ln.patron_id and
-                                    p.library_id = l.id and
-                                    l.id = :library_id
-                              group by p.library_id, ln.license_pool_id) lib_loans on lp.id = lib_holds.license_pool_id
-    where lp.identifier_id = i.id and
-          e.primary_identifier_id = i.id and
-          d.id = e.data_source_id and
-          c.id = lp.collection_id and
-          c.integration_configuration_id = ic.id and
-          ic.id = il.parent_id and
-          ic.id = collection_sharing.parent_id and
-          il.library_id = lib.id and
-          d.name = :data_source_name  and
-          lib.id = :library_id
-     order by title, author
+        return """
+            select
+               e.title,
+               e.author,
+               i.identifier,
+               e.language,
+               e.publisher,
+               e.medium as format,
+               ic.name collection_name,
+               DATE_PART('day', l.expires::date) - DATE_PART('day',lp.availability_time::date) as license_duration_days,
+               l.expires license_expiration_date,
+               l.checkouts_available initial_loan_count,
+               (l.checkouts_available-l.checkouts_left) consumed_loans,
+               l.checkouts_left remaining_loans,
+               l.terms_concurrency allowed_concurrent_users,
+               coalesce(lib_holds.active_hold_count, 0) library_active_hold_count,
+               coalesce(lib_loans.active_loan_count, 0) library_active_loan_count,
+               CASE WHEN collection_sharing.is_shared_collection THEN lp.patrons_in_hold_queue
+                    ELSE -1
+               END shared_active_hold_count,
+               CASE WHEN collection_sharing.is_shared_collection THEN lp.licenses_reserved
+                    ELSE -1
+               END shared_active_loan_count
+        from datasources d,
+             collections c,
+             integration_configurations ic,
+             integration_library_configurations il,
+             libraries lib,
+             editions e,
+             identifiers i,
+             (select ic.parent_id,
+                      count(ic.parent_id) > 1 is_shared_collection
+              from integration_library_configurations ic,
+                   integration_configurations i,
+                   collections c
+              where c.integration_configuration_id = i.id  and
+                    i.id = ic.parent_id group by ic.parent_id) collection_sharing,
+             licensepools lp left outer join licenses l on lp.id = l.license_pool_id
+                 left outer join (select h.license_pool_id,
+                                         p.library_id,
+                                         count(h.id) active_hold_count
+                                  from holds h,
+                                       patrons p,
+                                       libraries l
+                                  where p.id = h.patron_id and
+                                        p.library_id = l.id and
+                                        l.id = :library_id
+                                  group by p.library_id, h.license_pool_id) lib_holds on lp.id = lib_holds.license_pool_id
+                 left outer join (select ln.license_pool_id,
+                                         p.library_id,
+                                         count(ln.id) active_loan_count
+                                  from loans ln,
+                                       patrons p,
+                                       libraries l
+                                  where p.id = ln.patron_id and
+                                        p.library_id = l.id and
+                                        l.id = :library_id
+                                  group by p.library_id, ln.license_pool_id) lib_loans on lp.id = lib_holds.license_pool_id
+        where lp.identifier_id = i.id and
+              e.primary_identifier_id = i.id and
+              d.id = e.data_source_id and
+              c.id = lp.collection_id and
+              c.integration_configuration_id = ic.id and
+              ic.id = il.parent_id and
+              ic.id = collection_sharing.parent_id and
+              il.library_id = lib.id and
+              d.name = :data_source_name  and
+              lib.id = :library_id
+         order by title, author
         """
 
 
diff --git a/tests/core/test_scripts.py b/tests/core/test_scripts.py
index bf9eb29cac..f550593dbd 100644
--- a/tests/core/test_scripts.py
+++ b/tests/core/test_scripts.py
@@ -1,8 +1,10 @@
 from __future__ import annotations
 
+import csv
 import datetime
 import json
 import random
+from dataclasses import asdict
 from io import StringIO
 from unittest.mock import MagicMock, call, create_autospec, patch
 
@@ -35,6 +37,12 @@
     get_one,
     get_one_or_create,
 )
+from core.model.asynctask import (
+    AsyncTaskStatus,
+    AsyncTaskType,
+    InventoryReportTaskData,
+    queue_task,
+)
 from core.model.classification import Classification, Subject
 from core.model.customlist import CustomList
 from core.model.devicetokens import DeviceToken, DeviceTokenTypes
@@ -52,6 +60,7 @@
     CustomListUpdateEntriesScript,
     DeleteInvisibleLanesScript,
     Explain,
+    GenerateInventoryReports,
     IdentifierInputScript,
     LaneSweeperScript,
     LibraryInputScript,
@@ -2554,6 +2563,135 @@ def test_suppress_work(self, db: DatabaseTransactionFixture):
         assert work.suppressed_for == [test_library]
 
 
+class TestGenerateInventoryReports:
+    def test_do_run(self, db: DatabaseTransactionFixture):
+        # create some test data
+        library = db.library(short_name="test")
+        collection = db.collection(
+            name="BiblioBoard Test collection", data_source_name="BiblioBoard"
+        )
+        collection.libraries = [library]
+        ds = collection.data_source
+        title = "Leaves of Grass"
+        author = "Walt Whitman"
+        email = "test@email.com"
+        checkouts_left = 10
+        checkouts_available = 11
+        terms_concurrency = 5
+        edition = db.edition(data_source_name=ds.name)
+        edition.title = title
+        edition.author = author
+        work = db.work(
+            language="eng",
+            fiction=True,
+            with_license_pool=False,
+            data_source_name=ds.name,
+            presentation_edition=edition,
+            collection=collection,
+        )
+        licensepool = db.licensepool(
+            edition=edition,
+            open_access=False,
+            data_source_name=ds.name,
+            set_edition_as_presentation=True,
+            collection=collection,
+        )
+
+        db.license(
+            pool=licensepool,
+            checkouts_available=checkouts_available,
+            checkouts_left=checkouts_left,
+            terms_concurrency=terms_concurrency,
+        )
+
+        data = InventoryReportTaskData(
+            admin_id=1, library_id=library.id, admin_email=email
+        )
+        task, is_new = queue_task(
+            db.session, task_type=AsyncTaskType.INVENTORY_REPORT, data=asdict(data)
+        )
+
+        assert task.status == AsyncTaskStatus.READY
+
+        script = GenerateInventoryReports(db.session)
+        send_email_mock = create_autospec(script.services.email.container.send_email)
+        script.services.email.container.send_email = send_email_mock
+        script.do_run()
+        send_email_mock.assert_called_once()
+        args, kwargs = send_email_mock.call_args
+        assert task.status == AsyncTaskStatus.SUCCESS
+        assert kwargs["receivers"] == [email]
+        assert kwargs["subject"].__contains__("Inventory Report")
+        attachments: dict = kwargs["attachments"]
+        csv_titles_strings = [
+            "biblioboard",
+            "palace_marketplace",
+            "unlimited_listens",
+            "palace_bookshelf",
+        ]
+
+        def at_least_one_contains_the_other(list1: [str], list2: [str]) -> bool:
+            for s1 in list1:
+                for s2 in list2:
+                    if s1.__contains__(s2):
+                        return True
+            return False
+
+        assert at_least_one_contains_the_other(attachments.keys(), csv_titles_strings)
+
+        for key in attachments.keys():
+            value = attachments[key]
+            assert len(value) > 0
+            if str(key).__contains__("biblioboard"):
+                csv_file = StringIO(value)
+                reader = csv.reader(csv_file, delimiter=",")
+                first_row = None
+                row_count = 0
+
+                for row in reader:
+                    row_count += 1
+                    if not first_row:
+                        first_row = row
+                        row_headers = [
+                            "title",
+                            "author",
+                            "identifier",
+                            "language",
+                            "publisher",
+                            "format",
+                            "collection_name",
+                            "license_duration_days",
+                            "license_expiration_date",
+                            "initial_loan_count",
+                            "consumed_loans",
+                            "remaining_loans",
+                            "allowed_concurrent_users",
+                            "library_active_hold_count",
+                            "library_active_loan_count",
+                            "shared_active_hold_count",
+                            "shared_active_loan_count",
+                        ]
+                        for h in row_headers:
+                            assert h in row
+                        continue
+
+                    assert row[first_row.index("title")] == title
+                    assert row[first_row.index("author")] == author
+                    assert row[first_row.index("shared_active_hold_count")] == "-1"
+                    assert row[first_row.index("shared_active_loan_count")] == "-1"
+                    assert row[first_row.index("initial_loan_count")] == str(
+                        checkouts_available
+                    )
+                    assert row[first_row.index("consumed_loans")] == str(
+                        checkouts_available - checkouts_left
+                    )
+                    assert row[first_row.index("allowed_concurrent_users")] == str(
+                        terms_concurrency
+                    )
+
+                assert row_count == 2
+
+
 class TestWorkConsolidationScript:
     """TODO"""