Changes between Version 1 and Version 2 of DatabaseCreation


Ignore:
Timestamp:
09/13/26 21:19:15 (2 weeks ago)
Author:
231075
Comment:

--

Legend:

Unmodified
Added
Removed
Modified
  • DatabaseCreation

    v1 v2  
    11= DatabaseCreation =
    22
    3 == Опис ==
    4 
    5 Во оваа фаза е имплементирана релационата база на податоци за VQEMS системот во PostgreSQL.
    6 
    7 Структурата на базата е дефинирана со DDL скриптата, додека податоците потребни за работа и тестирање на системот се генерираат со посебна скрипта.
    8 
    9 Основните датотеки се:
    10 
    11 * [attachment:ddl.sql ddl.sql] - дефиниција на табелите, клучевите и ограничувањата
    12 * [attachment:seed_generator.py seed_generator.py] - генерирање на податоци за пополнување на базата
    13 
    14 == DML - работа со податоците ==
    15 
    16 По креирањето на структурата на базата, потребно е табелите да се пополнат со податоци кои ќе овозможат реално тестирање на системот.
    17 
    18 Поради големиот број на потребни записи, податоците не се внесуваат рачно со поединечни `INSERT` наредби. Наместо тоа, се користи Python скрипта која автоматски генерира податоци за сите табели.
    19 
    20 Скриптата користи `Faker` за генерирање на реалистични вредности како:
    21 
    22 * имиња и презимиња
    23 * компании
    24 * email адреси
    25 * URL адреси
    26 * текстови за reviews и disputes
    27 * датуми
    28 * буџети
    29 * договори и проекти
    30 
    31 За да може истиот сет на податоци повторно да се генерира, се користи фиксен seed:
     3== Имплементација на базата ==
     4
     5Релационата база на податоци е имплементирана во PostgreSQL според претходно дефинираниот релационен модел.
     6
     7Структурата на базата е дефинирана во:
     8
     9* [attachment:ddl.sql ddl.sql]
     10
     11За генерирање на податоците кои се користат при пополнување и тестирање на базата се користи:
     12
     13* [attachment:seed_generator.py seed_generator.py]
     14
     15== Генерирање и внесување на податоци ==
     16
     17Податоците се генерираат со Python скрипта и се запишуваат во CSV датотеки кои одговараат на табелите од базата.
     18
     19За генерирањето се користи `Faker`, при што е поставен фиксен seed за резултатите да можат повторно да се репродуцираат:
    3220
    3321{{{#!python
    … …  
    3927}}}
    4028
    41 == Редослед на внесување на податоците ==
    42 
    43 Податоците се генерираат според зависностите помеѓу табелите, со цел да се почитуваат странските клучеви дефинирани во базата.
    44 
    45 Редоследот е приближно:
    46 
    47 1. lookup табели (`Role`, `Permission`, `Industry`, `Project_Status`, `Technology`, ...)
    48 2. `Vendor` и `Client`
    49 3. `User` и неговите специјализации
    50 4. `Vendor_Subscription`
    51 5. `Client_Vendor_Contract`
    52 6. `Project`
    53 7. `Project_Technology`
    54 8. `Project_Budget_Audit`
    55 9. `Project_Status_History`
    56 10. `Review`
    57 11. `Review_Score`
    58 12. `Dispute_Ticket`
    59 
    60 Со ова се обезбедува записите кои се референцираат преку foreign key да постојат пред да бидат употребени во зависните табели.
    61 
    62 == Пример - генерирање на Project записи ==
    63 
    64 За табелата `Project` се генерираат записи со договор, статус, име, почетен и краен датум и буџет:
    65 
    66 {{{#!python
    67 NUM_PROJECTS = 1_400_000
    68 
     29За запишување на генерираните записи се користи заедничка функција:
     30
     31{{{#!python
     32@contextmanager
     33def csv_writer(filename: str, fieldnames: list):
     34path = OUT_DIR / filename
     35with path.open("w", newline="", encoding="utf-8") as f:
     36w = csv.DictWriter(f, fieldnames=fieldnames)
     37w.writeheader()
     38yield w
     39}}}
     40
     41== Основни податоци ==
     42
     43Најпрво се генерираат податоците за табелите од кои зависат останатите записи. На пример, статусите на проектите се дефинирани на следниот начин:
     44
     45{{{#!python
     46STATUSES = [
     47"Draft",
     48"In Progress",
     49"On Hold",
     50"Completed",
     51"Cancelled",
     52"Under Review"
     53]
     54
     55write_csv(
     56"project_status.csv",
     57["status_id", "status_name"],
     58[
     59{"status_id": i + 1, "status_name": s}
     60for i, s in enumerate(STATUSES)
     61],
     62)
     63}}}
     64
     65На сличен начин се генерираат и `Role`, `Permission`, `Industry`, `Technology`, `Rating_Dimension` и `Subscription_Tier`.
     66
     67== Клиенти и продавачи ==
     68
     69Записите за клиентите и продавачите се генерираат со реалистични имиња, веб страници и контакт информации:
     70
     71{{{#!python
     72write_csv(
     73"vendor.csv",
     74["vendor_id", "agency_name", "website"],
     75[
     76{
     77"vendor_id": i + 1,
     78"agency_name": fake.company(),
     79"website": fake.url()
     80}
     81for i in range(NUM_VENDORS)
     82],
     83)
     84
     85write_csv(
     86"client.csv",
     87["client_id", "industry_id", "company_name", "contact_email"],
     88[
     89{
     90"client_id": i + 1,
     91"industry_id": random.choice(industry_ids),
     92"company_name": fake.company(),
     93"contact_email": fake.company_email(),
     94}
     95for i in range(NUM_CLIENTS)
     96],
     97)
     98}}}
     99
     100`industry_id` се избира од претходно генерираните индустрии, со што се почитува врската помеѓу `Client` и `Industry`.
     101
     102== Корисници ==
     103
     104За различните типови на корисници се генерираат заедничките податоци од табелата `User`:
     105
     106{{{#!python
     107user_rows.append(
     108{
     109"user_id": uid,
     110"type": user_type,
     111"first_name": fake.first_name(),
     112"last_name": fake.last_name(),
     113"email": fake.unique.email(),
     114"password_hash": hashlib.sha256(
     115fake.password().encode()
     116).hexdigest(),
     117"is_active": random.random() > 0.25,
     118"last_login_at": nullable(rand_ts(1), 0.30),
     119"created_at": rand_ts(3),
     120"updated_at": rand_ts(1),
     121}
     122)
     123}}}
     124
     125Потоа корисниците се поврзуваат со соодветниот клиент, продавач или management улога:
     126
     127{{{#!python
     128write_csv(
     129"client_user.csv",
     130["user_id", "client_id"],
     131[
     132{
     133"user_id": u,
     134"client_id": random.choice(client_ids)
     135}
     136for u in client_user_ids
     137],
     138)
     139}}}
     140
     141== Проекти ==
     142
     143При генерирање на проект се користат веќе постоечки договори и проектни статуси:
     144
     145{{{#!python
     146w.writerow(
     147{
     148"project_id": i + 1,
     149"contract_id": random.choice(contract_ids),
     150"status_id": random.choice(status_ids),
     151"project_name": fake.catch_phrase(),
     152"start_date": sd,
     153"end_date": ed,
     154"budget": round(random.uniform(1_000, 250_000), 2),
     155}
     156)
     157}}}
     158
     159Со ова секој проект е поврзан со валиден `Client_Vendor_Contract` и `Project_Status`.
     160
     161За many-to-many врската помеѓу проектите и технологиите се генерираат записи во `Project_Technology`:
     162
     163{{{#!python
    69164with csv_writer(
    70 "project.csv",
    71 [
    72 "project_id",
    73 "contract_id",
    74 "status_id",
    75 "project_name",
    76 "start_date",
    77 "end_date",
    78 "budget",
    79 ],
     165"project_technology.csv",
     166["project_id", "technology_id"]
    80167) as w:
    81 for i in range(NUM_PROJECTS):
    82 sd = rand_date(0, 4)
    83 
    84 ```
    85     ed = (
    86         sd + timedelta(days=random.randint(30, 730))
    87         if random.random() > 0.2
    88         else None
    89     )
    90 
    91     w.writerow(
    92         {
    93             "project_id": i + 1,
    94             "contract_id": random.choice(contract_ids),
    95             "status_id": random.choice(status_ids),
    96             "project_name": fake.catch_phrase(),
    97             "start_date": sd,
    98             "end_date": ed,
    99             "budget": round(random.uniform(1_000, 250_000), 2),
    100         }
    101     )
    102 ```
    103 
    104 }}}
    105 
    106 При генерирањето, `contract_id` и `status_id` се избираат од веќе постоечките идентификатори, со што се задржува референцијалниот интегритет на податоците.
    107 
    108 == Пример - Reviews и Review Scores ==
    109 
    110 За дел од проектите се генерира review:
    111 
    112 {{{#!python
    113 reviewed_pids = random.sample(
    114 range(1, NUM_PROJECTS + 1),
    115 k=int(NUM_PROJECTS * 0.70)
    116 )
    117 }}}
    118 
    119 За секој review потоа се генерира оценка за секоја дефинирана димензија:
    120 
    121 {{{#!python
     168for pid in project_ids:
     169for tid in random.sample(
     170tech_ids,
     171random.randint(1, 5)
     172):
     173w.writerow(
     174{
     175"project_id": pid,
     176"technology_id": tid
     177}
     178)
     179}}}
     180
     181== Рецензии и оценки ==
     182
     183Рецензиите се поврзуваат со постоечки проект и клиентски корисник:
     184
     185{{{#!python
     186w.writerow(
     187{
     188"review_id": rid,
     189"project_id": pid,
     190"client_user_id": random.choice(client_user_ids),
     191"review_date": rand_date(0, 3),
     192"summary_text": fake.paragraph(nb_sentences=3),
     193"is_published": random.random() > 0.30,
     194}
     195)
     196}}}
     197
     198Оценките за рецензиите се внесуваат посебно во `Review_Score`:
     199
     200{{{#!python
     201with csv_writer(
     202"review_score.csv",
     203["review_id", "dimension_id", "score_value"]
     204) as w:
    122205for rid in review_ids:
    123206for did in dim_ids:
    … …  
    131214}}}
    132215
    133 На овој начин еден review има повеќе оценки, согласно моделот `Review` - `Review_Score` - `Rating_Dimension`.
    134 
    135 == Пример - врски помеѓу проект и технологии ==
    136 
    137 Еден проект може да користи повеќе технологии. За секој проект случајно се избираат од една до пет технологии:
    138 
    139 {{{#!python
    140 for pid in project_ids:
    141 for tid in random.sample(tech_ids, random.randint(1, 5)):
    142 w.writerow(
    143 {
     216На овој начин секоја оценка се поврзува со соодветна рецензија и димензија на оценување.
     217
     218== Историја и промени на податоците ==
     219
     220За да се зачува историјата на буџетот на проектите се генерираат записи со стара и нова вредност:
     221
     222{{{#!python
     223w.writerow(
     224{
     225"audit_id": audit_id,
    144226"project_id": pid,
    145 "technology_id": tid
    146 }
    147 )
    148 }}}
    149 
    150 Со ова се пополнува many-to-many релацијата `Project_Technology`.
    151 
    152 == Генерирање на податоците ==
    153 
    154 Скриптата може да се стартува со:
     227"old_budget": old_b,
     228"new_budget": round(
     229old_b * random.uniform(0.6, 1.6),
     2302
     231),
     232}
     233)
     234}}}
     235
     236Исто така се генерира историја на промена на статусите преку `Project_Status_History`, како и податоци за `Dispute_Ticket`.
     237
     238== Полнење на базата ==
     239
     240Скриптата генерира поголем број реалистични записи за различните делови од системот, вклучувајќи клиенти, продавачи, договори, проекти, технологии, рецензии и нивните зависни записи.
     241
     242Податоците се генерираат во редослед кој ги почитува foreign key зависностите помеѓу табелите. За поголемите табели се генерира доволно големо количество на податоци за тестирање на базата со реалистично оптоварување.
     243
     244Генерирањето се извршува со:
    155245
    156246{{{#!bash
    157247python3 seed_generator.py seed_data
    158248}}}
    159 
    160 Како резултат се генерираат CSV датотеки за табелите во базата, кои потоа се користат за нејзино пополнување.
    161 
    162 Поголемите табели содржат голем број записи за да се симулира реална употреба на апликацијата. На пример:
    163 
    164 || '''Табела''' || '''Број на записи''' ||
    165 || Vendor || 5,000 ||
    166 || Client || 20,000 ||
    167 || User || 77,000 ||
    168 || Client_Vendor_Contract || 250,000 ||
    169 || Project || 1,400,000 ||
    170 || Review || 980,000 ||
    171 || Review_Score || 4,900,000 ||
    172 
    173 Покрај нив, `Project_Technology` и `Project_Status_History` генерираат повеќе записи за секој проект, со што и тие достигнуваат многу голем број на редици.
    174 
    175 == Заклучок ==
    176 
    177 Со DDL скриптата е дефинирана структурата на PostgreSQL базата, додека DML делот е поддржан преку автоматско генерирање на реалистични податоци за нејзино пополнување.
    178 
    179 Податоците ги следат врските и ограничувањата дефинирани во релациониот модел и обезбедуваат доволно голем dataset за понатамошно тестирање, работа со погледи и оптимизација на упити.