car info: function that sorts all car info into tiers
diff --git a/scripts/count_cars.py b/scripts/count_cars.py
index 46d849946188aa..25bad2c9b4debe 100755
--- a/scripts/count_cars.py
+++ b/scripts/count_cars.py
@@ -2,12 +2,10 @@
 from collections import Counter
 from pprint import pprint
 
-from selfdrive.car.docs import get_tier_car_info
+from selfdrive.car.docs import get_all_car_info
 
 if __name__ == "__main__":
-  tiers = get_tier_car_info()
-  cars = [car for tier_cars in tiers.values() for car in tier_cars]
-
+  cars = get_all_car_info()
   make_count = Counter(l.make for l in cars)
   print("\n", "*" * 20, len(cars), "total", "*" * 20, "\n")
   pprint(make_count)
diff --git a/selfdrive/car/docs.py b/selfdrive/car/docs.py
index a39e8df805699e..64d4dbfef4ba5e 100755
--- a/selfdrive/car/docs.py
+++ b/selfdrive/car/docs.py
@@ -26,9 +26,8 @@ def get_all_footnotes() -> Dict[Enum, int]:
 CARS_MD_TEMPLATE = os.path.join(BASEDIR, "selfdrive", "car", "CARS_template.md")
 
 
-def get_tier_car_info() -> Dict[Tier, List[CarInfo]]:
-  tier_car_info: Dict[Tier, List[CarInfo]] = {tier: [] for tier in Tier}
-
+def get_all_car_info() -> List[CarInfo]:
+  all_car_info: List[CarInfo] = []
   for models in get_interface_attr("CAR_INFO").values():
     for model, car_info in models.items():
       # Hyundai exception: those with radar have openpilot longitudinal
@@ -43,8 +42,16 @@ def get_tier_car_info() -> Dict[Tier, List[CarInfo]]:
         car_info = (car_info,)
 
       for _car_info in car_info:
-        _car_info.init(CP, non_tested_cars, ALL_FOOTNOTES)
-        tier_car_info[_car_info.tier].append(_car_info)
+        all_car_info.append(_car_info.init(CP, non_tested_cars, ALL_FOOTNOTES))
+
+  # Sort cars by make and model + year
+  return natsorted(all_car_info, key=lambda car: (car.make + car.model).lower())
+
+
+def sort_by_tier(all_car_info: List[CarInfo]) -> Dict[Tier, List[CarInfo]]:
+  tier_car_info: Dict[Tier, List[CarInfo]] = {tier: [] for tier in Tier}
+  for car_info in all_car_info:
+    tier_car_info[car_info.tier].append(car_info)
 
   # Sort cars by make and model + year
   for tier, cars in tier_car_info.items():
@@ -53,12 +60,12 @@ def get_tier_car_info() -> Dict[Tier, List[CarInfo]]:
   return tier_car_info
 
 
-def generate_cars_md(tier_car_info: Dict[Tier, List[CarInfo]], template_fn: str) -> str:
+def generate_cars_md(all_car_info: List[CarInfo], template_fn: str) -> str:
   with open(template_fn, "r") as f:
     template = jinja2.Template(f.read(), trim_blocks=True, lstrip_blocks=True)
 
   footnotes = [fn.value.text for fn in ALL_FOOTNOTES]
-  return template.render(tiers=tier_car_info, footnotes=footnotes, Star=Star, Column=Column)
+  return template.render(tiers=sort_by_tier(all_car_info), footnotes=footnotes, Star=Star, Column=Column)
 
 
 if __name__ == "__main__":
@@ -70,5 +77,5 @@ def generate_cars_md(tier_car_info: Dict[Tier, List[CarInfo]], template_fn: str)
   args = parser.parse_args()
 
   with open(args.out, 'w') as f:
-    f.write(generate_cars_md(get_tier_car_info(), args.template))
+    f.write(generate_cars_md(get_all_car_info(), args.template))
   print(f"Generated and written to {args.out}")
diff --git a/selfdrive/car/docs_definitions.py b/selfdrive/car/docs_definitions.py
index e406422981a87e..1ca2205ce5e5e6 100644
--- a/selfdrive/car/docs_definitions.py
+++ b/selfdrive/car/docs_definitions.py
@@ -91,6 +91,7 @@ def init(self, CP: car.CarParams, non_tested_cars: List[str], all_footnotes: Dic
         self.row[column] = footnote.value.star
 
     self.tier = {5: Tier.GOLD, 4: Tier.SILVER}.get(list(self.row.values()).count(Star.FULL), Tier.BRONZE)
+    return self
 
   @no_type_check
   def get_column(self, column: Column, star_icon: str, footnote_tag: str) -> str:
diff --git a/selfdrive/car/tests/test_docs.py b/selfdrive/car/tests/test_docs.py
index 6c7ae1fdacd457..1530eb18af3714 100755
--- a/selfdrive/car/tests/test_docs.py
+++ b/selfdrive/car/tests/test_docs.py
@@ -1,15 +1,15 @@
 #!/usr/bin/env python3
 import unittest
 
-from selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_tier_car_info
+from selfdrive.car.docs import CARS_MD_OUT, CARS_MD_TEMPLATE, generate_cars_md, get_all_car_info
 
 
 class TestCarDocs(unittest.TestCase):
   def setUp(self):
-    self.tier_cars = get_tier_car_info()
+    self.all_cars = get_all_car_info()
 
   def test_generator(self):
-    generated_cars_md = generate_cars_md(self.tier_cars, CARS_MD_TEMPLATE)
+    generated_cars_md = generate_cars_md(self.all_cars, CARS_MD_TEMPLATE)
     with open(CARS_MD_OUT, "r") as f:
       current_cars_md = f.read()
 
@@ -18,15 +18,14 @@ def test_generator(self):
 
   def test_naming_conventions(self):
     # Asserts market-standard car naming conventions by make
-    for cars in self.tier_cars.values():
-      for car in cars:
-        if car.car_name == "hyundai":
-          tokens = car.model.lower().split(" ")
-          self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`")
-          self.assertNotIn("hev", tokens, "Use `Hybrid`")
-          self.assertNotIn("ev", tokens, "Use `Electric`")
-          if "plug-in hybrid" in car.model.lower():
-            self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization")
+    for car in self.all_cars:
+      if car.car_name == "hyundai":
+        tokens = car.model.lower().split(" ")
+        self.assertNotIn("phev", tokens, "Use `Plug-in Hybrid`")
+        self.assertNotIn("hev", tokens, "Use `Hybrid`")
+        self.assertNotIn("ev", tokens, "Use `Electric`")
+        if "plug-in hybrid" in car.model.lower():
+          self.assertIn("Plug-in Hybrid", car.model, "Use correct capitalization")
 
 
 if __name__ == "__main__":

One function returns all car info in a list, sorted by make and model, another sorts that into tiers and make and model.

So we can pass this into the website template for harness information without it being in tiers and get correct natural sorting