1888 lines
96 KiB
Python
1888 lines
96 KiB
Python
# facility_management_data_generator.py
|
|
import os
|
|
import django
|
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'hospital_management.settings')
|
|
django.setup()
|
|
|
|
import random
|
|
import uuid
|
|
from decimal import Decimal
|
|
from datetime import datetime, timedelta, date
|
|
from django.utils import timezone
|
|
from django.db import transaction
|
|
from core.models import Tenant
|
|
# Setup Django environment
|
|
|
|
|
|
# Import models
|
|
from accounts.models import User
|
|
from facility_management.models import *
|
|
|
|
# Set random seed for reproducibility
|
|
random.seed(42)
|
|
|
|
|
|
def create_users():
|
|
"""Create sample Saudi hospital users (facilities + clinical ops)."""
|
|
saudi_staff = [
|
|
('mohammed_fm', 'Mohammed', 'Al-Qahtani', 'Facilities Manager'),
|
|
('ibrahim_bme', 'Ibrahim', 'Al-Otaibi', 'Biomedical Engineer'),
|
|
('abdullah_main', 'Abdullah', 'Al-Zahrani', 'Maintenance Supervisor'),
|
|
('khalid_elec', 'Khalid', 'Al-Harbi', 'Electrical Engineer'),
|
|
('ahmed_civil', 'Ahmed', 'Al-Ghamdi', 'Civil Engineer'),
|
|
('fahad_hvac', 'Fahad', 'Al-Dossari', 'HVAC Specialist'),
|
|
('saad_safety', 'Saad', 'Al-Mutairi', 'Safety & Fire Officer'),
|
|
('ali_it', 'Ali', 'Al-Shammari', 'Health IT Manager'),
|
|
('nora_admin', 'Nora', 'Al-Anazi', 'Administrative Coordinator'),
|
|
('aisha_ops', 'Aisha', 'Al-Qurashi', 'Hospital Operations Coordinator'),
|
|
]
|
|
|
|
users = []
|
|
tenant = Tenant.objects.get(pk=1)
|
|
for username, first_name, last_name, _ in saudi_staff:
|
|
user, created = User.objects.get_or_create(
|
|
tenant=tenant,
|
|
username=username,
|
|
defaults={
|
|
'email': f'{username}@ksa-hospital.gov.sa',
|
|
'first_name': first_name,
|
|
'last_name': last_name,
|
|
}
|
|
)
|
|
if created:
|
|
user.set_password('password123')
|
|
user.save()
|
|
users.append(user)
|
|
return users, {u.username: u for u in users}
|
|
|
|
|
|
def create_buildings(users):
|
|
"""
|
|
Create hospital campus buildings in KSA.
|
|
|
|
NOTE: We keep the field name 'airport_code' for compatibility with your model.
|
|
Treat it as a 'campus_code' (e.g., RHF = Riyadh Health Facility, etc.).
|
|
CLINICAL = 'CLINICAL', 'Clinical'
|
|
NON_CLINICAL = 'NON_CLINICAL', 'Non Clinical'
|
|
OTHER = 'OTHER', 'Other'
|
|
"""
|
|
tenant=Tenant.objects.get(pk=1)
|
|
buildings_data = [
|
|
# Riyadh campus
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Main Hospital (Inpatient Tower)',
|
|
'code': 'RHF-MAIN',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 12,
|
|
'total_area_sqm': Decimal('210000.00'),
|
|
'construction_year': 2014,
|
|
'architect': 'Dar Al-Handasah',
|
|
'contractor': 'Al Bawani',
|
|
'last_major_renovation': date(2022, 4, 15),
|
|
'facility_manager': users['mohammed_fm'],
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Outpatient Clinics',
|
|
'code': 'RHF-OPD',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 6,
|
|
'total_area_sqm': Decimal('95000.00'),
|
|
'construction_year': 2018,
|
|
'architect': 'Zuhair Fayez Partnership',
|
|
'contractor': 'Al Kifah',
|
|
'facility_manager': users['aisha_ops']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Emergency Department & Trauma Center',
|
|
'code': 'RHF-ED',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 3,
|
|
'total_area_sqm': Decimal('45000.00'),
|
|
'construction_year': 2016,
|
|
'architect': 'Omrania & Associates',
|
|
'contractor': 'El Seif',
|
|
'facility_manager': users['saad_safety']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Surgical Centre (ORs & Recovery)',
|
|
'code': 'RHF-SURG',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 4,
|
|
'total_area_sqm': Decimal('60000.00'),
|
|
'construction_year': 2019,
|
|
'architect': 'Arab Engineering Bureau',
|
|
'contractor': 'Nesma & Partners',
|
|
'facility_manager': users['ibrahim_bme']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Diagnostic & Imaging Centre',
|
|
'code': 'RHF-DIAG',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 4,
|
|
'total_area_sqm': Decimal('38000.00'),
|
|
'construction_year': 2017,
|
|
'architect': 'Dar Al Riyadh',
|
|
'contractor': 'Almabani',
|
|
'facility_manager': users['ali_it']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Laboratory & Blood Bank',
|
|
'code': 'RHF-LAB',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 5,
|
|
'total_area_sqm': Decimal('42000.00'),
|
|
'construction_year': 2015,
|
|
'architect': 'IDOM',
|
|
'contractor': 'Al Rashid Trading & Contracting',
|
|
'facility_manager': users['abdullah_main']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Maternity & Children Hospital',
|
|
'code': 'RHF-MCH',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 8,
|
|
'total_area_sqm': Decimal('120000.00'),
|
|
'construction_year': 2020,
|
|
'architect': 'Perkins&Will',
|
|
'contractor': 'TAV Construction',
|
|
'facility_manager': users['ahmed_civil']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Support Services (CSSD/Laundry/Kitchen)',
|
|
'code': 'RHF-SUPPORT',
|
|
'building_type': 'NON_CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 3,
|
|
'total_area_sqm': Decimal('30000.00'),
|
|
'construction_year': 2016,
|
|
'architect': 'Khatib & Alami',
|
|
'contractor': 'Al Bawani',
|
|
'facility_manager': users['fahad_hvac']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Administration',
|
|
'code': 'RHF-ADMIN',
|
|
'building_type': 'NON_CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 7,
|
|
'total_area_sqm': Decimal('25000.00'),
|
|
'construction_year': 2015,
|
|
'architect': 'Omrania & Associates',
|
|
'contractor': 'Al Bawani',
|
|
'facility_manager': users['aisha_ops']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Mosque',
|
|
'code': 'RHF-MOSQ',
|
|
'building_type': 'OTHER',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 1,
|
|
'total_area_sqm': Decimal('3500.00'),
|
|
'construction_year': 2016,
|
|
'architect': 'Omrania & Associates',
|
|
'contractor': 'Saudi Binladin Group',
|
|
'facility_manager': users['mohammed_fm']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Central Utility Plant',
|
|
'code': 'RHF-UTIL',
|
|
'building_type': 'OTHER',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 2,
|
|
'total_area_sqm': Decimal('12000.00'),
|
|
'construction_year': 2014,
|
|
'architect': 'HOK',
|
|
'contractor': 'Al Bawani',
|
|
'facility_manager': users['khalid_elec']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Parking Garage P1',
|
|
'code': 'RHF-P1',
|
|
'building_type': 'NON_CLINICAL',
|
|
'address': 'Riyadh Health Campus, Riyadh 11564, Saudi Arabia',
|
|
'floor_count': 5,
|
|
'total_area_sqm': Decimal('65000.00'),
|
|
'construction_year': 2019,
|
|
'architect': 'Zuhair Fayez Partnership',
|
|
'contractor': 'Al Bawani',
|
|
'facility_manager': users['ahmed_civil']
|
|
},
|
|
|
|
# Jeddah micro-campus (shorter list)
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Jeddah General Hospital',
|
|
'code': 'JHF-MAIN',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Jeddah Health Campus, Jeddah 23337, Saudi Arabia',
|
|
'floor_count': 9,
|
|
'total_area_sqm': Decimal('160000.00'),
|
|
'construction_year': 2013,
|
|
'architect': 'AECOM',
|
|
'contractor': 'Saudi Oger',
|
|
'last_major_renovation': date(2021, 11, 5),
|
|
'facility_manager': users['mohammed_fm']
|
|
},
|
|
{
|
|
'tenant': tenant,
|
|
'name': 'Jeddah OPD & Imaging',
|
|
'code': 'JHF-OPD',
|
|
'building_type': 'CLINICAL',
|
|
'address': 'Jeddah Health Campus, Jeddah 23337, Saudi Arabia',
|
|
'floor_count': 5,
|
|
'total_area_sqm': Decimal('60000.00'),
|
|
'construction_year': 2016,
|
|
'architect': 'Dar Al-Handasah',
|
|
'contractor': 'Almabani',
|
|
'facility_manager': users['ali_it']
|
|
},
|
|
]
|
|
|
|
buildings = []
|
|
for data in buildings_data:
|
|
building, _ = Building.objects.get_or_create(
|
|
code=data['code'],
|
|
defaults=data
|
|
)
|
|
buildings.append(building)
|
|
return buildings
|
|
|
|
|
|
def create_floors(buildings):
|
|
"""Create floors appropriate for hospital buildings (with basements for plant areas)."""
|
|
floors = []
|
|
floor_names = {
|
|
-2: 'الطابق تحت الأرضي الثاني (Sub-Basement)',
|
|
-1: 'الطابق تحت الأرضي (Basement)',
|
|
0: 'الطابق الأرضي (Ground Floor)',
|
|
1: 'الطابق الأول (First Floor)',
|
|
2: 'الطابق الثاني (Second Floor)',
|
|
3: 'الطابق الثالث (Third Floor)',
|
|
4: 'الطابق الرابع (Fourth Floor)',
|
|
5: 'الطابق الخامس (Fifth Floor)',
|
|
6: 'الطابق السادس (Sixth Floor)',
|
|
7: 'الطابق السابع (Seventh Floor)',
|
|
8: 'الطابق الثامن (Eighth Floor)',
|
|
9: 'الطابق التاسع (Ninth Floor)',
|
|
10: 'الطابق العاشر (Tenth Floor)',
|
|
11: 'الطابق الحادي عشر (Eleventh Floor)',
|
|
12: 'الطابق الثاني عشر (Twelfth Floor)',
|
|
}
|
|
|
|
for b in buildings:
|
|
has_basement = b.building_type in ['inpatient', 'surgical', 'diagnostic', 'lab', 'support', 'office', 'utility'] and b.floor_count >= 3
|
|
|
|
if b.building_type == 'inpatient' and b.floor_count >= 10:
|
|
floor_range = range(-1 if has_basement else 0, b.floor_count)
|
|
elif has_basement:
|
|
floor_range = range(-1, b.floor_count)
|
|
else:
|
|
floor_range = range(0, b.floor_count)
|
|
|
|
base_area = (b.total_area_sqm / (len(floor_range) or 1)) if b.total_area_sqm else Decimal('1000.00')
|
|
|
|
for n in floor_range:
|
|
name = floor_names.get(n, (f'الطابق {n} (Floor {n})' if n >= 0 else f'الطابق تحت الأرضي {abs(n)} (Basement {abs(n)})'))
|
|
area_sqm = base_area * Decimal(str(random.uniform(0.92, 1.08)))
|
|
|
|
# Ceiling heights (higher in lobbies/OR/diagnostic)
|
|
if n == 0 and b.building_type in ['inpatient','outpatient','emergency','surgical','diagnostic']:
|
|
ceiling_height = Decimal('5.50')
|
|
elif b.building_type in ['surgical','diagnostic'] and n >= 1:
|
|
ceiling_height = Decimal('4.00')
|
|
elif n < 0:
|
|
ceiling_height = Decimal('3.20')
|
|
else:
|
|
ceiling_height = Decimal('3.60')
|
|
|
|
is_public_access = (
|
|
(b.building_type in ['outpatient','emergency'] and n >= 0) or
|
|
(b.building_type == 'inpatient' and n in [0,1]) or
|
|
(b.building_type == 'parking') or
|
|
(b.code.endswith('MOSQ'))
|
|
)
|
|
|
|
floor, _ = Floor.objects.get_or_create(
|
|
building=b,
|
|
floor_number=n,
|
|
defaults={
|
|
'name': name,
|
|
'area_sqm': area_sqm,
|
|
'ceiling_height_m': ceiling_height,
|
|
'is_public_access': is_public_access
|
|
}
|
|
)
|
|
floors.append(floor)
|
|
return floors
|
|
|
|
|
|
def create_rooms(floors):
|
|
"""Create rooms/clinical spaces per floor for hospitals."""
|
|
rooms = []
|
|
|
|
room_types_by_building = {
|
|
'inpatient': ['patient_room', 'nurse_station', 'icu_room', 'isolation', 'negative_pressure',
|
|
'storage', 'restroom', 'mechanical', 'electrical', 'server'],
|
|
'outpatient': ['consult_room', 'clinic_room', 'procedure_room', 'pharmacy_dispensing',
|
|
'waiting', 'triage', 'restroom', 'storage', 'server'],
|
|
'emergency': ['triage', 'resuscitation', 'fast_track', 'isolation', 'ct_room',
|
|
'xray_room', 'waiting', 'nurse_station', 'restroom', 'storage'],
|
|
'surgical': ['operating_room', 'preop', 'recovery', 'cssd', 'sterilization',
|
|
'anesthesia_storage', 'restroom', 'mechanical', 'electrical'],
|
|
'diagnostic': ['mri_room', 'ct_room', 'xray_room', 'ultrasound', 'mammo_room',
|
|
'waiting', 'control_room', 'restroom', 'storage'],
|
|
'lab': ['lab_blood', 'lab_micro', 'lab_chemistry', 'lab_pathology',
|
|
'blood_bank', 'storage', 'restroom', 'mechanical'],
|
|
'maternity': ['ld_room', 'nicu', 'picu', 'patient_room', 'nurse_station',
|
|
'isolation', 'restroom', 'storage'],
|
|
'support': ['cssd', 'laundry', 'kitchen', 'storage', 'mechanical', 'electrical'],
|
|
'utility': ['mechanical', 'electrical', 'medical_gas', 'boiler', 'chiller', 'storage'],
|
|
'parking': ['security', 'mechanical', 'electrical'],
|
|
'office': ['office', 'meeting_room', 'server', 'restroom', 'storage'],
|
|
'other': ['prayer_room', 'restroom', 'storage'],
|
|
}
|
|
|
|
special_rooms = {
|
|
'RHF-ED': [
|
|
{'room_number': 'G001', 'name': 'منطقة الفرز (Triage Area)', 'room_type': 'triage'},
|
|
{'room_number': 'G002', 'name': 'غرفة الإنعاش 1 (Resuscitation 1)', 'room_type': 'resuscitation'},
|
|
{'room_number': 'G003', 'name': 'غرفة الإنعاش 2 (Resuscitation 2)', 'room_type': 'resuscitation'},
|
|
{'room_number': 'G004', 'name': 'غرفة الأشعة المقطعية (CT Room)', 'room_type': 'ct_room'},
|
|
{'room_number': 'G005', 'name': 'غرفة أشعة (X-Ray Room)', 'room_type': 'xray_room'},
|
|
],
|
|
'RHF-SURG': [
|
|
{'room_number': '201', 'name': 'غرفة عمليات 1 (Operating Room 1)', 'room_type': 'operating_room'},
|
|
{'room_number': '202', 'name': 'غرفة عمليات 2 (Operating Room 2)', 'room_type': 'operating_room'},
|
|
{'room_number': '203', 'name': 'منطقة التعقيم المركزي (CSSD)', 'room_type': 'cssd'},
|
|
{'room_number': '101', 'name': 'منطقة ما قبل العملية (Pre-Op)', 'room_type': 'preop'},
|
|
{'room_number': '102', 'name': 'منطقة الإفاقة (Recovery)', 'room_type': 'recovery'},
|
|
],
|
|
'RHF-DIAG': [
|
|
{'room_number': 'B001', 'name': 'غرفة الرنين المغناطيسي (MRI 1)', 'room_type': 'mri_room'},
|
|
{'room_number': 'B002', 'name': 'غرفة الرنين المغناطيسي (MRI 2)', 'room_type': 'mri_room'},
|
|
{'room_number': 'A101', 'name': 'غرفة الأشعة المقطعية (CT 1)', 'room_type': 'ct_room'},
|
|
{'room_number': 'A201', 'name': 'غرفة الماموغرام (Mammography)', 'room_type': 'mammo_room'},
|
|
{'room_number': 'A301', 'name': 'غرفة موجات فوق صوتية (Ultrasound)', 'room_type': 'ultrasound'},
|
|
],
|
|
'RHF-LAB': [
|
|
{'room_number': '201', 'name': 'مختبر الكيمياء (Chemistry Lab)', 'room_type': 'lab_chemistry'},
|
|
{'room_number': '202', 'name': 'مختبر الدم (Hematology Lab)', 'room_type': 'lab_blood'},
|
|
{'room_number': '203', 'name': 'بنك الدم (Blood Bank)', 'room_type': 'blood_bank'},
|
|
{'room_number': '204', 'name': 'مختبر الأحياء الدقيقة (Microbiology Lab)', 'room_type': 'lab_micro'},
|
|
],
|
|
'RHF-MCH': [
|
|
{'room_number': '501', 'name': 'غرفة الولادة (L&D 1)', 'room_type': 'ld_room'},
|
|
{'room_number': '502', 'name': 'غرفة الولادة (L&D 2)', 'room_type': 'ld_room'},
|
|
{'room_number': '301', 'name': 'العناية المركزة لحديثي الولادة (NICU)', 'room_type': 'nicu'},
|
|
],
|
|
'RHF-MOSQ': [
|
|
{'room_number': '001', 'name': 'صالة الصلاة الرئيسية (Main Prayer Hall)', 'room_type': 'prayer_room'},
|
|
{'room_number': '002', 'name': 'قسم النساء (Women\'s Area)', 'room_type': 'prayer_room'},
|
|
{'room_number': '003', 'name': 'غرفة الإمام (Imam Room)', 'room_type': 'office'},
|
|
{'room_number': '004', 'name': 'مغاسل/وضوء (Ablution)', 'room_type': 'restroom'},
|
|
],
|
|
}
|
|
|
|
for floor in floors:
|
|
b = floor.building
|
|
btype = b.building_type
|
|
|
|
# baseline room count proportional to area
|
|
if floor.area_sqm:
|
|
num_rooms = int(floor.area_sqm / Decimal('120.0'))
|
|
num_rooms = min(max(num_rooms, 6), 40)
|
|
else:
|
|
num_rooms = random.randint(6, 20)
|
|
|
|
# Inject special rooms on key buildings (mostly ground & clinical floors)
|
|
if b.code in special_rooms and floor.floor_number in [0, 1, 2]:
|
|
for sr in special_rooms[b.code]:
|
|
room, _ = Room.objects.get_or_create(
|
|
floor=floor,
|
|
room_number=sr['room_number'],
|
|
defaults={
|
|
'name': sr['name'],
|
|
'room_type': sr['room_type'],
|
|
'area_sqm': Decimal(str(random.uniform(25, 160))),
|
|
'capacity': random.randint(1, 12),
|
|
'occupancy_status': 'occupied',
|
|
'is_accessible': True,
|
|
'has_hvac': True,
|
|
'has_electrical': True,
|
|
'has_plumbing': sr['room_type'] in ['restroom','cssd','sterilization','ld_room'],
|
|
'has_internet': True
|
|
}
|
|
)
|
|
num_rooms = max(5, num_rooms - len(special_rooms[b.code]))
|
|
|
|
# available room types
|
|
rtypes = room_types_by_building.get(btype, room_types_by_building['other'])
|
|
|
|
for i in range(1, num_rooms + 1):
|
|
floor_prefix = f'B{abs(floor.floor_number)}' if floor.floor_number < 0 else f'{floor.floor_number}'
|
|
room_number = f'{floor_prefix}{i:03d}'
|
|
rtype = random.choice(rtypes)
|
|
|
|
# Name by type
|
|
name_map = {
|
|
'patient_room': 'غرفة مريض',
|
|
'icu_room': 'غرفة عناية مركزة',
|
|
'nurse_station': 'محطة تمريض',
|
|
'isolation': 'غرفة عزل',
|
|
'negative_pressure': 'غرفة ضغط سلبي',
|
|
'operating_room': 'غرفة عمليات',
|
|
'preop': 'منطقة ما قبل العملية',
|
|
'recovery': 'منطقة الإفاقة',
|
|
'cssd': 'التعقيم المركزي',
|
|
'sterilization': 'غرفة التعقيم',
|
|
'triage': 'الفرز',
|
|
'resuscitation': 'إنعاش',
|
|
'fast_track': 'علاج سريع',
|
|
'ct_room': 'غرفة أشعة مقطعية',
|
|
'xray_room': 'غرفة أشعة',
|
|
'mri_room': 'غرفة رنين مغناطيسي',
|
|
'ultrasound': 'غرفة موجات فوق صوتية',
|
|
'mammo_room': 'غرفة ماموغرام',
|
|
'lab_blood': 'مختبر دم',
|
|
'lab_micro': 'مختبر أحياء دقيقة',
|
|
'lab_chemistry': 'مختبر كيمياء',
|
|
'blood_bank': 'بنك الدم',
|
|
'ld_room': 'غرفة ولادة',
|
|
'nicu': 'العناية المركزة لحديثي الولادة',
|
|
'picu': 'عناية مركزة أطفال',
|
|
'pharmacy_dispensing': 'صرف الأدوية',
|
|
'clinic_room': 'غرفة عيادة',
|
|
'consult_room': 'غرفة استشارة',
|
|
'waiting': 'منطقة انتظار',
|
|
'prayer_room': 'مصلى',
|
|
'anesthesia_storage': 'مخزن تخدير',
|
|
'medical_gas': 'غرفة غازات طبية',
|
|
}
|
|
base_name = name_map.get(rtype, rtype.replace('_',' ').title())
|
|
name = f'{base_name} {i}' if rtype not in ['nurse_station','cssd','sterilization','medical_gas'] else base_name
|
|
|
|
# Area/capacity by type
|
|
if rtype in ['operating_room']:
|
|
area = Decimal(str(random.uniform(45, 65)))
|
|
capacity = 6
|
|
elif rtype in ['icu_room','isolation','negative_pressure','ld_room']:
|
|
area = Decimal(str(random.uniform(22, 35)))
|
|
capacity = 2
|
|
elif rtype in ['patient_room','clinic_room','consult_room']:
|
|
area = Decimal(str(random.uniform(16, 28)))
|
|
capacity = 2
|
|
elif rtype in ['ct_room','xray_room','mri_room','mammo_room','ultrasound','lab_blood','lab_micro','lab_chemistry','blood_bank','cssd','sterilization']:
|
|
area = Decimal(str(random.uniform(20, 55)))
|
|
capacity = 4
|
|
elif rtype in ['preop','recovery','triage','resuscitation','fast_track','waiting']:
|
|
area = Decimal(str(random.uniform(18, 50)))
|
|
capacity = random.randint(4, 20)
|
|
else:
|
|
area = Decimal(str(random.uniform(12, 40)))
|
|
capacity = random.randint(1, 8)
|
|
|
|
has_plumbing = rtype in ['patient_room','icu_room','isolation','negative_pressure','ld_room','cssd','sterilization','lab_blood','lab_micro','lab_chemistry','blood_bank']
|
|
has_internet = True
|
|
|
|
room, _ = Room.objects.get_or_create(
|
|
floor=floor,
|
|
room_number=room_number,
|
|
defaults={
|
|
'name': name,
|
|
'room_type': rtype,
|
|
'area_sqm': area,
|
|
'capacity': capacity,
|
|
'occupancy_status': random.choice(['VACANT','OCCUPIED','MAINTENANCE']),
|
|
'is_accessible': rtype not in ['mechanical','electrical','server'],
|
|
# 'lease_start_date': None,
|
|
# 'lease_end_date': None,
|
|
# 'monthly_rent': None,
|
|
# 'has_hvac': True,
|
|
# 'has_electrical': True,
|
|
# 'has_plumbing': has_plumbing,
|
|
# 'has_internet': has_internet,
|
|
'notes': ''
|
|
}
|
|
)
|
|
rooms.append(room)
|
|
return rooms
|
|
|
|
|
|
def create_asset_categories():
|
|
"""Create hospital asset categories (clinical + facility)."""
|
|
categories = [
|
|
# MEDICAL (prefix MED- / LAB-)
|
|
{'name': 'MRI Scanners', 'code': 'MED-MRI', 'description': 'Magnetic Resonance Imaging systems'},
|
|
{'name': 'CT Scanners', 'code': 'MED-CT', 'description': 'Computed Tomography systems'},
|
|
{'name': 'X-Ray Systems', 'code': 'MED-XR', 'description': 'General radiography equipment'},
|
|
{'name': 'Ultrasound Machines', 'code': 'MED-US', 'description': 'Ultrasound imaging devices'},
|
|
{'name': 'Mammography Units', 'code': 'MED-MAM', 'description': 'Breast imaging systems'},
|
|
{'name': 'Ventilators', 'code': 'MED-VNT', 'description': 'Mechanical ventilation units'},
|
|
{'name': 'Anesthesia Machines', 'code': 'MED-ANES', 'description': 'OR anesthesia delivery systems'},
|
|
{'name': 'Patient Monitors', 'code': 'MED-MON', 'description': 'Bedside multiparameter monitors'},
|
|
{'name': 'Infusion Pumps', 'code': 'MED-INF', 'description': 'Infusion and syringe pumps'},
|
|
{'name': 'Defibrillators', 'code': 'MED-DEF', 'description': 'Defibrillators & AEDs'},
|
|
{'name': 'Operating Tables', 'code': 'MED-ORTB', 'description': 'Surgical operating tables'},
|
|
{'name': 'OR Lights', 'code': 'MED-ORLT', 'description': 'Surgical lights'},
|
|
{'name': 'Autoclaves/Sterilizers', 'code': 'MED-STER', 'description': 'Steam sterilizers for CSSD'},
|
|
{'name': 'Blood Bank Refrigerators','code': 'MED-BBFR', 'description': 'Blood storage refrigerators'},
|
|
{'name': 'Dialysis Machines', 'code': 'MED-DIAL', 'description': 'Hemodialysis systems'},
|
|
{'name': 'Medical Gas Systems', 'code': 'MED-GAS', 'description': 'O2/Vac/Air manifolds & alarms'},
|
|
{'name': 'Automated Dispensing', 'code': 'MED-ADISP', 'description': 'Pharmacy dispensing cabinets'},
|
|
{'name': 'Lab Analyzers', 'code': 'LAB-ANLYZ', 'description': 'Clinical lab analyzers'},
|
|
|
|
# IT & COMMS (hospital)
|
|
{'name': 'PACS', 'code': 'IT-PACS', 'description': 'Picture Archiving & Communication'},
|
|
{'name': 'Hospital Information System','code':'IT-HIS', 'description': 'Core HIS/EHR servers'},
|
|
{'name': 'Nurse Call', 'code': 'IT-NCALL', 'description': 'Nurse call systems'},
|
|
{'name': 'Pneumatic Tube System', 'code': 'IT-PTS', 'description': 'Hospital pneumatic tube network'},
|
|
|
|
# FACILITY (reuse many of yours)
|
|
{'name': 'Chillers', 'code': 'HVAC-CHL', 'description': 'Water chillers for AC'},
|
|
{'name': 'Air Handling Units', 'code': 'HVAC-AHU', 'description': 'AHUs'},
|
|
{'name': 'Cooling Towers', 'code': 'HVAC-CT', 'description': 'Cooling towers'},
|
|
{'name': 'Split AC Units', 'code': 'HVAC-SPLIT','description': 'Split AC'},
|
|
{'name': 'VRF Systems', 'code': 'HVAC-VRF', 'description': 'VRF systems'},
|
|
|
|
{'name': 'Generators', 'code': 'ELEC-GEN', 'description': 'Backup generators'},
|
|
{'name': 'Transformers', 'code': 'ELEC-TRAN', 'description': 'Power transformers'},
|
|
{'name': 'UPS Systems', 'code': 'ELEC-UPS', 'description': 'UPS'},
|
|
{'name': 'Distribution Boards', 'code': 'ELEC-DB', 'description': 'Electrical DBs'},
|
|
{'name': 'Lighting Systems', 'code': 'ELEC-LIGHT','description': 'Lighting systems'},
|
|
|
|
{'name': 'Water Pumps', 'code': 'PLMB-PUMP', 'description': 'Water pumps'},
|
|
{'name': 'Water Tanks', 'code': 'PLMB-TANK', 'description': 'Water tanks'},
|
|
{'name': 'Sanitary Fixtures', 'code': 'PLMB-SAN', 'description': 'Sanitary fixtures'},
|
|
|
|
{'name': 'Fire Alarm Systems', 'code': 'FIRE-ALARM','description': 'Fire alarm'},
|
|
{'name': 'Fire Sprinklers', 'code': 'FIRE-SPR', 'description': 'Sprinklers'},
|
|
{'name': 'Fire Extinguishers', 'code': 'FIRE-EXT', 'description': 'Extinguishers'},
|
|
|
|
{'name': 'Elevators', 'code': 'TRAN-ELEV', 'description': 'Elevators'},
|
|
{'name': 'Escalators', 'code': 'TRAN-ESC', 'description': 'Escalators'},
|
|
|
|
{'name': 'CCTV Systems', 'code': 'SEC-CCTV', 'description': 'Surveillance systems'},
|
|
{'name': 'Access Control', 'code': 'SEC-ACC', 'description': 'Access control'},
|
|
|
|
{'name': 'Network Equipment', 'code': 'IT-NET', 'description': 'Network routers/switches'},
|
|
{'name': 'Servers', 'code': 'IT-SRV', 'description': 'Servers'},
|
|
|
|
{'name': 'Seating', 'code': 'FURN-SEAT', 'description': 'Waiting area seating'},
|
|
{'name': 'Counters', 'code': 'FURN-COUNT','description': 'Reception/Pharmacy counters'},
|
|
{'name': 'Signage', 'code': 'FURN-SIGN', 'description': 'Wayfinding signage'},
|
|
]
|
|
|
|
out = []
|
|
for c in categories:
|
|
cat, _ = AssetCategory.objects.get_or_create(
|
|
code=c['code'],
|
|
defaults={'name': c['name'], 'description': c['description'], 'is_active': True}
|
|
)
|
|
out.append(cat)
|
|
return out
|
|
|
|
|
|
def create_assets(buildings, floors, rooms, asset_categories, users):
|
|
"""Create hospital assets mapped to clinical spaces and facility systems."""
|
|
assets = []
|
|
|
|
# quick lookups
|
|
cat = {c.code: c for c in asset_categories}
|
|
|
|
manufacturers = {
|
|
# Clinical
|
|
'MED-MRI': ['Siemens Healthineers','GE Healthcare','Philips','Canon Medical'],
|
|
'MED-CT': ['Siemens Healthineers','GE Healthcare','Philips','Canon Medical'],
|
|
'MED-XR': ['GE Healthcare','Philips','Siemens Healthineers','Shimadzu'],
|
|
'MED-US': ['GE Healthcare','Philips','Siemens Healthineers','Mindray','Samsung Medison'],
|
|
'MED-MAM': ['Hologic','GE Healthcare','Siemens Healthineers','Fujifilm'],
|
|
'MED-VNT': ['Dräger','GE Healthcare','Hamilton Medical','Mindray','Philips'],
|
|
'MED-ANES': ['Dräger','GE Healthcare','Mindray','Fabius'],
|
|
'MED-MON': ['Philips','GE Healthcare','Mindray','Nihon Kohden'],
|
|
'MED-INF': ['B. Braun','BD','Mindray','Fresenius Kabi'],
|
|
'MED-DEF': ['Zoll','Philips','Physio-Control','Mindray'],
|
|
'MED-ORTB': ['Maquet','Getinge','Stryker','Hillrom'],
|
|
'MED-ORLT': ['Stryker','Dräger','Getinge'],
|
|
'MED-STER': ['Getinge','Steris','Belimed'],
|
|
'MED-BBFR': ['Helmer Scientific','Thermo Fisher','PHCbi'],
|
|
'MED-DIAL': ['Fresenius Medical Care','B. Braun','Nipro'],
|
|
'MED-GAS': ['BeaconMedaes','Atlas Copco','Parker','Ceodeux'],
|
|
'MED-ADISP':['BD Pyxis','Omnicell'],
|
|
|
|
'LAB-ANLYZ':['Roche','Abbott','Siemens Healthineers','Beckman Coulter'],
|
|
|
|
# IT
|
|
'IT-PACS': ['Agfa','INFINITT','Sectra','GE Healthcare'],
|
|
'IT-HIS': ['InterSystems','Cerner/Oracle','Epic','Dedalus'],
|
|
'IT-NCALL': ['Ascom','Hillrom','Rauland','Jeron'],
|
|
'IT-PTS': ['Swisslog','TransLogic'],
|
|
|
|
# Facility
|
|
'HVAC-CHL': ['Carrier','Trane','York','Daikin','Zamil'],
|
|
'HVAC-AHU': ['Carrier','Trane','York','Al Salem Johnson Controls','Zamil'],
|
|
'HVAC-CT': ['Baltimore Aircoil','Evapco','Marley','Zamil'],
|
|
'HVAC-SPLIT':['Carrier','LG','Samsung','Zamil','Gree'],
|
|
'HVAC-VRF': ['Daikin','Mitsubishi Electric','LG','Samsung'],
|
|
|
|
'ELEC-GEN': ['Caterpillar','Cummins','MTU','Perkins','Kohler'],
|
|
'ELEC-TRAN':['ABB','Siemens','Schneider Electric','GE','Saudi Transformers'],
|
|
'ELEC-UPS': ['APC','Eaton','Schneider Electric','Emerson','ABB'],
|
|
'ELEC-DB': ['ABB','Schneider Electric','Siemens','Legrand','Hager'],
|
|
'ELEC-LIGHT':['Philips','Osram','Zumtobel','Fagerhult','Lucibel'],
|
|
|
|
'PLMB-PUMP':['Grundfos','Wilo','KSB','Xylem','Ebara'],
|
|
'PLMB-TANK':['National Plastic','United Steel Industrial','Dutco Tennant'],
|
|
'PLMB-SAN': ['Kohler','American Standard','TOTO','Grohe','Roca'],
|
|
|
|
'FIRE-ALARM':['Siemens','Honeywell','Johnson Controls','Bosch','Simplex'],
|
|
'FIRE-SPR': ['Tyco','Viking','Reliable','Globe','Victaulic'],
|
|
'FIRE-EXT': ['Amerex','Badger','Kidde','Buckeye','Saudi Fire Equipment'],
|
|
|
|
'TRAN-ELEV':['KONE','Otis','Schindler','ThyssenKrupp','Mitsubishi Electric'],
|
|
'TRAN-ESC': ['KONE','Otis','Schindler','ThyssenKrupp','Mitsubishi Electric'],
|
|
|
|
'SEC-CCTV': ['Axis','Bosch','Hikvision','Hanwha Techwin','Avigilon'],
|
|
'SEC-ACC': ['HID','Lenel','Honeywell','Johnson Controls','Bosch'],
|
|
|
|
'IT-NET': ['Cisco','HPE','Juniper','Huawei','Arista'],
|
|
'IT-SRV': ['Dell EMC','HPE','IBM','Lenovo','Oracle'],
|
|
}
|
|
|
|
# helper for assignment
|
|
def assigned_user_for(code):
|
|
if code.startswith('MED') or code.startswith('LAB'):
|
|
return users['ibrahim_bme']
|
|
if code.startswith('HVAC'):
|
|
return users['fahad_hvac']
|
|
if code.startswith('ELEC'):
|
|
return users['khalid_elec']
|
|
if code.startswith('IT'):
|
|
return users['ali_it']
|
|
if code.startswith('SEC'):
|
|
return users['saad_safety']
|
|
return users['abdullah_main']
|
|
|
|
# convenience: rooms by type
|
|
rooms_by_type = {}
|
|
for r in rooms:
|
|
rooms_by_type.setdefault(r.room_type, []).append(r)
|
|
|
|
# 1) Facility backbone per building (similar to your airport logic)
|
|
for b in buildings:
|
|
b_rooms = [r for r in rooms if r.floor.building == b]
|
|
b_floors = [f for f in floors if f.building == b]
|
|
|
|
# Chillers (for most clinical/support/office buildings)
|
|
if b.building_type in ['inpatient','outpatient','emergency','surgical','diagnostic','lab','maternity','support','office','utility']:
|
|
num_chillers = max(1, int(float(b.total_area_sqm or 20000) / 20000))
|
|
for i in range(1, num_chillers + 1):
|
|
code = 'HVAC-CHL'
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
model = f"{manuf} {random.choice([500,750,1000,1500])}RT"
|
|
asset_id = f"{b.code}-CHLR-{i:02d}"
|
|
mech = [r for r in b_rooms if r.room_type in ['mechanical','chiller'] and r.floor.floor_number <= 0]
|
|
room = random.choice(mech) if mech else None
|
|
floor = room.floor if room else b_floors[0]
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} Chiller {i}",
|
|
category=cat[code], building=b, floor=floor, room=room,
|
|
location_description="Chiller Plant" if not room else "",
|
|
manufacturer=manuf, model=model, serial_number=f"CHR{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365,3650)),
|
|
purchase_cost=Decimal(str(random.uniform(200000, 600000))),
|
|
current_value=Decimal(str(random.uniform(100000, 450000))),
|
|
status='operational', condition=random.choice(['excellent','good','good','fair']),
|
|
assigned_to=assigned_user_for(code)
|
|
))
|
|
|
|
# AHUs
|
|
code = 'HVAC-AHU'
|
|
if cat.get(code):
|
|
num_ahus = max(2, int(float(b.total_area_sqm or 10000) / 5000))
|
|
for i in range(1, num_ahus + 1):
|
|
manuf = random.choice(manufacturers[code])
|
|
model = f"{manuf} Series {random.randint(1000,9999)}"
|
|
asset_id = f"{b.code}-AHU-{i:02d}"
|
|
floor = random.choice(b_floors)
|
|
mech_on_floor = [r for r in b_rooms if r.room_type == 'mechanical' and r.floor == floor]
|
|
room = random.choice(mech_on_floor) if mech_on_floor else None
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} AHU {i}",
|
|
category=cat[code], building=b, floor=floor, room=room,
|
|
location_description=f"AHU {floor.floor_number}-{i}" if not room else "",
|
|
manufacturer=manuf, model=model, serial_number=f"AHU{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2920)),
|
|
purchase_cost=Decimal(str(random.uniform(60000, 180000))),
|
|
current_value=Decimal(str(random.uniform(30000, 110000))),
|
|
status=random.choices(['operational','maintenance'], weights=[0.9,0.1])[0],
|
|
condition=random.choice(['excellent','good','good','fair']),
|
|
assigned_to=assigned_user_for(code)
|
|
))
|
|
|
|
# Generators
|
|
code = 'ELEC-GEN'
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
model = f"{manuf} {random.choice([750,1000,1500,2000])}kVA"
|
|
asset_id = f"{b.code}-GEN-01"
|
|
elec_rooms = [r for r in b_rooms if r.room_type == 'electrical' and r.floor.floor_number <= 0]
|
|
room = random.choice(elec_rooms) if elec_rooms else None
|
|
floor = room.floor if room else b_floors[0]
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} Backup Generator",
|
|
category=cat[code], building=b, floor=floor, room=room,
|
|
location_description="Emergency Generator Room" if not room else "",
|
|
manufacturer=manuf, model=model, serial_number=f"GEN{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365,3650)),
|
|
purchase_cost=Decimal(str(random.uniform(250000, 600000))),
|
|
current_value=Decimal(str(random.uniform(120000, 450000))),
|
|
status='operational', condition=random.choice(['excellent','good','good','fair']),
|
|
assigned_to=assigned_user_for(code)
|
|
))
|
|
|
|
# Elevators (multi-storey)
|
|
if b.floor_count > 1 and cat.get('TRAN-ELEV'):
|
|
num_elev = max(2, int(b.floor_count / 2))
|
|
for i in range(1, num_elev + 1):
|
|
manuf = random.choice(manufacturers['TRAN-ELEV'])
|
|
asset_id = f"{b.code}-ELV-{i:02d}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} Elevator {i}",
|
|
category=cat['TRAN-ELEV'], building=b,
|
|
location_description=f"Elevator Core {i}",
|
|
manufacturer=manuf, model=f"{manuf} {random.choice(['Passenger','Service'])}",
|
|
serial_number=f"ELV{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365,3650)),
|
|
purchase_cost=Decimal(str(random.uniform(150000, 350000))),
|
|
current_value=Decimal(str(random.uniform(80000, 250000))),
|
|
status=random.choices(['operational','maintenance'], weights=[0.9,0.1])[0],
|
|
condition=random.choice(['excellent','good','good','fair']),
|
|
assigned_to=users['ibrahim_bme'],
|
|
warranty_start_date=timezone.now().date() - timedelta(days=random.randint(365,1825)),
|
|
warranty_end_date=timezone.now().date() + timedelta(days=random.randint(-365,1825)),
|
|
service_provider=random.choice(["KONE Saudi Arabia","Otis Saudi Arabia","Schindler Saudi"])
|
|
))
|
|
|
|
# Pneumatic Tube System (replace airport BHS)
|
|
if b.building_type in ['inpatient','outpatient','emergency','lab'] and cat.get('IT-PTS'):
|
|
asset_id = f"{b.code}-PTS-01"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
manuf = random.choice(manufacturers['IT-PTS'])
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} Pneumatic Tube System",
|
|
category=cat['IT-PTS'], building=b, location_description="Hospital-wide network",
|
|
manufacturer=manuf, model=f"{manuf} PTS",
|
|
serial_number=f"PTS{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2190)),
|
|
purchase_cost=Decimal(str(random.uniform(1500000, 4000000))),
|
|
current_value=Decimal(str(random.uniform(900000, 3000000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ali_it'],
|
|
service_provider=manuf + " Services"
|
|
))
|
|
|
|
# Nurse Call (building level)
|
|
if b.building_type in ['inpatient','maternity'] and cat.get('IT-NCALL'):
|
|
asset_id = f"{b.code}-NCALL-01"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
manuf = random.choice(manufacturers['IT-NCALL'])
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} Nurse Call System",
|
|
category=cat['IT-NCALL'], building=b, location_description="Wards & ICU",
|
|
manufacturer=manuf, model=f"{manuf} NurseCall",
|
|
serial_number=f"NCL{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2190)),
|
|
purchase_cost=Decimal(str(random.uniform(500000, 1500000))),
|
|
current_value=Decimal(str(random.uniform(300000, 1000000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ali_it']
|
|
))
|
|
|
|
# PACS/HIS (IT backbone)
|
|
if b.code.endswith('DIAG') and cat.get('IT-PACS'):
|
|
asset_id = f"{b.code}-PACS-01"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
manuf = random.choice(manufacturers['IT-PACS'])
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} PACS",
|
|
category=cat['IT-PACS'], building=b, location_description="Data Center / Radiology",
|
|
manufacturer=manuf, model=f"{manuf} PACS Cluster",
|
|
serial_number=f"PACS{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2190)),
|
|
purchase_cost=Decimal(str(random.uniform(1200000, 4000000))),
|
|
current_value=Decimal(str(random.uniform(700000, 2500000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ali_it']
|
|
))
|
|
if b.code.endswith('ADMIN') and cat.get('IT-HIS'):
|
|
asset_id = f"{b.code}-HIS-01"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
manuf = random.choice(manufacturers['IT-HIS'])
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} HIS/EHR",
|
|
category=cat['IT-HIS'], building=b, location_description="Data Center",
|
|
manufacturer=manuf, model=f"{manuf} HIS",
|
|
serial_number=f"HIS{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2190)),
|
|
purchase_cost=Decimal(str(random.uniform(2000000, 6000000))),
|
|
current_value=Decimal(str(random.uniform(1200000, 4000000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ali_it']
|
|
))
|
|
|
|
# Security systems
|
|
if cat.get('SEC-CCTV'):
|
|
asset_id = f"{b.code}-CCTV-01"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
manuf = random.choice(manufacturers['SEC-CCTV'])
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} CCTV System",
|
|
category=cat['SEC-CCTV'], building=b, location_description="Throughout building",
|
|
manufacturer=manuf, model=f"{manuf} CCTV",
|
|
serial_number=f"CCTV{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2190)),
|
|
purchase_cost=Decimal(str(random.uniform(200000, 1000000))),
|
|
current_value=Decimal(str(random.uniform(120000, 800000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['saad_safety'],
|
|
service_provider="Saudi Security Systems"
|
|
))
|
|
|
|
# 2) Clinical assets by room type
|
|
# Imaging
|
|
for room in rooms_by_type.get('mri_room', []):
|
|
code = 'MED-MRI'
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
asset_id = f"{room.floor.building.code}-MRI-{room.room_number}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"MRI - {room.room_number}",
|
|
category=cat[code], building=room.floor.building, floor=room.floor, room=room,
|
|
manufacturer=manuf, model=f"{manuf} 1.5T",
|
|
serial_number=f"MRI{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2920)),
|
|
purchase_cost=Decimal(str(random.uniform(4000000, 9000000))),
|
|
current_value=Decimal(str(random.uniform(2000000, 6000000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
for room in rooms_by_type.get('ct_room', []):
|
|
code = 'MED-CT'
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
asset_id = f"{room.floor.building.code}-CT-{room.room_number}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"CT - {room.room_number}",
|
|
category=cat[code], building=room.floor.building, floor=room.floor, room=room,
|
|
manufacturer=manuf, model=f"{manuf} 128-slice",
|
|
serial_number=f"CT{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2920)),
|
|
purchase_cost=Decimal(str(random.uniform(2500000, 6000000))),
|
|
current_value=Decimal(str(random.uniform(1200000, 4000000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
for room in rooms_by_type.get('xray_room', []):
|
|
code = 'MED-XR'
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
asset_id = f"{room.floor.building.code}-XR-{room.room_number}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"X-Ray - {room.room_number}",
|
|
category=cat[code], building=room.floor.building, floor=room.floor, room=room,
|
|
manufacturer=manuf, model=f"{manuf} DR System",
|
|
serial_number=f"XR{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2920)),
|
|
purchase_cost=Decimal(str(random.uniform(800000, 1800000))),
|
|
current_value=Decimal(str(random.uniform(400000, 1200000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
|
|
# OR equipment
|
|
for room in rooms_by_type.get('operating_room', []):
|
|
for code, name, model in [
|
|
('MED-ANES', 'Anesthesia Machine', 'Anesthesia System'),
|
|
('MED-ORTB', 'Operating Table', 'Surgical Table'),
|
|
('MED-ORLT', 'OR Light', 'Surgical Light'),
|
|
('MED-MON', 'Patient Monitor', 'Multiparameter Monitor'),
|
|
]:
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
asset_id = f"{room.floor.building.code}-{code.split('-')[1]}-{room.room_number}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{name} - {room.room_number}",
|
|
category=cat[code], building=room.floor.building, floor=room.floor, room=room,
|
|
manufacturer=manuf, model=f"{manuf} {model}",
|
|
serial_number=f"{code.replace('-','')}{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 3650)),
|
|
purchase_cost=Decimal(str(random.uniform(80000, 300000))),
|
|
current_value=Decimal(str(random.uniform(40000, 200000))),
|
|
status='operational', condition=random.choice(['excellent','good','good','fair']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
|
|
# ICU
|
|
for room in rooms_by_type.get('icu_room', []):
|
|
for code in ['MED-VNT','MED-MON','MED-INF','MED-DEF']:
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
asset_id = f"{room.floor.building.code}-{code.split('-')[1]}-{room.room_number}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{code.split('-')[1]} - {room.room_number}",
|
|
category=cat[code], building=room.floor.building, floor=room.floor, room=room,
|
|
manufacturer=manuf, model=f"{manuf} Unit",
|
|
serial_number=f"{code.replace('-','')}{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 3650)),
|
|
purchase_cost=Decimal(str(random.uniform(15000, 90000))),
|
|
current_value=Decimal(str(random.uniform(8000, 60000))),
|
|
status='operational', condition=random.choice(['excellent','good','good','fair']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
|
|
# CSSD & Blood Bank & Pharmacy
|
|
for room in rooms_by_type.get('cssd', []):
|
|
code = 'MED-STER'
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
asset_id = f"{room.floor.building.code}-STER-{room.room_number}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"Autoclave - {room.room_number}",
|
|
category=cat[code], building=room.floor.building, floor=room.floor, room=room,
|
|
manufacturer=manuf, model=f"{manuf} Steam Sterilizer",
|
|
serial_number=f"STER{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 3650)),
|
|
purchase_cost=Decimal(str(random.uniform(200000, 600000))),
|
|
current_value=Decimal(str(random.uniform(120000, 400000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
for room in rooms_by_type.get('blood_bank', []):
|
|
code = 'MED-BBFR'
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
asset_id = f"{room.floor.building.code}-BBFR-{room.room_number}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"Blood Bank Fridge - {room.room_number}",
|
|
category=cat[code], building=room.floor.building, floor=room.floor, room=room,
|
|
manufacturer=manuf, model=f"{manuf} BB Fridge",
|
|
serial_number=f"BBF{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2920)),
|
|
purchase_cost=Decimal(str(random.uniform(40000, 120000))),
|
|
current_value=Decimal(str(random.uniform(20000, 90000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
for room in rooms_by_type.get('pharmacy_dispensing', []):
|
|
code = 'MED-ADISP'
|
|
if cat.get(code):
|
|
manuf = random.choice(manufacturers[code])
|
|
asset_id = f"{room.floor.building.code}-ADISP-{room.room_number}"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"Automated Dispensing - {room.room_number}",
|
|
category=cat[code], building=room.floor.building, floor=room.floor, room=room,
|
|
manufacturer=manuf, model=f"{manuf} ADC",
|
|
serial_number=f"ADC{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 2920)),
|
|
purchase_cost=Decimal(str(random.uniform(250000, 600000))),
|
|
current_value=Decimal(str(random.uniform(120000, 400000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
|
|
# Medical Gas manifolds in utility/support
|
|
for b in buildings:
|
|
if b.building_type in ['utility','support'] and cat.get('MED-GAS'):
|
|
asset_id = f"{b.code}-MGAS-01"
|
|
if not Asset.objects.filter(asset_id=asset_id).exists():
|
|
manuf = random.choice(manufacturers['MED-GAS'])
|
|
assets.append(Asset.objects.create(
|
|
asset_id=asset_id, name=f"{b.name} Medical Gas Manifold",
|
|
category=cat['MED-GAS'], building=b, location_description="MGPS Room",
|
|
manufacturer=manuf, model=f"{manuf} MGPS",
|
|
serial_number=f"MGAS{random.randint(100000,999999)}",
|
|
purchase_date=timezone.now().date() - timedelta(days=random.randint(365, 3650)),
|
|
purchase_cost=Decimal(str(random.uniform(300000, 900000))),
|
|
current_value=Decimal(str(random.uniform(180000, 700000))),
|
|
status='operational', condition=random.choice(['excellent','good']),
|
|
assigned_to=users['ibrahim_bme']
|
|
))
|
|
|
|
return assets
|
|
|
|
|
|
def create_maintenance_types():
|
|
"""Create maintenance types"""
|
|
maintenance_types_data = [
|
|
{
|
|
'name': 'Preventive Maintenance',
|
|
'code': 'PM',
|
|
'description': 'Regular scheduled maintenance to prevent breakdowns',
|
|
'estimated_duration_hours': Decimal('4.00')
|
|
},
|
|
{
|
|
'name': 'Corrective Maintenance',
|
|
'code': 'CM',
|
|
'description': 'Repairs to fix equipment failures or malfunctions',
|
|
'estimated_duration_hours': Decimal('6.00')
|
|
},
|
|
{
|
|
'name': 'Emergency Repair',
|
|
'code': 'ER',
|
|
'description': 'Immediate repairs for critical systems',
|
|
'estimated_duration_hours': Decimal('3.00')
|
|
},
|
|
{
|
|
'name': 'Safety Inspection',
|
|
'code': 'SI',
|
|
'description': 'Inspection to ensure safety compliance',
|
|
'estimated_duration_hours': Decimal('2.00')
|
|
},
|
|
{
|
|
'name': 'Overhaul',
|
|
'code': 'OH',
|
|
'description': 'Complete disassembly and renewal of equipment',
|
|
'estimated_duration_hours': Decimal('24.00')
|
|
},
|
|
{
|
|
'name': 'Cleaning',
|
|
'code': 'CL',
|
|
'description': 'Cleaning of equipment and systems',
|
|
'estimated_duration_hours': Decimal('2.00')
|
|
},
|
|
{
|
|
'name': 'Calibration',
|
|
'code': 'CB',
|
|
'description': 'Calibration of instruments and controls',
|
|
'estimated_duration_hours': Decimal('1.50')
|
|
},
|
|
{
|
|
'name': 'Software Update',
|
|
'code': 'SU',
|
|
'description': 'Update of system software and firmware',
|
|
'estimated_duration_hours': Decimal('2.00')
|
|
},
|
|
{
|
|
'name': 'Replacement',
|
|
'code': 'RP',
|
|
'description': 'Replacement of worn parts or components',
|
|
'estimated_duration_hours': Decimal('4.00')
|
|
},
|
|
{
|
|
'name': 'Inspection',
|
|
'code': 'IN',
|
|
'description': 'General inspection of equipment',
|
|
'estimated_duration_hours': Decimal('1.00')
|
|
}
|
|
]
|
|
|
|
maintenance_types = []
|
|
for data in maintenance_types_data:
|
|
maint_type, created = MaintenanceType.objects.get_or_create(
|
|
code=data['code'],
|
|
defaults={
|
|
'name': data['name'],
|
|
'description': data['description'],
|
|
'estimated_duration_hours': data['estimated_duration_hours'],
|
|
'is_active': True
|
|
}
|
|
)
|
|
maintenance_types.append(maint_type)
|
|
|
|
return maintenance_types
|
|
|
|
|
|
def create_maintenance_requests(buildings, floors, rooms, assets, maintenance_types, users):
|
|
"""Create hospital-flavored maintenance requests."""
|
|
requests = []
|
|
|
|
issues = [
|
|
{'title': 'جهاز التنفس يعرض إنذار (Ventilator Alarm)', 'description': 'إنذار ضغط عالي في جهاز التنفس بوحدة العناية. (High-pressure alarm on ICU ventilator.)', 'maintenance_type':'CM','priority':'urgent'},
|
|
{'title': 'تسرب أكسجين في MGPS (O₂ Leak)', 'description': 'رائحة أكسجين ملحوظة في غرفة الغازات الطبية. (Notable O₂ smell in MGPS room.)', 'maintenance_type':'ER','priority':'emergency'},
|
|
{'title': 'تعطل جهاز التعقيم (Autoclave Down)', 'description': 'تعطل جهاز التعقيم في CSSD. (Autoclave fault in CSSD.)', 'maintenance_type':'ER','priority':'high'},
|
|
{'title': 'انقطاع نظام نداء التمريض (Nurse Call Outage)', 'description': 'تعطل نقاط نداء التمريض في جناح 5B. (Nurse call endpoints down in ward 5B.)', 'maintenance_type':'CM','priority':'high'},
|
|
{'title': 'ارتفاع حرارة بنك الدم (Blood Bank Temp High)', 'description': 'درجة حرارة ثلاجة بنك الدم مرتفعة. (Blood bank fridge temp out of range.)', 'maintenance_type':'ER','priority':'urgent'},
|
|
{'title': 'خلل في جهاز CT (CT Fault)', 'description': 'رمز خطأ متكرر في جهاز الأشعة المقطعية. (Recurring fault code on CT.)', 'maintenance_type':'CM','priority':'high'},
|
|
{'title': 'مشكلة ضغط سلبي (Negative Pressure Issue)', 'description': 'عدم ثبات الضغط السلبي بغرفة العزل. (Negative pressure not holding.)', 'maintenance_type':'IN','priority':'medium'},
|
|
{'title': 'تسرب مياه (Water Leak)', 'description': 'تسرب من سقف جناح المرضى. (Ceiling water leak on inpatient floor.)', 'maintenance_type':'ER','priority':'urgent'},
|
|
{'title': 'إضاءة غرفة العمليات وميض (OR Light Flicker)', 'description': 'وميض في إضاءة غرفة العمليات. (OR lights flickering.)', 'maintenance_type':'CM','priority':'high'},
|
|
{'title': 'ضوضاء شديدة AHU (AHU Noise)', 'description': 'صوت غير طبيعي في AHU الطابق 3. (Unusual noise in AHU L3.)', 'maintenance_type':'IN','priority':'medium'},
|
|
{'title': 'توقف مصعد (Elevator Stopped)', 'description': 'المصعد رقم 2 متوقف. (Elevator #2 stopped.)', 'maintenance_type':'ER','priority':'high'},
|
|
{'title': 'تحديث برنامج PACS (PACS Update)', 'description': 'تحديث أمني عاجل لنظام PACS. (Urgent security patch for PACS.)', 'maintenance_type':'SU','priority':'medium'},
|
|
]
|
|
|
|
# asset-bound requests
|
|
for asset in random.sample(assets, min(60, len(assets))):
|
|
issue = random.choice(issues)
|
|
mtype = next((mt for mt in maintenance_types if mt.code == issue['maintenance_type']), maintenance_types[0])
|
|
status = random.choices(['completed','in_progress','assigned','submitted','on_hold'], weights=[0.35,0.2,0.1,0.25,0.1])[0]
|
|
|
|
requested_date = timezone.now() - timedelta(days=random.randint(1, 90))
|
|
scheduled_date = requested_date + timedelta(days=random.randint(1, 7)) if status != 'submitted' else None
|
|
started_date = scheduled_date + timedelta(hours=random.randint(1, 24)) if status in ['in_progress','completed'] else None
|
|
completed_date = started_date + timedelta(hours=random.randint(1, 48)) if status == 'completed' else None
|
|
|
|
# assign by category prefix
|
|
if asset.category.code.startswith('HVAC'):
|
|
assigned_to = users['fahad_hvac']
|
|
elif asset.category.code.startswith('ELEC'):
|
|
assigned_to = users['khalid_elec']
|
|
elif asset.category.code.startswith(('MED','LAB')):
|
|
assigned_to = users['ibrahim_bme']
|
|
elif asset.category.code.startswith('IT'):
|
|
assigned_to = users['ali_it']
|
|
elif asset.category.code.startswith('SEC'):
|
|
assigned_to = users['saad_safety']
|
|
else:
|
|
assigned_to = users['abdullah_main']
|
|
if status == 'submitted':
|
|
assigned_to = None
|
|
|
|
est = Decimal(str(random.uniform(800, 8000))) if status != 'submitted' else None
|
|
actual = est * Decimal(str(random.uniform(0.8, 1.25))) if status == 'completed' else None
|
|
|
|
request_id = f"MR-{timezone.now().strftime('%Y%m%d')}-{random.randint(1000, 9999)}"
|
|
if not MaintenanceRequest.objects.filter(request_id=request_id).exists():
|
|
requests.append(MaintenanceRequest.objects.create(
|
|
request_id=request_id, title=issue['title'], description=issue['description'],
|
|
maintenance_type=mtype, building=asset.building, floor=asset.floor, room=asset.room, asset=asset,
|
|
priority=issue['priority'], status=status,
|
|
requested_by=random.choice(list(users.values())), assigned_to=assigned_to,
|
|
requested_date=requested_date, scheduled_date=scheduled_date,
|
|
started_date=started_date, completed_date=completed_date,
|
|
estimated_cost=est, actual_cost=actual, notes='',
|
|
completion_notes=random.choice([
|
|
'تم الإصلاح والاختبار بنجاح. (Fixed and tested)',
|
|
'استبدال القطعة المعطلة. (Replaced faulty part)',
|
|
'ترقية البرنامج واستقرار النظام. (Patched and stabilized)'
|
|
]) if status == 'completed' else ''
|
|
))
|
|
|
|
# room-level (no specific asset)
|
|
for room in random.sample(rooms, min(40, len(rooms))):
|
|
issue = random.choice(issues)
|
|
mtype = next((mt for mt in maintenance_types if mt.code == issue['maintenance_type']), maintenance_types[0])
|
|
status = random.choices(['completed','in_progress','assigned','submitted','on_hold'], weights=[0.3,0.2,0.1,0.3,0.1])[0]
|
|
requested_date = timezone.now() - timedelta(days=random.randint(1, 60))
|
|
scheduled_date = requested_date + timedelta(days=random.randint(1, 5)) if status != 'submitted' else None
|
|
started_date = scheduled_date + timedelta(hours=random.randint(1, 24)) if status in ['in_progress','completed'] else None
|
|
completed_date = started_date + timedelta(hours=random.randint(1, 48)) if status == 'completed' else None
|
|
assigned_to = random.choice([users['abdullah_main'], users['ibrahim_bme'], users['fahad_hvac']]) if status != 'submitted' else None
|
|
|
|
request_id = f"MR-{timezone.now().strftime('%Y%m%d')}-{random.randint(1000, 9999)}"
|
|
if not MaintenanceRequest.objects.filter(request_id=request_id).exists():
|
|
requests.append(MaintenanceRequest.objects.create(
|
|
request_id=request_id, title=issue['title'], description=issue['description'],
|
|
maintenance_type=mtype, building=room.floor.building, floor=room.floor, room=room,
|
|
priority=issue['priority'], status=status,
|
|
requested_by=random.choice(list(users.values())), assigned_to=assigned_to,
|
|
requested_date=requested_date, scheduled_date=scheduled_date,
|
|
started_date=started_date, completed_date=completed_date, notes=''
|
|
))
|
|
return requests
|
|
|
|
|
|
def create_maintenance_schedules(assets, maintenance_types, users):
|
|
"""Create maintenance schedules"""
|
|
schedules = []
|
|
|
|
# Create scheduled maintenance for critical assets
|
|
critical_asset_types = ['HVAC-CHL', 'ELEC-GEN', 'TRAN-ELEV', 'TRAN-ESC', 'TRAN-BHS', 'FIRE-ALARM']
|
|
critical_assets = [a for a in assets if a.category.code in critical_asset_types]
|
|
|
|
for asset in critical_assets:
|
|
# Select appropriate maintenance type
|
|
if asset.category.code.startswith('HVAC'):
|
|
maint_type_code = 'PM'
|
|
frequency = random.choice(['quarterly', 'monthly'])
|
|
name = f"Scheduled HVAC Maintenance - {asset.name}"
|
|
elif asset.category.code.startswith('ELEC'):
|
|
maint_type_code = 'PM'
|
|
frequency = 'monthly'
|
|
name = f"Electrical System Maintenance - {asset.name}"
|
|
elif asset.category.code.startswith('TRAN'):
|
|
maint_type_code = 'PM'
|
|
frequency = 'monthly'
|
|
name = f"Transportation System Maintenance - {asset.name}"
|
|
elif asset.category.code.startswith('FIRE'):
|
|
maint_type_code = 'SI'
|
|
frequency = 'quarterly'
|
|
name = f"Fire System Safety Inspection - {asset.name}"
|
|
else:
|
|
maint_type_code = 'PM'
|
|
frequency = random.choice(['semi_annual', 'quarterly'])
|
|
name = f"Scheduled Maintenance - {asset.name}"
|
|
|
|
maint_type = next((mt for mt in maintenance_types if mt.code == maint_type_code), maintenance_types[0])
|
|
|
|
# Determine who's responsible
|
|
if asset.category.code.startswith('HVAC'):
|
|
assigned_to = users['fahad_hvac']
|
|
elif asset.category.code.startswith('ELEC'):
|
|
assigned_to = users['khalid_elec']
|
|
elif asset.category.code.startswith('TRAN'):
|
|
assigned_to = users['ibrahim_eng']
|
|
elif asset.category.code.startswith('FIRE'):
|
|
assigned_to = users['saad_safety']
|
|
else:
|
|
assigned_to = users['abdullah_main']
|
|
|
|
# Dates
|
|
start_date = timezone.now().date() - timedelta(days=random.randint(90, 365))
|
|
end_date = start_date + timedelta(days=365 * 2) # 2 year schedule
|
|
|
|
next_due_date = timezone.now().date() + timedelta(days=random.randint(1, 90))
|
|
|
|
# Check if schedule already exists for this asset
|
|
if not MaintenanceSchedule.objects.filter(asset=asset, name=name).exists():
|
|
schedule = MaintenanceSchedule.objects.create(
|
|
name=name,
|
|
description=f"Regular maintenance schedule for {asset.name}",
|
|
maintenance_type=maint_type,
|
|
asset=asset,
|
|
building=asset.building,
|
|
frequency=frequency,
|
|
frequency_interval=1,
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
assigned_to=assigned_to,
|
|
estimated_duration_hours=maint_type.estimated_duration_hours,
|
|
is_active=True,
|
|
last_generated_date=timezone.now().date() - timedelta(days=random.randint(30, 90)),
|
|
next_due_date=next_due_date
|
|
)
|
|
schedules.append(schedule)
|
|
|
|
# Create some building-wide maintenance schedules
|
|
for building in set(a.building for a in assets):
|
|
# HVAC Cleaning Schedule
|
|
name = f"{building.name} - HVAC System Cleaning"
|
|
maint_type = next((mt for mt in maintenance_types if mt.code == 'CL'), maintenance_types[0])
|
|
|
|
if not MaintenanceSchedule.objects.filter(building=building, name=name).exists():
|
|
schedule = MaintenanceSchedule.objects.create(
|
|
name=name,
|
|
description="Regular cleaning of all HVAC components including filters, coils, and ducts",
|
|
maintenance_type=maint_type,
|
|
building=building,
|
|
frequency='quarterly',
|
|
frequency_interval=1,
|
|
start_date=timezone.now().date() - timedelta(days=random.randint(90, 365)),
|
|
end_date=timezone.now().date() + timedelta(days=365 * 2),
|
|
assigned_to=users['fahad_hvac'],
|
|
estimated_duration_hours=Decimal('16.00'),
|
|
is_active=True,
|
|
next_due_date=timezone.now().date() + timedelta(days=random.randint(1, 90))
|
|
)
|
|
schedules.append(schedule)
|
|
|
|
# Fire Safety Inspection
|
|
name = f"{building.name} - Fire Safety Systems Inspection"
|
|
maint_type = next((mt for mt in maintenance_types if mt.code == 'SI'), maintenance_types[0])
|
|
|
|
if not MaintenanceSchedule.objects.filter(building=building, name=name).exists():
|
|
schedule = MaintenanceSchedule.objects.create(
|
|
name=name,
|
|
description="Comprehensive inspection of all fire safety systems including alarms, sprinklers, and extinguishers",
|
|
maintenance_type=maint_type,
|
|
building=building,
|
|
frequency='monthly',
|
|
frequency_interval=1,
|
|
start_date=timezone.now().date() - timedelta(days=random.randint(90, 365)),
|
|
end_date=timezone.now().date() + timedelta(days=365 * 2),
|
|
assigned_to=users['saad_safety'],
|
|
estimated_duration_hours=Decimal('8.00'),
|
|
is_active=True,
|
|
next_due_date=timezone.now().date() + timedelta(days=random.randint(1, 30))
|
|
)
|
|
schedules.append(schedule)
|
|
|
|
return schedules
|
|
|
|
|
|
def create_vendors():
|
|
"""Create service vendors"""
|
|
vendors_data = [
|
|
{
|
|
'name': 'Saudi HVAC Services Co.',
|
|
'vendor_type': 'hvac',
|
|
'contact_person': 'Mohammed Al-Harbi',
|
|
'email': 'info@saudihvac.com',
|
|
'phone': '+966 11 482 3456',
|
|
'address': 'Industrial Area, Riyadh, Saudi Arabia',
|
|
'license_number': 'LIC-22-45678',
|
|
'rating': Decimal('4.50')
|
|
},
|
|
{
|
|
'name': 'Al Faisal Maintenance',
|
|
'vendor_type': 'maintenance',
|
|
'contact_person': 'Abdullah Al-Faisal',
|
|
'email': 'info@alfaisalmaint.com.sa',
|
|
'phone': '+966 11 463 7890',
|
|
'address': 'King Fahd Road, Riyadh, Saudi Arabia',
|
|
'license_number': 'LIC-23-12345',
|
|
'rating': Decimal('4.20')
|
|
},
|
|
{
|
|
'name': 'Zamil Air Conditioners',
|
|
'vendor_type': 'hvac',
|
|
'contact_person': 'Khalid Al-Zamil',
|
|
'email': 'service@zamilac.com',
|
|
'phone': '+966 13 847 5678',
|
|
'address': 'Dammam Industrial City, Dammam, Saudi Arabia',
|
|
'license_number': 'LIC-19-34567',
|
|
'rating': Decimal('4.80')
|
|
},
|
|
{
|
|
'name': 'Saudi Electrical Services Ltd.',
|
|
'vendor_type': 'electrical',
|
|
'contact_person': 'Ibrahim Al-Saud',
|
|
'email': 'service@saudielectrical.com.sa',
|
|
'phone': '+966 12 667 8901',
|
|
'address': 'Industrial Zone, Jeddah, Saudi Arabia',
|
|
'license_number': 'LIC-21-56789',
|
|
'rating': Decimal('4.30')
|
|
},
|
|
{
|
|
'name': 'KONE Saudi Arabia',
|
|
'vendor_type': 'maintenance',
|
|
'contact_person': 'Omar Al-Tuwairqi',
|
|
'email': 'service.sa@kone.com',
|
|
'phone': '+966 11 465 0123',
|
|
'address': 'Olaya District, Riyadh, Saudi Arabia',
|
|
'license_number': 'LIC-20-67890',
|
|
'rating': Decimal('4.60')
|
|
},
|
|
{
|
|
'name': 'Al Jazirah Cleaning Services',
|
|
'vendor_type': 'cleaning',
|
|
'contact_person': 'Ahmed Al-Juhani',
|
|
'email': 'info@aljaziraservices.com',
|
|
'phone': '+966 11 482 3456',
|
|
'address': 'Malaz District, Riyadh, Saudi Arabia',
|
|
'license_number': 'LIC-22-78901',
|
|
'rating': Decimal('3.90')
|
|
},
|
|
{
|
|
'name': 'Saudi Security Systems',
|
|
'vendor_type': 'security',
|
|
'contact_person': 'Majid Al-Otaibi',
|
|
'email': 'info@saudisecurity.com.sa',
|
|
'phone': '+966 12 665 4321',
|
|
'address': 'Palestine Street, Jeddah, Saudi Arabia',
|
|
'license_number': 'LIC-21-89012',
|
|
'rating': Decimal('4.40')
|
|
},
|
|
{
|
|
'name': 'Dar Al-Handasah Consultants',
|
|
'vendor_type': 'maintenance',
|
|
'contact_person': 'Saad Al-Shaikh',
|
|
'email': 'riyadh@dargroup.com',
|
|
'phone': '+966 11 464 5678',
|
|
'address': 'King Faisal Foundation Building, Riyadh, Saudi Arabia',
|
|
'license_number': 'LIC-18-90123',
|
|
'rating': Decimal('4.70')
|
|
},
|
|
{
|
|
'name': 'Al-Yamama Plumbing & Water Systems',
|
|
'vendor_type': 'plumbing',
|
|
'contact_person': 'Fahad Al-Yamani',
|
|
'email': 'service@alyamama.com.sa',
|
|
'phone': '+966 13 845 6789',
|
|
'address': 'King Abdulaziz Road, Dammam, Saudi Arabia',
|
|
'license_number': 'LIC-20-01234',
|
|
'rating': Decimal('4.10')
|
|
},
|
|
{
|
|
'name': 'Saudi Fire Protection Co.',
|
|
'vendor_type': 'maintenance',
|
|
'contact_person': 'Ali Al-Qahtani',
|
|
'email': 'info@saudifire.com',
|
|
'phone': '+966 11 482 9012',
|
|
'address': 'Industrial City, Riyadh, Saudi Arabia',
|
|
'license_number': 'LIC-19-12345',
|
|
'rating': Decimal('4.50')
|
|
},
|
|
]
|
|
|
|
vendors = []
|
|
for data in vendors_data:
|
|
# Add insurance data
|
|
data['insurance_policy'] = f"INS-{random.randint(10000, 99999)}"
|
|
data['insurance_expiry'] = timezone.now().date() + timedelta(days=random.randint(30, 730))
|
|
data['total_contracts'] = random.randint(3, 25)
|
|
data['is_active'] = True
|
|
|
|
vendor, created = Vendor.objects.get_or_create(
|
|
name=data['name'],
|
|
defaults=data
|
|
)
|
|
vendors.append(vendor)
|
|
|
|
return vendors
|
|
|
|
|
|
def create_service_contracts(buildings, vendors, users):
|
|
"""Create service contracts"""
|
|
contracts = []
|
|
|
|
# Match vendors with appropriate buildings
|
|
for vendor in vendors:
|
|
# How many contracts should this vendor have
|
|
num_contracts = random.randint(1, 3)
|
|
|
|
for _ in range(num_contracts):
|
|
# Select a building
|
|
building = random.choice(buildings)
|
|
|
|
# Contract duration 1-3 years
|
|
duration_days = random.randint(365, 365 * 3)
|
|
start_date = timezone.now().date() - timedelta(days=random.randint(0, 730))
|
|
end_date = start_date + timedelta(days=duration_days)
|
|
|
|
# Determine contract status
|
|
if end_date < timezone.now().date():
|
|
status = 'expired'
|
|
elif start_date > timezone.now().date():
|
|
status = 'draft'
|
|
else:
|
|
status = 'active'
|
|
|
|
# Contract value depends on vendor type and building size
|
|
if vendor.vendor_type == 'cleaning':
|
|
value_base = 50000
|
|
elif vendor.vendor_type in ['hvac', 'electrical', 'maintenance']:
|
|
value_base = 200000
|
|
else:
|
|
value_base = 100000
|
|
|
|
# Adjust for building size
|
|
if building.total_area_sqm:
|
|
size_factor = float(building.total_area_sqm) / 50000.0
|
|
size_factor = min(max(size_factor, 0.5), 5.0) # Limit factor range
|
|
else:
|
|
size_factor = 1.0
|
|
|
|
contract_value = Decimal(str(value_base * size_factor * random.uniform(0.8, 1.2)))
|
|
|
|
# Generate contract title
|
|
if vendor.vendor_type == 'hvac':
|
|
title = f"{building.name} HVAC Maintenance Contract"
|
|
service_areas = "All HVAC systems including chillers, AHUs, and ventilation systems"
|
|
elif vendor.vendor_type == 'electrical':
|
|
title = f"{building.name} Electrical Systems Service Contract"
|
|
service_areas = "Electrical distribution systems, lighting, and power backup systems"
|
|
elif vendor.vendor_type == 'cleaning':
|
|
title = f"{building.name} Cleaning Services Contract"
|
|
service_areas = "All public areas, offices, and restrooms"
|
|
elif vendor.vendor_type == 'security':
|
|
title = f"{building.name} Security Systems Maintenance"
|
|
service_areas = "CCTV, access control, and security systems"
|
|
elif vendor.vendor_type == 'plumbing':
|
|
title = f"{building.name} Plumbing and Water Systems Maintenance"
|
|
service_areas = "Water supply, drainage, and sanitary systems"
|
|
else:
|
|
title = f"{building.name} General Maintenance Services"
|
|
service_areas = "General building systems and equipment"
|
|
|
|
# Generate contract number
|
|
contract_number = f"SC-{building.code}-{vendor.vendor_type.upper()[:3]}-{random.randint(1000, 9999)}"
|
|
|
|
# Manager depends on contract type
|
|
if vendor.vendor_type == 'hvac':
|
|
manager = users['fahad_hvac']
|
|
elif vendor.vendor_type == 'electrical':
|
|
manager = users['khalid_elec']
|
|
elif vendor.vendor_type == 'security':
|
|
manager = users['saad_safety']
|
|
else:
|
|
manager = users['mohammed_fm']
|
|
|
|
if not ServiceContract.objects.filter(contract_number=contract_number).exists():
|
|
contract = ServiceContract.objects.create(
|
|
contract_number=contract_number,
|
|
vendor=vendor,
|
|
title=title,
|
|
description=f"Service contract with {vendor.name} for {building.name}",
|
|
start_date=start_date,
|
|
end_date=end_date,
|
|
contract_value=contract_value,
|
|
payment_terms=random.choice(['Net 30', 'Net 45', 'Net 60']),
|
|
service_areas=service_areas,
|
|
status=status,
|
|
auto_renewal=random.choice([True, False]),
|
|
renewal_notice_days=30,
|
|
contract_manager=manager,
|
|
notes=""
|
|
)
|
|
|
|
# Add buildings to contract
|
|
contract.buildings.add(building)
|
|
|
|
contracts.append(contract)
|
|
|
|
return contracts
|
|
|
|
|
|
def create_inspections(buildings, users):
|
|
"""Create hospital inspections (CBAHI/SFDA/etc.)."""
|
|
inspections = []
|
|
insp_types = [
|
|
{'type':'safety', 'title_prefix':'Safety Compliance Inspection', 'inspector':'saad_safety', 'org':'Saudi Civil Defense'},
|
|
{'type':'fire', 'title_prefix':'Fire Safety Systems Inspection', 'inspector':'saad_safety', 'org':'Saudi Civil Defense Fire Dept'},
|
|
{'type':'infection', 'title_prefix':'Infection Control Audit', 'inspector':'aisha_ops', 'org':'Ministry of Health - Infection Control'},
|
|
{'type':'biomed', 'title_prefix':'Biomedical Equipment Audit', 'inspector':'ibrahim_bme', 'org':'Saudi Food & Drug Authority (SFDA)'},
|
|
{'type':'cbahi', 'title_prefix':'CBAHI Accreditation Survey', 'inspector':'aisha_ops', 'org':'CBAHI'},
|
|
{'type':'radiation', 'title_prefix':'Radiation Safety Inspection', 'inspector':'ibrahim_bme', 'org':'Radiation Protection - KSA'},
|
|
{'type':'environment', 'title_prefix':'Environmental Health Inspection', 'inspector':'aisha_ops', 'org':'Saudi Environmental Agency'},
|
|
{'type':'electrical', 'title_prefix':'Electrical Systems Safety Inspection', 'inspector':'khalid_elec', 'org':'Saudi Electrical Engineering Dept'},
|
|
{'type':'hvac', 'title_prefix':'HVAC Performance Audit', 'inspector':'fahad_hvac', 'org':'Saudi HVAC Association'},
|
|
]
|
|
|
|
for b in buildings:
|
|
for _ in range(random.randint(2, 5)):
|
|
t = random.choice(insp_types)
|
|
scheduled_date = timezone.now() - timedelta(days=random.randint(1, 180))
|
|
if scheduled_date > timezone.now():
|
|
status = 'scheduled'; started_date = None; completed_date = None
|
|
elif scheduled_date + timedelta(days=1) > timezone.now():
|
|
status = 'in_progress'; started_date = scheduled_date + timedelta(hours=random.randint(1,8)); completed_date = None
|
|
else:
|
|
status = 'completed'; started_date = scheduled_date + timedelta(hours=random.randint(1,4)); completed_date = started_date + timedelta(hours=random.randint(2,8))
|
|
|
|
inspection_id = f"INS-{timezone.now().strftime('%Y%m%d')}-{random.randint(1000, 9999)}"
|
|
if not Inspection.objects.filter(inspection_id=inspection_id).exists():
|
|
insp = Inspection.objects.create(
|
|
inspection_id=inspection_id, inspection_type=t['type'],
|
|
title=f"{t['title_prefix']} - {b.name}",
|
|
description=f"Comprehensive {t['type']} inspection of {b.name}",
|
|
building=b, scheduled_date=scheduled_date,
|
|
estimated_duration_hours=Decimal(str(random.uniform(2.0, 8.0))),
|
|
inspector=users[t['inspector']], inspector_external=f"Inspector from {t['org']}",
|
|
inspector_organization=t['org'], status=status,
|
|
started_date=started_date, completed_date=completed_date
|
|
)
|
|
if status == 'completed':
|
|
overall = random.choices(['Pass','Conditional Pass','Fail'], weights=[0.7,0.2,0.1])[0]
|
|
insp.overall_rating = overall
|
|
if overall == 'Pass':
|
|
insp.findings = "Compliant with KSA regulations; minor recommendations."
|
|
insp.recommendations = "Maintain schedules; update logs as per CBAHI."
|
|
insp.requires_followup = False
|
|
elif overall == 'Conditional Pass':
|
|
insp.findings = "Mostly compliant; several items need attention within 30 days."
|
|
insp.recommendations = "Close findings within 30 days; submit action plan to CBAHI/SFDA."
|
|
insp.requires_followup = True
|
|
insp.followup_date = (completed_date + timedelta(days=30)).date()
|
|
else:
|
|
insp.findings = "Significant non-conformities detected."
|
|
insp.recommendations = "Immediate remediation and reinspection required."
|
|
insp.requires_followup = True
|
|
insp.followup_date = (completed_date + timedelta(days=14)).date()
|
|
insp.save()
|
|
|
|
# Scope (a few floors/rooms)
|
|
if b.floors.exists():
|
|
for fl in random.sample(list(b.floors.all()), min(3, b.floors.count())):
|
|
insp.floors.add(fl)
|
|
if fl.rooms.exists():
|
|
for r in random.sample(list(fl.rooms.all()), min(5, fl.rooms.count())):
|
|
insp.rooms.add(r)
|
|
inspections.append(insp)
|
|
return inspections
|
|
|
|
|
|
def create_energy_meters(buildings, users):
|
|
"""Create energy meters"""
|
|
meters = []
|
|
|
|
# Create different types of meters for each building
|
|
for building in buildings:
|
|
# Electricity meters
|
|
meter_id = f"{building.code}-ELEC-01"
|
|
if not EnergyMeter.objects.filter(meter_id=meter_id).exists():
|
|
elec_meter = EnergyMeter.objects.create(
|
|
meter_id=meter_id,
|
|
meter_type='electricity',
|
|
building=building,
|
|
location_description="Main Electrical Room",
|
|
manufacturer=random.choice(["Schneider Electric", "ABB", "Siemens", "GE", "Eaton"]),
|
|
model=f"PowerLogic {random.randint(1000, 9999)}",
|
|
serial_number=f"E{random.randint(100000, 999999)}",
|
|
installation_date=timezone.now().date() - timedelta(days=random.randint(365, 3650)),
|
|
current_reading=Decimal(str(random.uniform(50000, 5000000))),
|
|
last_reading_date=timezone.now() - timedelta(days=random.randint(1, 30)),
|
|
is_active=True,
|
|
calibration_date=timezone.now().date() - timedelta(days=random.randint(30, 365)),
|
|
next_calibration_date=timezone.now().date() + timedelta(days=random.randint(30, 365))
|
|
)
|
|
meters.append(elec_meter)
|
|
|
|
# Water meter
|
|
meter_id = f"{building.code}-WATER-01"
|
|
if not EnergyMeter.objects.filter(meter_id=meter_id).exists():
|
|
water_meter = EnergyMeter.objects.create(
|
|
meter_id=meter_id,
|
|
meter_type='water',
|
|
building=building,
|
|
location_description="Main Water Supply Room",
|
|
manufacturer=random.choice(["Zenner", "Itron", "Kamstrup", "Elster", "Badger Meter"]),
|
|
model=f"FlowMaster {random.randint(100, 999)}",
|
|
serial_number=f"W{random.randint(100000, 999999)}",
|
|
installation_date=timezone.now().date() - timedelta(days=random.randint(365, 3650)),
|
|
current_reading=Decimal(str(random.uniform(5000, 500000))),
|
|
last_reading_date=timezone.now() - timedelta(days=random.randint(1, 30)),
|
|
is_active=True,
|
|
calibration_date=timezone.now().date() - timedelta(days=random.randint(30, 365)),
|
|
next_calibration_date=timezone.now().date() + timedelta(days=random.randint(30, 365))
|
|
)
|
|
meters.append(water_meter)
|
|
|
|
# Add gas meter for buildings with HVAC systems
|
|
if building.building_type in ['terminal', 'office', 'maintenance']:
|
|
meter_id = f"{building.code}-GAS-01"
|
|
if not EnergyMeter.objects.filter(meter_id=meter_id).exists():
|
|
gas_meter = EnergyMeter.objects.create(
|
|
meter_id=meter_id,
|
|
meter_type='gas',
|
|
building=building,
|
|
location_description="Gas Supply Room",
|
|
manufacturer=random.choice(["Elster", "Itron", "Dresser", "Actaris", "Sensus"]),
|
|
model=f"GasTracker {random.randint(100, 999)}",
|
|
serial_number=f"G{random.randint(100000, 999999)}",
|
|
installation_date=timezone.now().date() - timedelta(days=random.randint(365, 3650)),
|
|
current_reading=Decimal(str(random.uniform(1000, 100000))),
|
|
last_reading_date=timezone.now() - timedelta(days=random.randint(1, 30)),
|
|
is_active=True,
|
|
calibration_date=timezone.now().date() - timedelta(days=random.randint(30, 365)),
|
|
next_calibration_date=timezone.now().date() + timedelta(days=random.randint(30, 365))
|
|
)
|
|
meters.append(gas_meter)
|
|
|
|
# Add chilled water meter for large terminals
|
|
if building.building_type == 'terminal' and building.total_area_sqm and building.total_area_sqm > Decimal(
|
|
'100000'):
|
|
meter_id = f"{building.code}-CHW-01"
|
|
if not EnergyMeter.objects.filter(meter_id=meter_id).exists():
|
|
chw_meter = EnergyMeter.objects.create(
|
|
meter_id=meter_id,
|
|
meter_type='chilled_water',
|
|
building=building,
|
|
location_description="Chiller Plant Room",
|
|
manufacturer=random.choice(["Kamstrup", "Siemens", "Danfoss", "Honeywell", "Belimo"]),
|
|
model=f"CoolTrack {random.randint(100, 999)}",
|
|
serial_number=f"C{random.randint(100000, 999999)}",
|
|
installation_date=timezone.now().date() - timedelta(days=random.randint(365, 3650)),
|
|
current_reading=Decimal(str(random.uniform(10000, 1000000))),
|
|
last_reading_date=timezone.now() - timedelta(days=random.randint(1, 30)),
|
|
is_active=True,
|
|
calibration_date=timezone.now().date() - timedelta(days=random.randint(30, 365)),
|
|
next_calibration_date=timezone.now().date() + timedelta(days=random.randint(30, 365))
|
|
)
|
|
meters.append(chw_meter)
|
|
|
|
return meters
|
|
|
|
|
|
def create_energy_readings(meters, users):
|
|
"""Create historical energy readings"""
|
|
readings = []
|
|
|
|
# Create 12 months of monthly readings for each meter
|
|
for meter in meters:
|
|
prev_reading = meter.current_reading * Decimal('0.7') # Start at 70% of current
|
|
|
|
for month in range(12, 0, -1):
|
|
reading_date = timezone.now() - timedelta(days=30 * month + random.randint(0, 5))
|
|
|
|
# Calculate consumption growth
|
|
# Summer months (May-Sept) have higher consumption in Saudi Arabia
|
|
month_num = reading_date.month
|
|
if meter.meter_type == 'electricity' and 5 <= month_num <= 9:
|
|
growth_factor = random.uniform(1.05, 1.15) # Higher consumption in summer for AC
|
|
elif meter.meter_type == 'water' and 5 <= month_num <= 9:
|
|
growth_factor = random.uniform(1.03, 1.08) # Higher water use in summer
|
|
else:
|
|
growth_factor = random.uniform(0.95, 1.05) # Normal variation
|
|
|
|
# Create reading
|
|
new_reading = prev_reading * Decimal(str(growth_factor))
|
|
consumption = new_reading - prev_reading
|
|
|
|
# Calculate cost based on meter type and consumption
|
|
if meter.meter_type == 'electricity':
|
|
unit_cost = Decimal('0.18') # SAR per kWh
|
|
elif meter.meter_type == 'water':
|
|
unit_cost = Decimal('2.5') # SAR per cubic meter
|
|
elif meter.meter_type == 'gas':
|
|
unit_cost = Decimal('0.9') # SAR per cubic meter
|
|
else:
|
|
unit_cost = Decimal('0.15') # SAR per kWh equivalent
|
|
|
|
cost = consumption * unit_cost
|
|
|
|
# Check if reading already exists
|
|
if not EnergyReading.objects.filter(meter=meter, reading_date=reading_date).exists():
|
|
# Create the reading
|
|
reading = EnergyReading.objects.create(
|
|
meter=meter,
|
|
reading_date=reading_date,
|
|
reading_value=new_reading,
|
|
consumption=consumption,
|
|
cost=cost,
|
|
read_by=users['nora_admin'],
|
|
is_estimated=False,
|
|
notes=""
|
|
)
|
|
|
|
readings.append(reading)
|
|
|
|
prev_reading = new_reading
|
|
|
|
return readings
|
|
|
|
|
|
def create_space_reservations(rooms, users):
|
|
"""Create space reservations (conference/training/sim labs/clinics)."""
|
|
reservations = []
|
|
|
|
reservable = [r for r in rooms if r.room_type in ['meeting_room','consult_room','clinic_room','other','prayer_room']]
|
|
titles = [
|
|
'اجتماع الجودة السريرية (Clinical Quality Meeting)',
|
|
'لجنة مكافحة العدوى (Infection Control Committee)',
|
|
'مؤتمر علمي (Scientific Conference)',
|
|
'تدريب الطوارئ (ED Training)',
|
|
'محاكاة غرفة العمليات (OR Simulation)',
|
|
'اجتماع إدارة الأسرة (Bed Management Meeting)',
|
|
'اجتماع شهري (Monthly Meeting)',
|
|
'مراجعة مؤشرات الأداء (KPI Review)'
|
|
]
|
|
|
|
for i in range(50):
|
|
room = random.choice(reservable) if reservable else random.choice(rooms)
|
|
is_past = random.random() < 0.6
|
|
if is_past:
|
|
start = timezone.now() - timedelta(days=random.randint(1,90), hours=random.randint(1,8))
|
|
duration = random.randint(1,4)
|
|
end = start + timedelta(hours=duration)
|
|
status = random.choices(['completed','no_show','cancelled'], weights=[0.8,0.1,0.1])[0]
|
|
else:
|
|
start = timezone.now() + timedelta(days=random.randint(1,60), hours=random.randint(1,8))
|
|
duration = random.randint(1,4)
|
|
end = start + timedelta(hours=duration)
|
|
status = random.choices(['confirmed','pending'], weights=[0.7,0.3])[0]
|
|
|
|
reserved_by = random.choice(list(users.values()))
|
|
reservation_id = f"RES-{timezone.now().strftime('%Y%m%d')}-{i+1:04d}"
|
|
|
|
if not SpaceReservation.objects.filter(reservation_id=reservation_id).exists():
|
|
r = SpaceReservation.objects.create(
|
|
reservation_id=reservation_id, room=room,
|
|
title=random.choice(titles), description="Hospital meeting/training reservation",
|
|
start_datetime=start, end_datetime=end, reserved_by=reserved_by,
|
|
contact_person=f"{reserved_by.first_name} {reserved_by.last_name}",
|
|
contact_email=reserved_by.email,
|
|
contact_phone="+966 5" + str(random.randint(10000000, 99999999)),
|
|
expected_attendees=random.randint(4, 30),
|
|
setup_requirements=random.choice(["Standard setup", "U-shape arrangement", "Theater style", ""]),
|
|
catering_required=random.choice([True, False]),
|
|
av_equipment_required=True,
|
|
status=status,
|
|
hourly_rate=Decimal(str(random.randint(50, 250))) if room.room_type == 'meeting_room' else None,
|
|
notes=""
|
|
)
|
|
if status == 'confirmed':
|
|
r.approved_by = users['nora_admin']
|
|
r.approved_at = start - timedelta(days=random.randint(1,10))
|
|
r.save()
|
|
reservations.append(r)
|
|
return reservations
|
|
|
|
|
|
@transaction.atomic
|
|
def main():
|
|
"""Generate all sample data"""
|
|
print("Generating KSA facility management data...")
|
|
|
|
users, users_dict = create_users()
|
|
print("✓ Created users")
|
|
|
|
buildings = create_buildings(users_dict)
|
|
print("✓ Created buildings")
|
|
|
|
floors = create_floors(buildings)
|
|
print("✓ Created floors")
|
|
|
|
rooms = create_rooms(floors)
|
|
print("✓ Created rooms")
|
|
|
|
asset_categories = create_asset_categories()
|
|
print("✓ Created asset categories")
|
|
|
|
assets = create_assets(buildings, floors, rooms, asset_categories, users_dict)
|
|
print("✓ Created assets")
|
|
|
|
maintenance_types = create_maintenance_types()
|
|
print("✓ Created maintenance types")
|
|
|
|
maintenance_requests = create_maintenance_requests(buildings, floors, rooms, assets, maintenance_types, users_dict)
|
|
print("✓ Created maintenance requests")
|
|
|
|
maintenance_schedules = create_maintenance_schedules(assets, maintenance_types, users_dict)
|
|
print("✓ Created maintenance schedules")
|
|
|
|
vendors = create_vendors()
|
|
print("✓ Created vendors")
|
|
|
|
service_contracts = create_service_contracts(buildings, vendors, users_dict)
|
|
print("✓ Created service contracts")
|
|
|
|
inspections = create_inspections(buildings, users_dict)
|
|
print("✓ Created inspections")
|
|
|
|
energy_meters = create_energy_meters(buildings, users_dict)
|
|
print("✓ Created energy meters")
|
|
|
|
energy_readings = create_energy_readings(energy_meters, users_dict)
|
|
print("✓ Created energy readings")
|
|
|
|
space_reservations = create_space_reservations(rooms, users_dict)
|
|
print("✓ Created space reservations")
|
|
|
|
print("Data generation complete!")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |