Changes between Version 7 and Version 8 of ERModel


Ignore:
Timestamp:
09/02/26 01:56:20 (5 days ago)
Author:
236024
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • ERModel

    v7 v8  
    1 = Entity-Relationship Model v.09 =
     1= Entity-Relationship Model v.10 =
    22== Diagram ==
    33
    4 [[Image(wiki:ERModel:ERModel_v09.png)]]
     4[[Image(wiki:ERModel:ERModel_v10.png)]]
    55== Data requirements ==
    66
     
    1111Students represents UKIM students known to the system, including external participants and students who later become BEST Skopje members. It is modeled as the root person entity because every member is also a student, but not every student is necessarily a member. The entity is needed so that applications, activity participation, and membership can all refer to the same student record.
    1212
    13  * '''Candidate keys:''' index is chosen as the primary key because the project is scoped to UKIM students and the student index uniquely identifies a student in this context.
    14  * '''Attributes:'''
    15    * index: numeric/text identifier, required, unique.
     13 * '''Candidate keys:''' student_index is chosen as the primary key because the project is scoped to UKIM students and the student index uniquely identifies a student in this context.
     14 * '''Attributes:'''
     15   * student_index: numeric identifier, required, unique.
    1616   * first_name: text, required.
    1717   * last_name: text, required.
     
    2727Members represents students who are part of BEST Skopje. It is modeled as a specialization of Students because member records inherit the student data, while membership-specific information is represented through membership stages, mentorship, responsibilities, and organizational roles. This avoids duplicating personal data while allowing the system to distinguish ordinary event participants from members.
    2828
    29  * '''Candidate keys:''' the primary key is inherited from Students, so index is used.
    30  * '''Attributes:''' no direct attributes in the ER model.
     29 * '''Candidate keys:''' the primary key is inherited from Students, so student_index is used.
    3130
    3231==== Observers ====
     
    3433Observers represents members in the observer/associate stage. It is modeled as a subtype of Members because observers are members with a specific membership status and possible future progression to young member.
    3534
    36  * '''Candidate keys:''' inherited index from Members.
    37  * '''Attributes:''' no direct attributes in the ER model.
     35 * '''Candidate keys:''' inherited student_index from Members.
    3836
    3937==== !YoungMembers ====
    4038
    41 !YoungMembers represents members in the young member stage. It is modeled as a subtype of Members because young members are members with additional membership-development meaning, especially mentorship by a full member and later possible progression to full member.
    42 
    43  * '''Candidate keys:''' inherited index from Members.
    44  * '''Attributes:''' no direct attributes in the ER model.
     39!YoungMembers represents members in the young member stage. It is modeled as a subtype of Members because young members are members with additional membership-development meaning, especially mentorship by a full member and later possible progression to full member.
     40
     41 * '''Candidate keys:''' inherited student_index from Members.
    4542
    4643==== !FullMembers ====
     
    4845!FullMembers represents members with full membership rights and responsibilities. It is modeled as a subtype of Members because full members can mentor young members and hold organizational functions, including Board and non-Board roles.
    4946
    50  * '''Candidate keys:''' inherited index from Members.
    51  * '''Attributes:''' no direct attributes in the ER model.
     47 * '''Candidate keys:''' inherited student_index from Members.
    5248
    5349==== Alumni ====
     
    5551Alumni represents former full members with alumni status. It is modeled as a subtype of Members because alumni are still relevant to BEST Skopje activities and history, but they are distinct from active observer, young, and full members. Their year of studies would be 0 if they've finished their studies.
    5652
    57  * '''Candidate keys:''' inherited index from Members.
    58  * '''Attributes:''' no direct attributes in the ER model.
     53 * '''Candidate keys:''' inherited student_index from Members.
    5954
    6055==== !MembershipStages ====
     
    6257!MembershipStages stores the possible membership stages used in membership history, such as observer, young member, full member, and alumni. It is modeled separately even though the member subtypes exist because the has_stage relationship needs to store historical periods with valid_from and valid_to.
    6358
    64  * '''Candidate keys:''' ms_name is chosen as the primary key because membership stage names are stable controlled values.
    65  * '''Attributes:'''
     59 * '''Candidate keys:''' ms_id and ms_name. ms_id is chosen as the primary key because membership stage names are text labels. ms_name is unique.
     60 * '''Attributes:'''
     61   * ms_id: numeric identifier, required, unique.
    6662   * ms_name: text, required, unique.
    6763
     
    7066!OrganizationalFunctions represents reusable organizational role types, such as President, Treasurer, Secretary, HR coordinator, FR coordinator, PR coordinator, IT coordinator, Design coordinator, Grants coordinator or Vivaldi coordinator. It is modeled separately from members because a function can be held by different full members in different mandates, and one full member can hold different functions over time.
    7167
    72  * '''Candidate keys:''' of_title is chosen as the primary key because function titles are stable role names within the organization.
    73  * '''Attributes:'''
     68 * '''Candidate keys:''' of_id and of_title. of_id is chosen as the primary key because function titles are text labels. of_title is unique.
     69 * '''Attributes:'''
     70   * of_id: numeric identifier, required, unique.
    7471   * of_title: text, required, unique.
    7572
     
    7875Board represents organizational functions that are Board roles. It is modeled as a subtype of !OrganizationalFunctions because Board membership is treated as a role/function, not as a separate membership stadium.
    7976
    80  * '''Candidate keys:''' inherited of_title from !OrganizationalFunctions.
    81  * '''Attributes:''' no direct attributes in the ER model.
     77 * '''Candidate keys:''' inherited of_id from !OrganizationalFunctions.
    8278
    8379==== !NonBoard ====
     
    8581!NonBoard represents organizational functions that are not Board roles, such as non-Board coordinators. It is modeled as a subtype of !OrganizationalFunctions so the model can distinguish Board and non-Board responsibilities while keeping both under the same function structure.
    8682
    87  * '''Candidate keys:''' inherited of_title from !OrganizationalFunctions.
    88  * '''Attributes:''' no direct attributes in the ER model.
     83 * '''Candidate keys:''' inherited of_id from !OrganizationalFunctions.
    8984
    9085==== !ActivityTypes ====
    9186
    92 !ActivityTypes represents abstract categories of internal activities, such as weekly meetings, BEST Course in Summer meetings, working groups, workshops, FR calls, trainings, full member meetings, Board meetings, parties, alumni parties, informative meetings, and assemblies. It is modeled separately from activity sessions because many concrete sessions can occur for the same activity type.
    93 
    94  * '''Candidate keys:''' at_name is chosen as the primary key because activity type names are controlled and unique.
    95  * '''Attributes:'''
     87!ActivityTypes represents abstract categories of internal activities, such as weekly meetings, BEST Course in Summer meetings, working groups, workshops, FR calls, trainings, full member meetings, Board meetings, parties, alumni parties, and informative meetings. It is modeled separately from activity sessions because many concrete sessions can occur for the same activity type.
     88
     89 * '''Candidate keys:''' at_id and at_name. at_id is chosen as the primary key because activity type names are text labels. at_name is unique.
     90 * '''Attributes:'''
     91   * at_id: numeric identifier, required, unique.
    9692   * at_name: text, required, unique.
    9793   * at_description: text, required, explanation of what the activity type is used for.
    9894
     95==== Locations ====
     96
     97Locations represents reusable places where event editions, event sessions, and activity sessions can happen.
     98
     99 * '''Candidate keys:''' l_id and l_name. l_id is chosen as the primary key because location names are text labels. l_name is unique.
     100 * '''Attributes:'''
     101   * l_id: numeric identifier, required, unique.
     102   * l_name: text, required, unique.
     103
    99104==== !ActivitySessions ====
    100105
    101 !ActivitySessions represents concrete occurrences of activity types. For example, one weekly meeting, one FR call, one IT workshop, or one training session is an activity session. It is modeled as a weak entity dependent on !ActivityTypes because a session is an instance of an abstract activity type and is identified in context by that type, start time, and location.
    102 
    103  * '''Candidate keys:''' the chosen primary key is the composite key at_name + as_start_time + as_location, where at_name comes from the owning !ActivityTypes entity and as_start_time and as_location are the partial keys.
    104  * '''Attributes:'''
    105    * as_start_time: datetime, required, valid future or past timestamp depending on whether the session is planned or completed.
    106    * as_location: text, required, room/place/online location.
     106!ActivitySessions represents concrete occurrences of activity types. For example, one weekly meeting, one FR call, one IT workshop, or one training session is an activity session.
     107
     108 * '''Candidate keys:''' as_id is chosen as the primary key because it keeps references to a concrete session simple.
     109 * '''Attributes:'''
     110   * as_id: numeric identifier, required, unique.
     111   * as_start_time: timestamp, required, valid future or past timestamp depending on whether the session is planned or completed.
    107112
    108113==== !EventTypes ====
     
    110115!EventTypes represents abstract event categories, such as Job Fair, BEST Course in Summer, Motivational Weekend, beBESTIE, BOMBA, Hackathon, BEST Skopje Birthday, or other event types. It is modeled separately from event editions because the same event type can be organized multiple times over different years or editions.
    111116
    112  * '''Candidate keys:''' et_name is chosen as the primary key because event type names are controlled and unique.
    113  * '''Attributes:'''
     117 * '''Candidate keys:''' et_id and et_name. et_id is chosen as the primary key because event type names are text labels. et_name is unique.
     118 * '''Attributes:'''
     119   * et_id: numeric identifier, required, unique.
    114120   * et_name: text, required, unique.
    115121   * et_description: text, required, explanation of the event type.
     
    117123==== !EventEditions ====
    118124
    119 !EventEditions represents one concrete edition of an event type, such as Job Fair 2026 or BEST Course in Summer 2026. It is modeled as a weak entity dependent on !EventTypes because an edition exists as an edition of an abstract event type, and the edition is identified in context by its event type, title, and start date.
    120 
    121  * '''Candidate keys:''' the chosen primary key is the composite key et_name + title + start, where et_name comes from !EventTypes and start and title are the partial keys.
    122  * '''Attributes:'''
    123    * start: date/datetime, required, valid event start date/time.
    124    * end: date/datetime, required, must be greater than or equal to start.
    125    * title: text, required, title of the event edition.
    126    * locations: multivalued text, required because the event has at least one known location; each value is a room/place/online location.
     125!EventEditions represents one concrete edition of an event type, such as Job Fair 2026 or BEST Course in Summer 2026.
     126
     127 * '''Candidate keys:''' ee_id is chosen as the primary key because titles and start times can change or repeat.
     128 * '''Attributes:'''
     129   * ee_id: numeric identifier, required, unique.
     130   * ee_start_time: timestamp, required, valid event start time.
     131   * ee_end_time: timestamp, required, must be greater than or equal to ee_start_time.
     132   * ee_title: text, required, title of the event edition.
    127133
    128134==== !EventSessionTypes ====
    129135
    130 !EventSessionTypes represents abstract types of sessions inside event editions, such as official opening, company stands, official closing, lecture timeslot, coffee break, city rally, or social activity. It is modeled separately because many event sessions across different events can be of the same type and may require a typical number of volunteers.
    131 
    132  * '''Candidate keys:''' est_name is chosen as the primary key because event session type names are controlled and unique.
    133  * '''Attributes:'''
     136!EventSessionTypes represents abstract types of sessions inside event editions, such as official opening, company stands, official closing, lecture timeslot, coffee break, city rally, or social activity. It is modeled separately because many event sessions across different events can be of the same type.
     137
     138 * '''Candidate keys:''' est_id and est_name. est_id is chosen as the primary key because event session type names are text labels. est_name is unique.
     139 * '''Attributes:'''
     140   * est_id: numeric identifier, required, unique.
    134141   * est_name: text, required, unique.
    135142   * est_description: text, required, explanation of the session type.
    136    * needed_no_volunteers: numeric, required, non-negative integer representing the usual needed number of volunteers.
    137143
    138144==== !EventSessions ====
    139145
    140 !EventSessions represents concrete sessions within an event edition, such as one official opening, one company stand period, one coffee break, or one lecture slot. It is modeled as a weak entity dependent on !EventEditions and !EventSessionTypes, because an event session exists only as part of a specific event edition and it is an instance of an event session type.
    141 
    142  * '''Candidate keys:''' the chosen primary key is the composite key et_name + title + start + est_name + es_start_time + location.
    143  * '''Attributes:'''
    144    * es_start_time: datetime, required, valid timestamp within or near the event edition period.
    145    * location: text, required, room/place/online location.
     146!EventSessions represents concrete sessions within an event edition, such as one official opening, one company stand period, one coffee break, or one lecture slot. It is modeled as a weak entity dependent on !EventEditions, because an event session exists only as part of a specific event edition.
     147
     148 * '''Candidate keys:''' the chosen primary key is the event edition key + es_no, where es_no is the partial key inside one event edition.
     149 * '''Attributes:'''
     150   * es_no: numeric, required, unique inside one event edition.
     151   * es_start_time: timestamp, required, valid timestamp within or near the event edition period.
    146152   * es_title: text, required, title of the event session.
    147 
    148 ==== !CoreTeamFunctions ====
    149 
    150 !CoreTeamFunctions represents available organizing-team functions for a specific event edition, such as Main Organiser, PR, FR, Logistics, Design, IT, HR, or another event-specific function. It is modeled as a weak entity dependent on !EventEditions because available functions are defined per event edition and may differ between event editions.
    151 
    152  * '''Candidate keys:''' the chosen primary key is the composite key !EventEditions key + function_name.
    153  * '''Attributes:'''
    154    * function_name: text, required, function name unique within the same event edition.
     153   * needed_no_volunteers: numeric, required, non-negative integer.
     154
     155==== !EventFunctions ====
     156
     157!EventFunctions represents available organizing functions for a specific event edition, such as Main Organiser, PR, FR, Logistics, Design, IT, HR, or another event-specific function. It is modeled as a weak entity dependent on !EventEditions because available functions are defined per event edition and may differ between event editions.
     158
     159 * '''Candidate keys:''' the chosen primary key is the event edition key + ef_no, where ef_no is the partial key inside one event edition.
     160 * '''Attributes:'''
     161   * ef_no: numeric, required, unique inside one event edition.
     162   * ef_name: text, required.
    155163
    156164==== Applications ====
    157165
    158 Applications represents applications submitted by students, with motivation letters, for BEST Skopje processes such as applying for a core team function, applying for participation connected to an event edition, applying to become a young member through BOMBA, or applying to become a mentor. It is modeled as a separate entity because applications have their own status and content and must be connected both to the applicant and to the event/function context.
    159 
    160  * '''Candidate keys:''' application_id is chosen as the primary key because one student can submit multiple applications over time and natural combinations would be less stable. 
     166Applications represents applications submitted by students, with motivation letters, for BEST Skopje processes such as applying for an event function, applying for participation connected to an event edition, applying to become a young member through BOMBA, or applying to become a mentor. It is modeled as a separate entity because applications have their own status and content and must be connected both to the applicant and to the event/function context.
     167
     168 * '''Candidate keys:''' application_id is chosen as the primary key because one student can submit multiple applications over time and natural combinations would be less stable.
    161169 * '''Attributes:'''
    162170   * application_id: numeric identifier, required, unique.
    163171   * motivational_letter: long text, required.
    164    * application_type: text, required, controlled value such as core team function, event participation, BOMBA young member, or mentor application.
     172   * application_type: text, required, controlled value such as event function, event participation, BOMBA young member, or mentor application.
    165173   * application_status: text, required, controlled value such as submitted, accepted, rejected, or withdrawn.
    166174
     
    181189This specialization represents that every member is also a student, while not every student in the system must be a member. It is needed because external students can participate or apply for some activities/events, while members have additional BEST Skopje membership history, responsibilities, and roles.
    182190
    183  * '''Candidate keys:''' inherited from the supertype.
    184  * '''Attributes:''' no relationship attributes.
    185 
    186191==== Members Membership-Type Specialization ====
    187192
    188193This disjoint specialization separates Members into Observers, !YoungMembers, !FullMembers, and Alumni. It is needed to show the main membership stadiums clearly in the conceptual ER model. The historical periods of these stages are still stored through has_stage.
    189194
    190  * '''Candidate keys:''' inherited from the supertype.
    191  * '''Attributes:''' no relationship attributes.
    192 
    193195==== !OrganizationalFunctions Board/!NonBoard Specialization ====
    194196
    195197This disjoint specialization separates organizational functions into Board and !NonBoard roles. It is needed because Board membership is treated as a function/role rather than as a membership stage, but the model still needs to distinguish Board functions from other coordinator functions.
    196198
    197  * '''Candidate keys:''' inherited from the supertype.
    198  * '''Attributes:''' no relationship attributes.
    199 
    200199==== applies_with ====
    201200
    202201applies_with connects Students to Applications. It represents the fact that each application is submitted by exactly one student, while one student may submit many applications. It is needed to know who wrote the motivation letter and whose application status is being tracked.
    203202
    204  * '''Candidate keys:''' it is stored as a foreign key from Applications to Students.
    205  * '''Attributes:''' no relationship attributes.
    206 
    207203==== for_function ====
    208204
    209 for_function connects Applications to !CoreTeamFunctions. It represents applications that target a specific available core team function for an event edition. It is needed so that an accepted application can imply that the student became part of the core team in that function.
    210 
    211  * '''Candidate keys:''' application_id is sufficient for this relationship since each function application targets at most one core team function.
    212  * '''Attributes:''' no relationship attributes.
     205for_function connects Applications to !EventFunctions. It represents applications that target a specific available event function for an event edition. It is needed so that an accepted application can imply that the student became part of the event organization in that function.
    213206
    214207==== for_participation ====
     
    216209for_participation connects Applications to !EventEditions. It represents applications for participation in an !EventEdition, such as participant of a Hackathon, as well as applications from full members to become a mentor and observers to become a young member in BOMBA.
    217210
    218  * '''Candidate keys:''' application_id is sufficient because each application targets one event edition.
    219  * '''Attributes:''' no relationship attributes.
    220 
    221211==== available_for ====
    222212
    223 available_for connects !CoreTeamFunctions to !EventEditions. It is the identifying relationship that states which functions are available for a specific event edition. It is needed because the same function name can appear in many event editions, but the available role belongs to one concrete edition.
    224 
    225  * '''Candidate keys:''' the relationship contributes the !EventEditions key to the weak entity key of !CoreTeamFunctions.
    226  * '''Attributes:''' no relationship attributes.
     213available_for connects !EventFunctions to !EventEditions. It is the identifying relationship that states which functions are available for a specific event edition. It is needed because the same function name can appear in many event editions, but the available role belongs to one concrete edition.
     214
     215 * '''Candidate keys:''' the relationship identifies !EventFunctions through event edition key + ef_no.
    227216
    228217==== edition_of ====
     
    230219edition_of connects !EventEditions to !EventTypes. It represents that each concrete event edition is an edition of exactly one abstract event type. It is needed to distinguish event categories from concrete yearly/dated editions.
    231220
    232  * '''Candidate keys:''' the relationship contributes the !EventTypes key to the weak entity key of !EventEditions.
    233  * '''Attributes:''' no relationship attributes.
    234 
    235221==== part_of ====
    236222
    237223part_of connects !EventSessions to !EventEditions. It represents that every event session belongs to one event edition, and an event edition is composed of event sessions. It is modeled as identifying because event sessions do not exist independently of the event edition they are part of.
    238224
    239  * '''Candidate keys:''' the relationship contributes the event edition key to the weak entity key of !EventSessions.
    240  * '''Attributes:''' no relationship attributes.
     225 * '''Candidate keys:''' the relationship identifies !EventSessions through event edition key + es_no.
    241226
    242227==== session_inst_of ====
     
    244229session_inst_of connects !EventSessions to !EventSessionTypes. It represents that a concrete event session is an instance of an abstract event session type, such as official opening or coffee break. It is needed so that sessions can be categorized consistently without duplicating type descriptions.
    245230
    246  * '''Candidate keys:''' the relationship contributes the !EventSessionTypes key to the weak entity key of !EventSessions.
    247  * '''Attributes:''' no relationship attributes.
    248 
    249231==== instance_of ====
    250232
    251 instance_of connects !ActivitySessions to !ActivityTypes. It represents that a concrete activity session is an instance of an abstract activity type, such as a weekly meeting, working group, FR call, training, or party. It is modeled as identifying because an activity session is defined through its type, start time, and location.
    252 
    253  * '''Candidate keys:''' the relationship contributes !ActivityTypes key to the weak entity key of !ActivitySessions.
    254  * '''Attributes:''' no relationship attributes.
     233instance_of connects !ActivitySessions to !ActivityTypes. It represents that a concrete activity session is an instance of an abstract activity type, such as a weekly meeting, working group, FR call, training, or party.
    255234
    256235==== related_to ====
    257236
    258 related_to connects !ActivitySessions to !EventEditions. It represents activity sessions that are connected to a specific event edition, such as BEST Course in Summer meetings, FR calls for a Hackathon, or working sessions for an event. It is optional because many activity sessions, such as regular weekly meetings, may not be related to a specific event edition.
     237related_to connects !ActivitySessions to !EventEditions. It represents activity sessions that are connected to a specific event edition, such as BEST Course in Summer meetings, FR calls for a specific event, or working sessions for an event. It is optional because many activity sessions, such as regular weekly meetings, may not be related to a specific event edition.
    259238
    260239 * '''Candidate keys:''' the natural key is the activity session key because each activity session is related to at most one event edition in the current model.
    261  * '''Attributes:''' no relationship attributes.
     240
     241==== takes_place_at ====
     242
     243takes_place_at connects !ActivitySessions to Locations. It represents the location where a concrete activity session is held.
     244
     245==== hosted_at ====
     246
     247hosted_at connects !EventEditions to Locations. It represents the one or more locations connected to an event edition.
     248
     249 * '''Candidate keys:''' event edition key + location key.
     250
     251==== held_at ====
     252
     253held_at connects !EventSessions to Locations. It represents the location where a concrete event session is held.
    262254
    263255==== participation ====
     
    265257participation connects Students to !ActivitySessions. It represents RSVP and attendance checking for activity sessions, such as meetings, workshops, trainings, FR calls, parties, or other internal activities. It is needed because students or members can say they are going and the organization can later record whether they attended.
    266258
    267  * '''Candidate keys:''' index + activity session key is chosen as the primary key because one student should have at most one participation record for the same activity session.
    268  * '''Attributes:'''
    269    * rsvp_status: text, required, controlled value such as going, not going, maybe, or default = no response.
    270    * rsvp_at: datetime, optional until an RSVP is submitted; required when an RSVP is recorded.
     259 * '''Candidate keys:''' student_index + activity session key is chosen as the primary key because one student should have at most one participation record for the same activity session.
     260 * '''Attributes:'''
     261   * rsvp_status: text, required, controlled value such as going, not going, maybe, or deault = no response.
     262   * rsvp_at: timestamp, optional until an RSVP is submitted; required when an RSVP is recorded.
    271263   * attendance_status: text, required when attendance is checked, controlled value such as present, absent, excused, late, online, left early or not checked.
    272264
     
    275267announces_absence connects Members to !ActivitySessions. It represents members announcing that they will be absent from an activity session, with a reason and time of announcement. It is needed for processes where absence from mandatory or important activities should be communicated before the session.
    276268
    277  * '''Candidate keys:''' member index + activity session key is the natural primary key because a member can have only one active absence announcement per session.
     269 * '''Candidate keys:''' member student_index + activity session key is the natural primary key if a member can have only one active absence announcement per session.
    278270 * '''Attributes:'''
    279271   * reason: text, required, explanation of the absence.
    280    * announced_at: datetime, required, timestamp when the absence was announced.
     272   * announced_at: timestamp, required, timestamp when the absence was announced.
    281273
    282274==== responsible_for ====
    283275
    284 responsible_for connects Members to !ActivitySessions. It represents members responsible for organizing or holding an activity session, such as the President holding weekly meetings, HR coordinating training sessions, or event organizers/core team members coordinating event-related activity sessions.
    285 
    286  * '''Candidate keys:''' member index + activity session key is the natural primary key because the same member should not be linked twice as responsible for the same session.
    287  * '''Attributes:''' no relationship attributes.
     276responsible_for connects Members to !ActivitySessions. It represents members responsible for organizing or holding an activity session, such as the President holding weekly meetings, HR coordinating training sessions, or event organizers coordinating event-related activity sessions.
     277
     278 * '''Candidate keys:''' member student_index + activity session key is the natural primary key because the same member should not be linked twice as responsible for the same session.
    288279
    289280==== volunteers ====
    290281
    291 volunteers connects Members to !EventSessions. It represents members volunteering for concrete event sessions, such as helping at company stands, coffee breaks, official openings, social activities, or other event-session tasks. It is modeled separately from core team applications because volunteering for a session is operational help, not necessarily an organizing-team function.
    292 
    293  * '''Candidate keys:''' member index + event session key + volunteer_role is the primary key since one member can volunteer in multiple roles for the same session.
    294  * '''Attributes:'''
    295    * volunteer_role: text, required, description or controlled value of the volunteer task/role.
     282volunteers connects Members to !EventSessions. It represents members volunteering for concrete event sessions, such as helping at company stands, coffee breaks, official openings, social activities, or other event-session tasks. It is modeled separately from event function applications because volunteering for a session is operational help, not necessarily an organizing-team function.
     283
     284 * '''Candidate keys:''' member student_index + event session key is chosen because one member is stored once for the same event session.
     285 * '''Attributes:'''
     286   * volunteer_role: text, required.
    296287
    297288==== has_stage ====
     
    299290has_stage connects Members to !MembershipStages. It stores membership history: which membership stage a member had and during what period. It is needed even though member subtypes exist, because subtypes show the conceptual categories while this relationship records time periods and transitions.
    300291
    301  * '''Candidate keys:''' member index + ms_name + valid_from is chosen because a member can potentially enter the same stage more than once over time.
     292 * '''Candidate keys:''' member student_index + ms_id + valid_from is chosen because a member can potentially enter the same stage more than once over time.
    302293 * '''Attributes:'''
    303294   * valid_from: date, required, start date of the membership stage.
    304    * valid_to: date, optional for the current active stage; When present, must be greater than or equal to valid_from.
     295   * valid_to: date, optional for the current active stage; when present, must be greater than or equal to valid_from.
    305296
    306297==== mentorship ====
    307298
    308 mentorship connects !FullMembers to !YoungMembers. It represents that a young member must have a mentor who is a full member.
    309 
    310  * '''Candidate keys:''' the young member key is sufficient since each young member has exactly one mentor forever.
    311  * '''Attributes:''' no relationship attributes.
    312 
    313 ==== is ====
    314 
    315 is connects !FullMembers to !OrganizationalFunctions. It represents a full member holding an organizational function during a mandate. It is needed because Board membership and coordinator responsibilities are roles held by full members, not separate membership stages.
    316 
    317  * '''Candidate keys:''' full member index + of_title + mandate is chosen because the same full member can hold different functions and the same function can be held by different full members in different mandates.
    318  * '''Attributes:'''
    319    * mandate: text or date-range label, required, controlled format such as 2025/2026.
     299mentorship connects !FullMembers to !YoungMembers. It represents that a young member must have a mentor who is a full member.
     300
     301==== holds_function ====
     302
     303holds_function connects !FullMembers to !OrganizationalFunctions. It represents a full member holding an organizational function during a mandate. It is needed because Board membership and coordinator responsibilities are roles held by full members, not separate membership stages.
     304
     305 * '''Candidate keys:''' full member student_index + of_id + mandate_year is chosen because the same full member can hold different functions and the same function can be held by different full members in different mandates.
     306 * '''Attributes:'''
     307   * mandate_year: numeric, required, year of the organizational function mandate.
    320308
    321309==== cooperation ====
     
    323311cooperation connects Companies to !EventEditions. It represents cooperation between a company and a specific event edition, such as sponsorship, partnership, Job Fair participation, challenge provider, or similar cooperation. It is intentionally simple because detailed company contacts and communications are outside this project scope.
    324312
    325  * '''Candidate keys:''' c_id + event edition key + cooperation_type since one company can cooperate with the same event edition in multiple ways.
     313 * '''Candidate keys:''' c_id + event edition key is chosen because one company is stored once per event edition in this model.
    326314 * '''Attributes:'''
    327315   * cooperation_type: text, required, controlled value such as sponsor, partner, participant, challenge provider, or supporter.
     
    338326||v08||Changed the weak entity keys of !EventSessions, !EventEditions and !ActivitySessions to account for edge cases where two sessions or editions could otherwise have the same identifying values and need location/title/start time to distinguish them.||
    339327||v09||Removed "required_attendance" relationship after realising it is redundant.||
     328||v10||Changed string primary keys to numeric identifiers where needed. Renamed the student key from index to student_index. Added Locations as an entity. Changed !ActivitySessions, !EventEditions, !EventSessionTypes, !EventSessions and !EventFunctions to use numeric identifiers/partial numeric identifiers. Changed !EventSessions, !EventEditions and !ActivitySessions to not be weak entities. Renamed !CoreTeamFunctions to !EventFunctions and renamed the relationship between !FullMembers and !OrganizationalFunctions from is to holds_function. Made some relationships to be mandatory based on logic.||
    340329
    341330== AI Use ==