How to apply OOP to real world examples without putting all logic in Manager classes?

I’m lately trying to implement a specific problem using an object-oriented approach. I get the main principles and its advantages, but I fail to apply it to a real world problem. Most examples one could find consist of Cats or Dogs being implementations of Animal. These however don’t give me enough understanding how to model below problem regarding another frequent example: a school administration system.

Imagine a school having Students, Courses, Professors, and Notes. My implementation would be something like this:

class Person {     string name;     int age;      Person(string name, int age) {         this.name = name;         this.age = age;     } }  class Student extends Person {     double gpa;      Student(string name, int age) {         super(name, age);     } }  class Professor extends Person {     string roomNumber;      Professor(string name, int age, string roomNumber) {         super(name, age);         this.roomNumber = roomNumber;     } }  class Course {     string name;     Professor professor;     Students[] student;      Course(string name, Professor professor) {         this.name = name;         this.professor = professor;         this.students = new Student[];     }      void enrolStudent(Student student) {         students.add(student);     } }  class Note {     Course course;     Student student;     double value;      Note(Course course, Student student, double value) {         this.course = course;         this.student = student;         this.value = value;     } } 

Now the Student has a bunch of Notes and we want to calculate its GPA. This could be either straightforward averaging its Notes‘ values or more complex logic using weights and/or ignoring optional courses.

Now my question is: where do we put this logic? Ideally I would have a function double calculateGpa() on Student so you could call student.calculateGpa(), but having this logic on Student would break the SRP in my view. It also does not belong to any other class listed here. A class called GpaCalculator or NotesManager would be another guess but that seems to me too much like moving all the logic away from the domain and into classes that do not represent a real object but just actions (see also this answer).

If that would be the way to go here, why wouldn’t I then just write a pure, static, stateless function in a class called NotesHelper? Creating a manager class to just have one function double calculate(), and using its instance instead of a static function feels to me like making it look like OOP while it isn’t really. I feel like there should be a better approach, probably one I didn’t think of, or maybe I am wrong here. Could you guys give me some pointers?

Thanks!