Changes between Version 1 and Version 2 of AditionalApplicationDevelopment


Ignore:
Timestamp:
09/16/25 19:02:25 (5 days ago)
Author:
221028
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • AditionalApplicationDevelopment

    v1 v2  
    22== Напредни PostgreSQL компоненти
    33
    4 ** 1) Интегритетни ограничувања (CHECK/UNIQUE)**
    5 **1.1 Реакција: различни елементи**
     4**1) Интегритетни ограничувања (CHECK/UNIQUE)**
     5**1.1 Reaction: различни елементи**
     6
     7Цел: да не се креира реакција со ист елемент двапати.
    68
    79{{{
     
    1214
    1315
    14 **1.2 Елементи: валидни физички вредности**
     16
     17Каде се користи:
     18
     19DatabaseManager.add_reaction(...) и update_reaction(...) (backend): пред-валидација и фаќање CheckViolation.
     20
     21
     22**1.2 Elements: физички валидни вредности**
     23
     24Цел: основна научна валидност за елементите.
     25
     26
    1527
    1628{{{
    1729ALTER TABLE elements
    1830  ADD CONSTRAINT elements_atomic_number_positive CHECK (atomic_number > 0);
    19 
    2031ALTER TABLE elements
    2132  ADD CONSTRAINT elements_mass_positive CHECK (atomic_weight > 0);
    22 
    2333ALTER TABLE elements
    24   ADD CONSTRAINT elements_melting_before_boiling CHECK (
    25     boiling_point IS NULL OR melting_point IS NULL OR melting_point < boiling_point
    26   );
    27 
    28 }}}
     34  ADD CONSTRAINT elements_melting_before_boiling
     35  CHECK (boiling_point IS NULL OR melting_point IS NULL OR melting_point < boiling_point);
     36
     37}}}
     38
     39
     40Каде се користи:
     41
     42DatabaseManager.add_element(...) / update_element(...): фаќа CheckViolation и враќа јасна порака.
    2943
    3044**1.3 Партиципација: без дупликати**
     45
     46Цел: еден корисник да не „учествува“ двапати во истиот експеримент.
     47
    3148
    3249{{{
     
    3653
    3754
    38 **2) Временско печатење на учество**
     55
     56Каде се користи:
     57
     58DatabaseManager.track_experiment_participation(...): ON CONFLICT (user_id, experiment_id) DO NOTHING.
     59
     60**1.4 Уникатни бизнис-клучеви (email/symbol/equipment_name)**
     61
     62Цел: да нема дупликат email/симбол/име на опрема.
     63
     64
     65
     66{{{
     67ALTER TABLE "User"      ADD CONSTRAINT uq_user_email      UNIQUE (email);
     68ALTER TABLE elements    ADD CONSTRAINT uq_elements_symbol UNIQUE (symbol);
     69ALTER TABLE labequipment ADD CONSTRAINT uq_equipment_name UNIQUE (equipment_name);
     70}}}
     71
     72
     73
     74Каде се користи:
     75
     76Регистрација (DatabaseManager.register_user): фаќа UniqueViolation за email.
     77
     78Додавање/уредување елемент (add_element/update_element): _norm_symbol() → UniqueViolation ако постои.
     79
     80Додавање опрема (add_lab_equipment): UniqueViolation ако постои.
     81
     82**1.5 Уникатна тројка за Reaction (element1, element2, conditions)**
     83
     84Цел: иста реакција под исти услови да не се дуплира.
     85
     86
     87
     88{{{
     89CREATE UNIQUE INDEX IF NOT EXISTS uq_reaction_e1_e2_cond
     90ON reaction (element1_id, element2_id, COALESCE(conditions, ''));
     91}}}
     92
     93
     94
     95Каде се користи:
     96
     97add_reaction/update_reaction: фаќа UniqueViolation и враќа None/False со лог порака „duplicate (element1, element2, conditions)“.
     98
     99**2) Временско печатење на учество (TIMESTAMPTZ)**
     100
     101Цел: да се бележи точниот момент (со временска зона) кога студентот учествувал.
     102
     103
    39104
    40105{{{
    41106ALTER TABLE userparticipatesinexperiment
    42107  ADD COLUMN IF NOT EXISTS participation_timestamp TIMESTAMPTZ;
    43 
    44108UPDATE userparticipatesinexperiment
    45109   SET participation_timestamp = COALESCE(participation_timestamp, NOW());
    46 
    47110ALTER TABLE userparticipatesinexperiment
    48111  ALTER COLUMN participation_timestamp SET NOT NULL,
    49112  ALTER COLUMN participation_timestamp SET DEFAULT NOW();
    50113}}}
    51  
    52 
    53 **3) Тригер: set_default_safety (на experiment)
    54 **
     114
     115
     116
     117Каде се користи:
     118
     119get_user_experiments(...), get_student_participation_experiments(...): ORDER BY participation_timestamp DESC.
     120
     121Извештаи /reports/... што групираат/редат по участие.
     122
     123**3) Тригер: default safety_warning (на experiment)**
     124
     125Цел: ако UI не прати безбедносно предупредување, базата сама да пополни разумен default.
     126
    55127
    56128{{{
     
    74146
    75147
     148Каде се користи:
     149
     150DatabaseManager.insert_experiment(...): во кодот праќаме NULL ако корисникот остави празно → тригерот пополнува.
     151
     152
    76153**4) Погледи (Views)**
    77154**4.1 vw_students_experiments_detailed**
     155
     156Цел: брз извештај за студент, експеримент и време на учество.
    78157
    79158
     
    85164  e.experiment_id,
    86165  e.result,
    87   e.time_stamp AS participation_time
     166  up.participation_timestamp::timestamp AS participation_time
    88167FROM student s
    89168JOIN "User" u ON s.student_id = u.user_id
    90169JOIN userparticipatesinexperiment up ON s.student_id = up.user_id
    91170JOIN experiment e ON up.experiment_id = e.experiment_id
    92 ORDER BY u.user_name, e.time_stamp DESC;
    93 
    94 }}}
     171ORDER BY u.user_name, up.participation_timestamp DESC;
     172}}}
     173
    95174
    96175**4.2 vw_user_activity_summary**
     176
     177Цел: консолидиран преглед на активност по корисник.
    97178
    98179
     
    112193GROUP BY u.user_id, full_name, u.role
    113194ORDER BY full_name;
    114 
    115 }}}
     195}}}
     196
    116197
    117198**4.3 vw_students_experiments_for_teacher**
     199
     200Цел: на наставник да му се прикажат експериментите на неговите студенти.
    118201
    119202
     
    139222JOIN elements el2 ON r.element2_id = el2.element_id
    140223ORDER BY up.participation_timestamp DESC, student_name;
    141 }}}
    142 
    143 
    144 **5) Stored function: create_reaction_and_experiment_fn(...)**
    145 
    146 
    147 {{{
    148 create_reaction_and_experiment_fn(
    149   p_teacher_id    INT,
    150   p_element1_id   INT,
    151   p_element2_id   INT,
    152   p_product       TEXT,
    153   p_conditions    TEXT,
    154   p_exp_result    TEXT,
    155   p_safety        TEXT,
    156   p_equipment_ids INT[]
     224
     225
     226}}}
     227
     228Каде се користи:
     229
     230Backend методи: vw_students_experiments_detailed(), vw_students_experiments_for_teacher(teacher_id), get_user_activity_summary().
     231
     232**5) Складирана функција: create_reaction_and_experiment_fn(...)**
     233
     234Цел: атомично креирање Reaction → Experiment → (N:M) Equipment во една транскација на ниво на база.
     235Код (скратено):
     236
     237
     238{{{
     239CREATE OR REPLACE FUNCTION create_reaction_and_experiment_fn(
     240  p_teacher_id INT, p_element1_id INT, p_element2_id INT,
     241  p_product TEXT, p_conditions TEXT, p_exp_result TEXT,
     242  p_safety TEXT, p_equipment_ids INT[]
    157243) RETURNS TABLE(reaction_id INT, experiment_id INT)
    158 
    159 }}}
    160 
    161 
    162 Логика (атомично во една функција):
    163 
    164 INSERT во reaction → reaction_id.
    165 
    166 Ако p_exp_result е празен → авто-генерира текст „Експеримент со X + Y…“.
    167 
    168 INSERT во experiment (time_stamp = CURRENT_TIMESTAMP, safety_warning = p_safety).
    169 
    170 Ако има p_equipment_ids → INSERT во experimentlabequipment со unnest(p_equipment_ids).
    171 
    172 Враќа {reaction_id, experiment_id}.
    173 
    174 Во апликацијата, оваа функција се повикува ако е достапна; ако не, има Python fallback со транскација (исто поведение).
     244LANGUAGE plpgsql AS $$
     245DECLARE v_reaction_id INT; v_experiment_id INT; v_result TEXT;
     246BEGIN
     247  INSERT INTO reaction(...) RETURNING reaction_id INTO v_reaction_id;
     248
     249  IF p_exp_result IS NULL OR p_exp_result = '' THEN
     250    -- авто-опис: „Експеримент со X и Y под услови...“
     251    SELECT 'Експеримент со ' || e1.symbol || ' и ' || e2.symbol ||
     252           CASE WHEN p_conditions IS NULL OR p_conditions = '' THEN
     253                ' под услови: стандардни. ' ELSE ' под услови: ' || p_conditions || '. ' END ||
     254           'Очекуван производ: ' || COALESCE(p_product, 'непознат') || '.'
     255    INTO v_result
     256    FROM elements e1, elements e2
     257    WHERE e1.element_id = p_element1_id AND e2.element_id = p_element2_id;
     258  ELSE v_result := p_exp_result; END IF;
     259
     260  INSERT INTO experiment(...) VALUES (...) RETURNING experiment_id INTO v_experiment_id;
     261
     262  IF p_equipment_ids IS NOT NULL AND array_length(p_equipment_ids,1) > 0 THEN
     263    INSERT INTO experimentlabequipment(experiment_id, equipment_id)
     264    SELECT v_experiment_id, unnest(p_equipment_ids) ON CONFLICT DO NOTHING;
     265  END IF;
     266
     267  RETURN QUERY SELECT v_reaction_id, v_experiment_id;
     268END; $$;
     269}}}
     270
     271
     272
     273Каде се користи:
     274
     275DatabaseManager.create_reaction_and_experiment(...): прво повикува DB-функција; ако не постои → Python fallback со транскација.
     276
     277Route /reactions/add: UI формата креира Reaction + Experiment + Equipment во еден чекор.
    175278
    176279**6) Индекси (перформанси)**
    177280
    178 {{{
    179 CREATE INDEX IF NOT EXISTS idx_reaction_elements ON reaction (element1_id, element2_id);
    180 CREATE INDEX IF NOT EXISTS idx_exp_reaction      ON experiment (reaction_id);
    181 CREATE INDEX IF NOT EXISTS idx_up_user           ON userparticipatesinexperiment (user_id);
    182 CREATE INDEX IF NOT EXISTS idx_up_experiment     ON userparticipatesinexperiment (experiment_id);
    183 }}}
    184 
    185 
     281Цел: побрзи пребарувања и сортирања.
     282
     283
     284{{{
     285-- филтрирање по E1/E2:
     286CREATE INDEX IF NOT EXISTS idx_reaction_elements   ON reaction (element1_id, element2_id);
     287-- експерименти по реакција (JOIN/филтер):
     288CREATE INDEX IF NOT EXISTS idx_exp_reaction        ON experiment (reaction_id);
     289-- учества по корисник/експеримент:
     290CREATE INDEX IF NOT EXISTS idx_up_user             ON userparticipatesinexperiment (user_id);
     291CREATE INDEX IF NOT EXISTS idx_up_experiment       ON userparticipatesinexperiment (experiment_id);
     292-- реални ORDER BY по време:
     293CREATE INDEX IF NOT EXISTS idx_up_user_time        ON userparticipatesinexperiment (user_id, participation_timestamp DESC);
     294CREATE INDEX IF NOT EXISTS idx_exp_reaction_time   ON experiment (reaction_id, time_stamp DESC);
     295
     296
     297}}}
     298
     299Каде се користат:
     300
     301get_experiments_by_reaction(reaction_id) → idx_exp_reaction(_time)
     302
     303get_user_experiments(user_id) и get_student_participation_experiments(student_id) → idx_up_user(_time)
     304
     305get_reaction_by_symbols(sym1,sym2) → elements(symbol) + idx_reaction_elements (бидејќи симболите се нормализирани во кодот, индексот е употреблив).
     306