= DatabaseCreation = == Имплементација на базата == Релационата база на податоци е имплементирана во PostgreSQL според претходно дефинираниот релационен модел. Структурата на базата е дефинирана во: * [attachment:ddl.sql ddl.sql] За генерирање на податоците кои се користат при пополнување и тестирање на базата се користи: * [attachment:seed_generator.py seed_generator.py] == Генерирање и внесување на податоци == Податоците се генерираат со Python скрипта и се запишуваат во CSV датотеки кои одговараат на табелите од базата. За генерирањето се користи `Faker`, при што е поставен фиксен seed за резултатите да можат повторно да се репродуцираат: {{{#!python SEED = 42 fake = Faker() Faker.seed(SEED) random.seed(SEED) }}} За запишување на генерираните записи се користи заедничка функција: {{{#!python @contextmanager def csv_writer(filename: str, fieldnames: list): path = OUT_DIR / filename with path.open("w", newline="", encoding="utf-8") as f: w = csv.DictWriter(f, fieldnames=fieldnames) w.writeheader() yield w }}} == Основни податоци == Најпрво се генерираат податоците за табелите од кои зависат останатите записи. На пример, статусите на проектите се дефинирани на следниот начин: {{{#!python STATUSES = [ "Draft", "In Progress", "On Hold", "Completed", "Cancelled", "Under Review" ] write_csv( "project_status.csv", ["status_id", "status_name"], [ {"status_id": i + 1, "status_name": s} for i, s in enumerate(STATUSES) ], ) }}} На сличен начин се генерираат и `Role`, `Permission`, `Industry`, `Technology`, `Rating_Dimension` и `Subscription_Tier`. == Клиенти и продавачи == Записите за клиентите и продавачите се генерираат со реалистични имиња, веб страници и контакт информации: {{{#!python write_csv( "vendor.csv", ["vendor_id", "agency_name", "website"], [ { "vendor_id": i + 1, "agency_name": fake.company(), "website": fake.url() } for i in range(NUM_VENDORS) ], ) write_csv( "client.csv", ["client_id", "industry_id", "company_name", "contact_email"], [ { "client_id": i + 1, "industry_id": random.choice(industry_ids), "company_name": fake.company(), "contact_email": fake.company_email(), } for i in range(NUM_CLIENTS) ], ) }}} `industry_id` се избира од претходно генерираните индустрии, со што се почитува врската помеѓу `Client` и `Industry`. == Корисници == За различните типови на корисници се генерираат заедничките податоци од табелата `User`: {{{#!python user_rows.append( { "user_id": uid, "type": user_type, "first_name": fake.first_name(), "last_name": fake.last_name(), "email": fake.unique.email(), "password_hash": hashlib.sha256( fake.password().encode() ).hexdigest(), "is_active": random.random() > 0.25, "last_login_at": nullable(rand_ts(1), 0.30), "created_at": rand_ts(3), "updated_at": rand_ts(1), } ) }}} Потоа корисниците се поврзуваат со соодветниот клиент, продавач или management улога: {{{#!python write_csv( "client_user.csv", ["user_id", "client_id"], [ { "user_id": u, "client_id": random.choice(client_ids) } for u in client_user_ids ], ) }}} == Проекти == При генерирање на проект се користат веќе постоечки договори и проектни статуси: {{{#!python w.writerow( { "project_id": i + 1, "contract_id": random.choice(contract_ids), "status_id": random.choice(status_ids), "project_name": fake.catch_phrase(), "start_date": sd, "end_date": ed, "budget": round(random.uniform(1_000, 250_000), 2), } ) }}} Со ова секој проект е поврзан со валиден `Client_Vendor_Contract` и `Project_Status`. За many-to-many врската помеѓу проектите и технологиите се генерираат записи во `Project_Technology`: {{{#!python with csv_writer( "project_technology.csv", ["project_id", "technology_id"] ) as w: for pid in project_ids: for tid in random.sample( tech_ids, random.randint(1, 5) ): w.writerow( { "project_id": pid, "technology_id": tid } ) }}} == Рецензии и оценки == Рецензиите се поврзуваат со постоечки проект и клиентски корисник: {{{#!python w.writerow( { "review_id": rid, "project_id": pid, "client_user_id": random.choice(client_user_ids), "review_date": rand_date(0, 3), "summary_text": fake.paragraph(nb_sentences=3), "is_published": random.random() > 0.30, } ) }}} Оценките за рецензиите се внесуваат посебно во `Review_Score`: {{{#!python with csv_writer( "review_score.csv", ["review_id", "dimension_id", "score_value"] ) as w: for rid in review_ids: for did in dim_ids: w.writerow( { "review_id": rid, "dimension_id": did, "score_value": random.randint(1, 5), } ) }}} На овој начин секоја оценка се поврзува со соодветна рецензија и димензија на оценување. == Историја и промени на податоците == За да се зачува историјата на буџетот на проектите се генерираат записи со стара и нова вредност: {{{#!python w.writerow( { "audit_id": audit_id, "project_id": pid, "old_budget": old_b, "new_budget": round( old_b * random.uniform(0.6, 1.6), 2 ), } ) }}} Исто така се генерира историја на промена на статусите преку `Project_Status_History`, како и податоци за `Dispute_Ticket`. == Полнење на базата == Скриптата генерира поголем број реалистични записи за различните делови од системот, вклучувајќи клиенти, продавачи, договори, проекти, технологии, рецензии и нивните зависни записи. Податоците се генерираат во редослед кој ги почитува foreign key зависностите помеѓу табелите. За поголемите табели се генерира доволно големо количество на податоци за тестирање на базата со реалистично оптоварување. Генерирањето се извршува со: {{{#!bash python3 seed_generator.py seed_data }}}