wiki:AditionalApplicationDevelopment

Version 2 (modified by 221028, 5 days ago) ( diff )

--

Напредни PostgreSQL компоненти

1) Интегритетни ограничувања (CHECK/UNIQUE) 1.1 Reaction: различни елементи

Цел: да не се креира реакција со ист елемент двапати.

ALTER TABLE reaction
  ADD CONSTRAINT reaction_distinct_elements
  CHECK (element1_id <> element2_id);

Каде се користи:

DatabaseManager.add_reaction(...) и update_reaction(...) (backend): пред-валидација и фаќање CheckViolation.

1.2 Elements: физички валидни вредности

Цел: основна научна валидност за елементите.

ALTER TABLE elements
  ADD CONSTRAINT elements_atomic_number_positive CHECK (atomic_number > 0);
ALTER TABLE elements
  ADD CONSTRAINT elements_mass_positive CHECK (atomic_weight > 0);
ALTER TABLE elements
  ADD CONSTRAINT elements_melting_before_boiling
  CHECK (boiling_point IS NULL OR melting_point IS NULL OR melting_point < boiling_point);

Каде се користи:

DatabaseManager.add_element(...) / update_element(...): фаќа CheckViolation и враќа јасна порака.

1.3 Партиципација: без дупликати

Цел: еден корисник да не „учествува“ двапати во истиот експеримент.

ALTER TABLE userparticipatesinexperiment
  ADD CONSTRAINT uq_user_experiment UNIQUE (user_id, experiment_id);

Каде се користи:

DatabaseManager.track_experiment_participation(...): ON CONFLICT (user_id, experiment_id) DO NOTHING.

1.4 Уникатни бизнис-клучеви (email/symbol/equipment_name)

Цел: да нема дупликат email/симбол/име на опрема.

ALTER TABLE "User"      ADD CONSTRAINT uq_user_email      UNIQUE (email);
ALTER TABLE elements    ADD CONSTRAINT uq_elements_symbol UNIQUE (symbol);
ALTER TABLE labequipment ADD CONSTRAINT uq_equipment_name UNIQUE (equipment_name);

Каде се користи:

Регистрација (DatabaseManager.register_user): фаќа UniqueViolation за email.

Додавање/уредување елемент (add_element/update_element): _norm_symbol() → UniqueViolation ако постои.

Додавање опрема (add_lab_equipment): UniqueViolation ако постои.

1.5 Уникатна тројка за Reaction (element1, element2, conditions)

Цел: иста реакција под исти услови да не се дуплира.

CREATE UNIQUE INDEX IF NOT EXISTS uq_reaction_e1_e2_cond
ON reaction (element1_id, element2_id, COALESCE(conditions, ''));

Каде се користи:

add_reaction/update_reaction: фаќа UniqueViolation и враќа None/False со лог порака „duplicate (element1, element2, conditions)“.

2) Временско печатење на учество (TIMESTAMPTZ)

Цел: да се бележи точниот момент (со временска зона) кога студентот учествувал.

ALTER TABLE userparticipatesinexperiment
  ADD COLUMN IF NOT EXISTS participation_timestamp TIMESTAMPTZ;
UPDATE userparticipatesinexperiment
   SET participation_timestamp = COALESCE(participation_timestamp, NOW());
ALTER TABLE userparticipatesinexperiment
  ALTER COLUMN participation_timestamp SET NOT NULL,
  ALTER COLUMN participation_timestamp SET DEFAULT NOW();

Каде се користи:

get_user_experiments(...), get_student_participation_experiments(...): ORDER BY participation_timestamp DESC.

Извештаи /reports/... што групираат/редат по участие.

3) Тригер: default safety_warning (на experiment)

Цел: ако UI не прати безбедносно предупредување, базата сама да пополни разумен default.

CREATE OR REPLACE FUNCTION trg_experiment_default_safety()
RETURNS TRIGGER AS $$
BEGIN
  IF NEW.safety_warning IS NULL OR NEW.safety_warning = '' THEN
    NEW.safety_warning := 'Стандардни безбедносни мерки';
  END IF;
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

DROP TRIGGER IF EXISTS set_default_safety ON experiment;
CREATE TRIGGER set_default_safety
BEFORE INSERT ON experiment
FOR EACH ROW
EXECUTE FUNCTION trg_experiment_default_safety();

Каде се користи:

DatabaseManager.insert_experiment(...): во кодот праќаме NULL ако корисникот остави празно → тригерот пополнува.

4) Погледи (Views) 4.1 vw_students_experiments_detailed

Цел: брз извештај за студент, експеримент и време на учество.

CREATE OR REPLACE VIEW vw_students_experiments_detailed AS
SELECT 
  s.student_id,
  (u.user_name || ' ' || u.user_surname) AS full_name,
  e.experiment_id,
  e.result,
  up.participation_timestamp::timestamp AS participation_time
FROM student s
JOIN "User" u ON s.student_id = u.user_id
JOIN userparticipatesinexperiment up ON s.student_id = up.user_id
JOIN experiment e ON up.experiment_id = e.experiment_id
ORDER BY u.user_name, up.participation_timestamp DESC;

4.2 vw_user_activity_summary

Цел: консолидиран преглед на активност по корисник.

CREATE OR REPLACE VIEW vw_user_activity_summary AS
SELECT 
  u.user_id,
  (u.user_name || ' ' || u.user_surname) AS full_name,
  u.role,
  COUNT(DISTINCT uve.element_id)    AS elements_viewed,
  COUNT(DISTINCT uvl.equipment_id)  AS equipment_viewed,
  COUNT(DISTINCT upe.experiment_id) AS experiments_participated
FROM "User" u
LEFT JOIN userviewselement           uve ON u.user_id = uve.user_id
LEFT JOIN userviewslabequipment      uvl ON u.user_id = uvl.user_id
LEFT JOIN userparticipatesinexperiment upe ON u.user_id = upe.user_id
GROUP BY u.user_id, full_name, u.role
ORDER BY full_name;

4.3 vw_students_experiments_for_teacher

Цел: на наставник да му се прикажат експериментите на неговите студенти.

CREATE OR REPLACE VIEW vw_students_experiments_for_teacher AS
SELECT 
  (u.user_name || ' ' || u.user_surname) AS student_name,
  s.student_id,
  e.experiment_id,
  e.result,
  e.time_stamp,
  r.product,
  el1.symbol AS element1_symbol,
  el2.symbol AS element2_symbol,
  up.participation_timestamp AS participation_date,
  s.teacher_id
FROM student s
JOIN "User" u ON s.student_id = u.user_id
JOIN userparticipatesinexperiment up ON s.student_id = up.user_id
JOIN experiment e ON up.experiment_id = e.experiment_id
JOIN reaction  r ON e.reaction_id   = r.reaction_id
JOIN elements el1 ON r.element1_id = el1.element_id
JOIN elements el2 ON r.element2_id = el2.element_id
ORDER BY up.participation_timestamp DESC, student_name;


Каде се користи:

Backend методи: vw_students_experiments_detailed(), vw_students_experiments_for_teacher(teacher_id), get_user_activity_summary().

5) Складирана функција: create_reaction_and_experiment_fn(...)

Цел: атомично креирање Reaction → Experiment → (N:M) Equipment во една транскација на ниво на база. Код (скратено):

CREATE OR REPLACE FUNCTION create_reaction_and_experiment_fn(
  p_teacher_id INT, p_element1_id INT, p_element2_id INT,
  p_product TEXT, p_conditions TEXT, p_exp_result TEXT,
  p_safety TEXT, p_equipment_ids INT[]
) RETURNS TABLE(reaction_id INT, experiment_id INT)
LANGUAGE plpgsql AS $$
DECLARE v_reaction_id INT; v_experiment_id INT; v_result TEXT;
BEGIN
  INSERT INTO reaction(...) RETURNING reaction_id INTO v_reaction_id;

  IF p_exp_result IS NULL OR p_exp_result = '' THEN
    -- авто-опис: „Експеримент со X и Y под услови...“
    SELECT 'Експеримент со ' || e1.symbol || ' и ' || e2.symbol ||
           CASE WHEN p_conditions IS NULL OR p_conditions = '' THEN
                ' под услови: стандардни. ' ELSE ' под услови: ' || p_conditions || '. ' END ||
           'Очекуван производ: ' || COALESCE(p_product, 'непознат') || '.'
    INTO v_result
    FROM elements e1, elements e2
    WHERE e1.element_id = p_element1_id AND e2.element_id = p_element2_id;
  ELSE v_result := p_exp_result; END IF;

  INSERT INTO experiment(...) VALUES (...) RETURNING experiment_id INTO v_experiment_id;

  IF p_equipment_ids IS NOT NULL AND array_length(p_equipment_ids,1) > 0 THEN
    INSERT INTO experimentlabequipment(experiment_id, equipment_id)
    SELECT v_experiment_id, unnest(p_equipment_ids) ON CONFLICT DO NOTHING;
  END IF;

  RETURN QUERY SELECT v_reaction_id, v_experiment_id;
END; $$;

Каде се користи:

DatabaseManager.create_reaction_and_experiment(...): прво повикува DB-функција; ако не постои → Python fallback со транскација.

Route /reactions/add: UI формата креира Reaction + Experiment + Equipment во еден чекор.

6) Индекси (перформанси)

Цел: побрзи пребарувања и сортирања.

-- филтрирање по E1/E2:
CREATE INDEX IF NOT EXISTS idx_reaction_elements   ON reaction (element1_id, element2_id);
-- експерименти по реакција (JOIN/филтер):
CREATE INDEX IF NOT EXISTS idx_exp_reaction        ON experiment (reaction_id);
-- учества по корисник/експеримент:
CREATE INDEX IF NOT EXISTS idx_up_user             ON userparticipatesinexperiment (user_id);
CREATE INDEX IF NOT EXISTS idx_up_experiment       ON userparticipatesinexperiment (experiment_id);
-- реални ORDER BY по време:
CREATE INDEX IF NOT EXISTS idx_up_user_time        ON userparticipatesinexperiment (user_id, participation_timestamp DESC);
CREATE INDEX IF NOT EXISTS idx_exp_reaction_time   ON experiment (reaction_id, time_stamp DESC);


Каде се користат:

get_experiments_by_reaction(reaction_id) → idx_exp_reaction(_time)

get_user_experiments(user_id) и get_student_participation_experiments(student_id) → idx_up_user(_time)

get_reaction_by_symbols(sym1,sym2) → elements(symbol) + idx_reaction_elements (бидејќи симболите се нормализирани во кодот, индексот е употреблив).

Note: See TracWiki for help on using the wiki.