Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

57 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

מערכת ניהול דיור מוגן

שמות המגישים: אלישע רובין ודניאל סיבוני
שם המערכת: מערכת ניהול דיור מוגן
היחידה הנבחרת: יחידת המגורים והאירועים


תוכן עניינים


מבוא

מערכת זו נועדה לנהל את הפעילות השוטפת של מוסד לדיור מוגן. המערכת שומרת נתונים על חדרים, דיירים, אנשי צוות, אירועים, תפריטים, ציוד ותחזוקה.
היא מאפשרת לעקוב אחר:

  • ניהול דיירים ושיבוצם לחדרים
  • תיעוד אירועים והשתתפות דיירים
  • מעקב אחר ציוד ובקשות תחזוקה
  • ניהול תפריטי ארוחות והצוות שאחראי לבישול
  • זיהוי תפקידים של אנשי צוות במערכת

1. מבנה בסיס הנתונים

DB Diagram 1
DB Diagram 2

1.1 חלוקה ליישויות נפרדות (Normalization)

בחרנו לעבוד לפי עקרונות הנורמליזציה (עד דרגה 3) כדי להבטיח:

  • הפחתת כפילויות
  • עדכון יעיל
  • מניעת שגיאות לוגיות

1.2 קשרי 1:רבים ו-n:n

  • resident ←→ room – קשר 1:רבים
  • resident ←→ event – טבלת קשר visiting_event לקשר n:n
  • maintenance_req – מפתח ראשי מורכב: room_id, staff_member_id, request_id
  • is_chef – קשר n:n בין staff_member ל-meal

1.3 טבלת meal עם מפתח מורכב

צירוף meal_type + day_of_the_week מהווה מזהה ייחודי.

1.4 שמירה על עקביות עם מפתחות זרים

  • לא ניתן להוסיף דייר לחדר לא קיים
  • בקשת תחזוקה חייבת להתייחס לישות קיימת

1.5 הפרדת המידע האנושי והלוגיסטי

מפריד בין:

  • ישויות אנושיות (דיירים, עובדים)
  • ישויות לוגיסטיות (חדרים, ציוד)

1.6 בחירה במזהים מספריים

למשל resident_id, room_id:

  • מפשט שאילתות
  • קישורים פשוטים
  • יעילות בביצועים

1.7 הוספת הנתונים

הוספנו נתונים ב-3 דרכים:

  1. בינה מלאכותית
  2. סקריפט Python
  3. אתר Mockaroo

image
image
image

דוגמה לגיבוי ושחזור נתונים:
image
image


2. שלב ב'

2.1 Queries

  • 5 האירועים הכי פופולריים

  • image

  • בקשות תחזוקה לפי חדר וקיבולת החדר

  • image

  • התפלגות השתתפות באירועים

  • image

  • 3 הפריטים שהשתמשו בהם הכי הרבה בבבקשות תחזוקה

  • image

  • חדרים עם פחות/יותר מ-2 בקשות תחזוקה

  • image

  • מספר השפים שמכינים ארוחות לאורך הזמן

  • image

  • מציאת העובדים שסיימו הכי הרבה משימות מכל תפקיד

  • image

  • אחוזי התפוסה בפועל של החדרים לעומת הקיבולת המקסימלית בבניין

  • image

2.2 Updates/Deletes

image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image
image

2.3 Rollback/Commit

image

image

image

image

image

image

image

image

2.4 alter tables:

image

image

image

image

image

image

image

3. שלב ג

ביצענו merge עם בסיס נתונים אחר שאחראי על הדיירים. זה הERD של בסיס הנתונים: other DB ERD וזה ה DSD: other DB DSD

והנה אותם תרשימים אחרי ה merge: updated ERD updated DSD

החלטנו לבצע את האינטגרציה כך שכאשר היתה טבלה משותפת (לדוג' לשנינו היה טבלה של resident אז השתמשנו בטבלה שלהם בסוף). הדבר לא יצר בעיות התאמה בגלל ששנינו הגדרנו את המפתחות של רוב הדברים פשוט במספרים בין 1 ל-1000. עדיין נתקלנו בבעיות מפני שטבלאת resident שלבם כללה פחות אנשים מטבלאת resident שלנו מה שגרם לבעיות בטבלאות אחרות שלנו שנשענו על resident, אז ביצענו merge Into בין הטבלאות:

MERGE INTO resident AS t1
USING resident_temp AS t2  -- our table
ON t1.residentid = t2.resident_id
WHEN NOT MATCHED THEN
    INSERT (residentid, firstname, lastname, dateofbirth, admissiondate, roomid)
    VALUES (
        t2.resident_id,
        SPLIT_PART(t2.resident_name, ' ', 1),
        SPLIT_PART(t2.resident_name, ' ', 2),
        t2.resident_dob,
        '01/01/2021',
        t2.room_id
    );

דהיינו הוספנו לטבלה שלהם את כל האיברים בטבלה שלנו שלא קיימים בטבלה שלהם. חוץ מזה, האינטגרציה רצה על מי מנוחות.

יצירת המבטים

החלטנו ליצור שני מבטים. המבט הראשון הוא עבור עובדי התחזוקה ונותן להם את המידע המעניין אותם, קרי: משימות שהם צריכים לעשות, החדרים שצריכים טיפול וכו'. הנה איך היצירה שלו נראית ואיך הוא נראה:

create view caregiver_maintenance as 
select c.caregiverid, (c.firstname || ' ' || c.lastname) caregiver_name, i.item_name, i.quantity, r.roomnumber, mr.req_description, mr.req_status
from maintenance_req mr
join caregiver c on mr.staff_member_id = c.caregiverid
join inventory i on mr.item_id = i.item_id
join department d on c.departmentid = d.departmentid
join room r on mr.room_id = r.roomid
where d.name not ilike '%Care%' and d.name not ilike '%nursing%'
 

image

הרצנו שני שאילתות עליו. הראשונה נועדה לברר אילו מוצרים זקוקים אליהם בשביל משימות שעומדות להתקיים שיש מחסור מהם:

image

השאילתא השנייה מחפשת את כל המשימות שעובד מסויים צריך לבצע, יחד עם מספר החדר בו המשימה מתבצעת.

image

המבט השני הוא מידע בשביל המשפחה, דהיינו: הסטוריית ביקורים, ההיסטוריה הרפואית של הדייר ומידע על הרופאים שלו וכו'.

create view family_view as 
select fv.visitdate, fv.visitorname, r.firstname || ' ' || r.lastname resident_name,
c.firstname || ' ' || c.lastname caregiver_name, c.phone caregiver_phone, mt.treatmentdate, mt.treatmenttype
from familyvisit fv
join medicaltreatment mt on mt.residentid = fv.residentid
join caregiver c on c.caregiverid = mt.caregiverid
join resident r on r.residentid = mt.residentid

image

הרצנו עליו שני שאילתות. הראשונה מחפשת מידע על כל הרופאים שטיפלו בחולה מסויים: image

השנייה מיצרת את הסטוריית הטיפולים של חולה מסויים:

image

4. שלב ד

יצרנו 3 פונקציונאליות חדשות באמצעות pg/plsql- פונקציה לעידכון בטבלאת האירועים על ימי הולדת של דיירים, פונקציה להמלצה על אירועים לדייר כלשהו על פי כמה משתתפים אחרים הולכים לאותו אירוע, ומערכת להורדת כמות המוצרים במערכת בצורה בטוחה.

בנוסף לכך, יצרנו טבלאות חדשות ל updates_log, orders כדי לתאר את העידכונים וכדי לערוך הזמנות מוצרים בהתאמה

4.1 עידכון ימי הולדת

יצרנו פונקציה שעוברת על כל הדיירים, ואם היומולדת שלהם מחר אז יוצרים איבנט חדש בטבלה event.

יצרנו עבור זה גם פונקציות עזר כגון get_resident_age ו add_event שדואג להכנסת אירוע בלי צורך להגדיר מפתח יחודי

-- update birthdays, make the location of the event at the resident's room and record in log
CREATE OR REPLACE FUNCTION create_upcoming_birthday_events()
RETURNS VOID AS $$
DECLARE
  r RECORD;
  v_event_date DATE := CURRENT_DATE + INTERVAL '1 day';
  v_age INT;
  v_room INT;
  v_event_name VARCHAR(100);
BEGIN
  FOR r IN
    SELECT residentid, firstname, lastname, dateofbirth, roomnumber
    FROM resident re
    JOIN room ro ON re.roomid = ro.roomid 
    WHERE EXTRACT(MONTH FROM re.dateofbirth) = EXTRACT(MONTH FROM v_event_date)
      AND EXTRACT(DAY FROM re.dateofbirth) = EXTRACT(DAY FROM v_event_date)
  LOOP
    -- Use existing function to get current age
    v_age := get_resident_age(r.residentid) + 1;
    v_room := r.roomnumber;

    -- Construct event title
    v_event_name := r.firstname || ' ' || r.lastname || '''s ' || v_age || ' birthday!';

    -- Insert birthday event
    CALL add_event(v_event_name, v_event_date, CAST(v_room AS VARCHAR));

    -- Log the event creation
    INSERT INTO updates_log(description)
    VALUES ('Birthday event added: "' || v_event_name || '" for ' || v_event_date);
  END LOOP;
END;
$$ LANGUAGE plpgsql;

-- select create_upcoming_birthday_events();

ולהלן התוצאה:

image

image

4.2 המלצת אירועים

יצרנו מערכת המלצת אירועים שממליצה עבור דייר מסוים אירועים שאנשים בטווח גילאים שלו ומאותו מין כמוהו הולכים אליהם גם להלן הקוד:

-- Main function - recommend events based on similar age groups
CREATE OR REPLACE FUNCTION recommend_events(target_resident_id INT)
RETURNS TABLE(
    event_date DATE,
    event_location VARCHAR,
    similar_age_attendees BIGINT
)
LANGUAGE plpgsql
AS $$
DECLARE
    target_age INT;
BEGIN
    -- Log the request
    CALL log_recommendation_request(target_resident_id);
    
    -- Get target resident's age
    SELECT get_resident_age(target_resident_id) INTO target_age;
    
    -- Return future events attended by residents within 5 years of target's age
    RETURN QUERY
    SELECT 
        e.event_date,
        e.event_location,
        COUNT(ve.resident_id) as similar_age_attendees
    FROM event e
    JOIN visiting_event ve ON e.event_id = ve.event_id
    -- WHERE e.event_date >= CURRENT_DATE
      WHERE e.event_id NOT IN (
          SELECT event_id 
          FROM visiting_event 
          WHERE resident_id = target_resident_id
      )
      AND get_resident_age(ve.resident_id) BETWEEN (target_age - 5) AND (target_age + 5)
    GROUP BY e.event_id, e.event_date, e.event_location
    ORDER BY COUNT(ve.resident_id) DESC
    LIMIT 10;
END;
$$;

-- SELECT * FROM recommend_events(123);

ולהלן התוצאה:

image

4.3 עידכון מלאי

יצרנו פרוצדורה שמקטינה את הכמות של מוצר כלשהו במלאי, ומוודאת שלא מקטינים אותה ביותר מידי. בנוסף לכך, יצרנו טריגר שמכניס בצורה אוטומטית לטבלה orders הזמנה של מוצר אם הכמות שלו במלאי קטנה מחמש

להלן הקוד:

CREATE OR REPLACE PROCEDURE decrease_item_quantity(
    target_item_id INT,
    decrease_amount INT DEFAULT 1
)
LANGUAGE plpgsql
AS $$
BEGIN
    -- Update the quantity
	
    UPDATE inventory 
    SET quantity = quantity - decrease_amount
    WHERE item_id = target_item_id AND quantity > decrease_amount;
	
    -- Check if update affected any rows
    IF NOT FOUND THEN
        RAISE EXCEPTION 'Item with ID % not found', target_item_id;
    END IF;
    
    INSERT INTO updates_log(description)
	VALUES('Decreased quantity of item ' || target_item_id || ' by ' || decrease_amount);
END;
$$;

להלן הקוד עבור הטריגר (יצרנו פונקציית עזר שפרקטית מכניסה את ההזמנה לטבלה)

CREATE OR REPLACE TRIGGER order_low_stock
    AFTER UPDATE OF quantity ON inventory
    FOR EACH ROW
    WHEN (NEW.quantity < 5)
    EXECUTE FUNCTION auto_order_low_stock();


CREATE OR REPLACE FUNCTION auto_order_low_stock()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
    -- Check if quantity dropped below 5
    IF NEW.quantity < 5 THEN
        -- Check if we haven't already placed a pending order for this item
        IF NOT EXISTS (
            SELECT 1 FROM orders 
            WHERE item_id = NEW.item_id 
            AND status = 'PENDING'
        ) THEN
            -- Insert new order
            INSERT INTO orders (item_id, item_name, order_quantity)
            VALUES (NEW.item_id, NEW.item_name, 10);
            
            RAISE NOTICE 'Auto-ordered 10 units of % (ID: %) - stock level: %', 
                         NEW.item_name, NEW.item_id, NEW.quantity;
        END IF;
    END IF;
    
    RETURN NEW;
END;
$$;

ולהלן התוצאה:

image

image

4.4 פונקציות נוספות

יצרנו טריגר שמוודא שלא מכניסים לתוך meintenance req בקשה שהיא לא recived.

CREATE OR REPLACE FUNCTION public.check_maintenance_status()
RETURNS trigger
BEGIN
	IF UPPER(NEW.req_status) <> 'RECEIVED' THEN
    RAISE EXCEPTION 'Maintenance request status must be "Received".';
END IF;
RETURN NEW;
END;

4.5 פונקציות עזר

יצרנו פונקציה שמטרתה היא שנוכל להכניס אירוע ושהיא תחולל לנו מפתח ייחודי לאירוע:

--generates a unique id for an event i want to insert
CREATE OR REPLACE PROCEDURE add_event(
  p_name VARCHAR(100),
  p_date DATE,
  p_location VARCHAR(100)
)
LANGUAGE plpgsql
AS $$
DECLARE
  v_event_id INT;
BEGIN
  -- Generate unique event_id using timestamp and random number
  v_event_id := TRUNC(RANDOM() * 100000)::INT;

  -- Insert into event table
  INSERT INTO event(event_id, event_name, event_date, event_location)
  VALUES (v_event_id, p_name, p_date, p_location);

  -- Optional: output confirmation
  RAISE NOTICE 'Event % added with ID: %', p_name, v_event_id;
END;
$$;

בנוסף לכך יצרנו פונקיית עזר שמחשבת לנו את גילו של דייר כלשהו

-- helper function to calculate a resident age
CREATE OR REPLACE FUNCTION get_resident_age(p_resident_id INT)
RETURNS INT AS $$
DECLARE
  v_dob DATE;
  v_age INT;
BEGIN
  -- Get the resident's date of birth
  SELECT dateofbirth INTO v_dob
  FROM resident
  WHERE resident_id = p_resident_id;

  -- Calculate the age
  v_age := DATE_PART('year', AGE(CURRENT_DATE, v_dob));

  RETURN v_age;

EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RAISE EXCEPTION 'Resident with ID % not found.', p_resident_id;
END;
$$ LANGUAGE plpgsql;

5. שלב ה

בנינו GUI באמצעות python ע"י שימוש בספריית tkinter בשביל הגרפיקה ובספריית sqlalchemy בשביל התקשורת עם בסיס הנתונים. מפאת המקום (כ-1700 שורות) לא נראה את הקוד, אך נדגים את פעולותיו ונראה את התוצר הסופי.

5.1 מסך בית

אנחנו יצרנו 4 ממשקים בשביל המשתמשים השונים בבסיס הנתונים, כל אחד "מסתכל" על המידע הרלוונטי עבורו: ממשק עבור הדייר, ממשק עבור תחזוקה, ממשק עבור המשפחה של הדייר וממשק עבור המנהל.

image

5.2 resident view

image

בממשק זה אנו מצריכים את הדייר להיכנס למערכת (signin) ומאפשרים לו לצפות בפעילויות הפופולריות, בפעילויות המומלצות עבורו ונותנים לו את האפשרות להירשם לפעילויות או לבטל הרשמה.

image

כמו שניתן לראות, דייר בעל ת.ז. 123 נכנס למערכת ומופיעים עבורו הפעילויות אליהם הוא רשום, הפעילויות המומלצות עבורו, טופ 5 פעילויות פופולריות וכן הבקשות תחזוקה שביצע.

בנוסף לכך, נותנים לו אפשרות לבצע בקשת תחזוקה עבור החדר שבו הוא גר (הוא גם יכול לראות את הבקשות שביצע כבר)

5.3 janitor view

image

בממשק זה אנו מצריכים את איש התחזוקה להיכנס למערכת (signin) ומאפשרים לו לצפות בבקשות התחזוקה שהוא יוכל לשייך אליו, לצפות בבקשות שכבר משויכות אליו (עם בר שמראה את ההתקדמות) ולראות את כל הפריטים הקיימים במלאי ולהפחית את הכמות במידה והוא השתמש בחלק מהפריטים.

image

לאחר שאיש התחזוקה בעל הת.ז. 604 נכנס, הוא יכול לראות את הבקשות תחזוקה שמשויכות אליו (ניתן גם לראות את בר ההתקדמות), את הבקשות שעדיין לא משויכות לאף אחד והוא יכול לקחת עליהם אחריות ובכך לעדכן את בסיס הנתונים. בנוסף הוא גם יכול לראות את המלאי של כל הפריטים ובמידת הצורך לעדכן כמויות מסוימות של פריטים וכך לעדכן את בסיס הנתונים.

5.4 family view

image

בממשק זה אנו מצריכים את בן המשפחה להיכנס למערכת (signin) ומאפשרים לו לצפות במידע הרלוונטי על הדייר: את היסטוריית הטיפולים הרפואיים ואת הצוות הרפואי שמטפל בדייר.

image

כפי שניתן לראות, הוא יכול לראות את המידע הרלוונטי על הדייר.

5.5 manager view

בממשק זה צריך להיכנס עם שם משתמש וסיסמה של המנהל על מנת לראות את הגרפים שמתארים ביצועים של בית האבות.

image

לאחר שהמנהל נכנס, ישנם כמה גרפים שהוא מסוגל לראות: אחוזים של השתתפות בפעילויות, השתתפות לאורך זמן, כמות התפוסה של החדרים בבית האבות, leaderboard של העובדים החרוצים בתחומם (נמדד ע"פ מספר הבקשות שביצעו) וכמות החדרים באחוזים עם מספר תלונות גבוה.

image

image

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages