Reply Pilot Database

reply-pilot-db je samostatny PostgreSQL modul pro Reply Pilot. Nevystavuje REST/JSON API; vystavuje samotny PostgreSQL server na sdilene Docker siti reply-pilot-internal a na host bind ${HOST_DB_BIND}:${HOST_DB_PORT}.

Co modul resi

  • persistentni ulozeni databazovych souboru na host path mimo kontejner
  • PostgreSQL konfiguraci pres conf/postgresql.conf
  • logovani do logs/reply-pilot-db.log
  • verzovane Liquibase changelogy pres scripts/db-migrate.sh
  • automaticke spusteni Liquibase validate a update pri start a deploy

Persistencni host path

Nejdulesitejsi parametr je HOST_DATA_DIR v .env.local nebo .env.server. Tahle cesta urcuje, kam na hostu fyzicky spadnou PostgreSQL data. Proto data preziji restart kontejneru i serveru.

Uvnitř kontejneru se PostgreSQL cluster inicializuje do PGDATA=/app/data/postgresql. To je schvalne podadresar bind mountu, protoze oficialni PostgreSQL image nepodporuje initdb primo do rootu mountpointu.

Migrace

Liquibase pouziva vlastni tabulky:

  • public.databasechangelog
  • public.databasechangeloglock

Pri prvnim spusteni nad starsi databazi se puvodni public.schema_migrations automaticky prevede do Liquibase historie, aby se uz aplikovane zmeny nepoustely znovu. Nasledny changeset 0004 stary tracking table smaze.

Aktualni user a aplikacni konfiguracni tabulky jsou:

  • public.app_user
  • public.app_user_jira_profile
  • public.app_configuration

Tabulka app_user ma zatim:

  • id BIGINT GENERATED BY DEFAULT AS IDENTITY
  • login_key TEXT NOT NULL
  • auth_user_id TEXT NOT NULL DEFAULT ''
  • email TEXT NULL
  • display_name TEXT NOT NULL DEFAULT ''
  • gmal_box_link TEXT NOT NULL DEFAULT ''
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  • updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

Tabulka app_user_jira_profile drzi lokalni Jira assignment nastaveni pro aplikacniho uzivatele:

  • app_user_id BIGINT PRIMARY KEY REFERENCES public.app_user (id) ON DELETE CASCADE
  • jira_account_id TEXT NULL
  • jira_email TEXT NULL
  • jira_assignable BOOLEAN NOT NULL DEFAULT TRUE
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  • updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

jira_account_id je stabilni Atlassian identifikator uzivatele a ma prednost pri mapovani assignee z Jiry. Kdyz jira_email neni vyplneny, aplikace pro Jira lookup pouzije app_user.email. Migrace zalozi vychozi profil pro existujici uzivatele a aplikacni Jira assignee service pri nacitani seznamu zalozi chybejici profily pro nove uzivatele s jira_assignable = TRUE.

Tabulka app_configuration drzi singleton konfiguraci aplikace:

  • id SMALLINT PRIMARY KEY DEFAULT 1
  • default_email_sales_user_id BIGINT NULL REFERENCES public.app_user (id) ON DELETE SET NULL
  • created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  • updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

default_email_sales_user_id je globalni fallback obchodnik pro emailovou komunikaci, kdyz konkretni firma nema nastaveny vlastni party_organization.default_email_sales_user_id.

Dalsi migrace zavedly:

  • public.party
  • public.party_person
  • public.party_organization
  • public.party_outreach_policy
  • public.contact_mech
  • public.contact_mech_email
  • public.contact_mech_phone
  • public.party_contact_mech
  • public.party_relationship
  • public.activity_type
  • public.activity
  • public.activity_email
  • public.activity_email_attachment
  • public.activity_email_link
  • public.activity_call
  • public.activity_meeting
  • public.activity_note
  • public.activity_participant
  • public.task_type
  • public.task
  • public.task_jira
  • public.task_jira_email_thread
  • public.task_jira_work_type
  • public.task_jira_supplier_onboarding
  • public.task_jira_email_thread_reply
  • public.jira_sync_state
  • public.ai_prompt_type
  • public.ai_prompt
  • public.lead_import_batch
  • public.lead_import_item
  • public.requirement_type
  • public.requirement_state_type
  • public.requirement_evidence_source_type
  • public.requirement_evidence_verdict_type
  • public.party_requirement_state
  • public.party_requirement_evidence
  • public.party_requirement_eval_queue
  • public.party_feed_fact
  • public.party_supplier_identifier_fact

Legacy tabulky public.company, public.company_contact a public.communication_history byly po migraci na party/activity model odstranene changesetem 0010.

Treti changeset pridava:

  • public.simple_auth_nonce

Tuto tabulku pouziva reply-pilot-app pro replay protection simple-auth callbacku. Uklada pouzite rp_nonce a jejich expiraci.

Jedenacty changeset pridava task model:

  • public.task_type
  • public.task
  • public.task_jira
  • public.task_jira_email_thread
  • public.task_jira_work_type
  • public.task_jira_supplier_onboarding
  • public.task_jira_email_thread_reply

task je obecny root zaznam podobny activity. Aktualne ma jediny typ jira, ulozeny v task_type.

task_jira drzi Jira-specific pole a lokalni vazby:

  • jira_key
  • status
  • party_id
  • user_id
  • resolution
  • jira_work_type_code

jira_work_type_code rozlisuje lokalni cache pro Jira work item typu supplier_onboarding nebo email_thread_reply. Jira zustava source of truth pro workflow status, lokalni DB drzi work-type klasifikaci a vazby potrebne pro aplikacni workflow. Jira summary a description se v Reply Pilotu neukladaji; aplikace je smi pouze docasne nacist z Jiry pro aktualni request.

task_jira_supplier_onboarding je subtype tabulka pro supplier onboarding ticket. Vazba na firmu je pres task_jira.party_id; onboarding-only metadata feed_state_code a enlistment_table jsou ulozena tady, ne v base task_jira.

task_jira_email_thread_reply je subtype tabulka pro email-thread reply ticket. Vazba na firmu je pres task_jira.party_id, vazba na email thread pres external_thread_id.

task_jira_email_thread je historicka tabulka z puvodniho modelu. Novy kod pro reply work items pouziva task_jira_email_thread_reply.

Pozdejsi migrace rozsiruje task_jira o metadata lokalni Jira cache:

  • jira_updated_at
  • jira_synced_at
  • jira_sync_error
  • jira_assignee_account_id
  • jira_assignee_display_name
  • jira_assignee_email

Jira-owned pole jako status a assignee se periodicky synchronizuji z Jiry. Lokální DB je cache a Jira zustava primarni zdroj pravdy pro stav ticketu. Summary a description se zamerne nesynchronizuji ani neukladaji v lokalni DB. user_id zustava lokalni mapovani na app_user, zatimco jira_assignee_* drzi raw hodnoty z Jiry i pro uzivatele, ktere Reply Pilot lokalne nezna.

Dvanacty changeset pridava:

  • public.party_organization.show_by_default BOOLEAN NOT NULL DEFAULT TRUE

show_by_default urcuje, jestli se firma ma bezne zobrazovat ve standardnich listingu, odvozenych company lookupu a company search dokumentech. Primy detail firmy zustava pristupny i kdyz je hodnota nastavena na FALSE.

Pozdejsi migrace rozsiruje public.party_organization o:

  • default_email_sales_user_id BIGINT NULL REFERENCES public.app_user (id) ON DELETE SET NULL

Sloupec urcuje vychoziho obchodnika pro emailovou komunikaci konkretni firmy. Aplikace ho pouzije pri predvyplneni assignee email reply tasku, pokud je uzivatel porad dostupny v Jira assignable seznamu.

Trinacty changeset rozsiril public.task_jira o:

  • feed BOOLEAN NOT NULL DEFAULT FALSE
  • enlistment_table BOOLEAN NOT NULL DEFAULT FALSE

Oba atributy jsou lokalni metadata Jira tasku v Reply Pilotu. Pri vytvoreni tasku maji vychozi hodnotu FALSE a meni se v editaci tasku.

Ctrnacty changeset pridava CME company tracking:

  • public.party_cme_match
  • public.party_cme_check_queue

party_cme_match drzi posledni znamy vysledek porovnani firmy proti CME lookupu. Uklada technicky vysledek matchu (NO_MATCH, MATCHED, AMBIGUOUS, ERROR), CME integration_state, volitelny cme_dodavatel_id, dalsi metadata shody a cas posledni kontroly.

party_cme_check_queue je interní worker fronta pro firmy, ktere cekaji na CME check. Umoznuje periodicky zpracovavat jen nove nebo starnouci firmy misto full scanu cele tabulky party_organization.

Patnacty changeset nahrazuje puvodni task_jira.feed BOOLEAN stavovym kodem:

  • public.task_feed_state
  • public.task_jira.feed_state_code TEXT NOT NULL DEFAULT 'missing'

task_feed_state je lookup tabulka s internimi kody:

  • missing
  • verifying
  • available

Pri migraci se puvodni feed = FALSE prevadi na missing a feed = TRUE na available. Zobrazene ceske popisky zustavaji zalezitosti aplikace, ne DB schema, aby se daly menit v releasu bez prepisu ulozenych dat.

Sestnacty changeset pridava requirement tracking nad firmami:

  • public.requirement_type
  • public.requirement_state_type
  • public.requirement_evidence_source_type
  • public.requirement_evidence_verdict_type
  • public.party_requirement_state
  • public.party_requirement_evidence
  • public.party_requirement_eval_queue

requirement_type urcuje, jake onboardingove nebo dokumentacni pozadavky se u firmy sleduji, napr. ENLISTMENT_TABLE, PRICE_LIST, TOP10, FEED nebo BUSINESS_TERMS.

party_requirement_state drzi aktualni agregovany stav pozadavku pro jednu firmu. Umoznuje odlisit UNKNOWN, REQUESTED, CANDIDATE_RECEIVED, CONFIRMED_RECEIVED, REJECTED a NEEDS_REVIEW, plus volitelny manualni override.

party_requirement_evidence uklada jednotlive dukazy odvozene z emailu, threadu, prilohy nebo manualniho vstupu. Evidence muze odkazovat na konkretni activity_email zpravu pres activity_email_id, a protoze model zatim nema samostatnou DB entitu pro email thread ani attachment, uklada i external_thread_id, metadata prilohy a AI vysvetleni/confidence.

party_requirement_eval_queue je worker fronta pro prubezny prepocet agregovaneho stavu po novych emailech, novych prompt verzi nebo manualnich zasazich bez nutnosti full scanu celeho activity modelu.

Sedmnacty changeset pridava extracted facts z emailu:

  • public.party_feed_fact
  • public.party_supplier_identifier_fact

party_feed_fact uklada konkretni kandidátní feed hodnoty nalezene v jednom emailu nebo jeho priloze. Drzi URL, delivery mode, confidence a odkaz na zdrojovy activity_email; volitelne muze ukazovat i na party_requirement_evidence.

party_supplier_identifier_fact uklada konkretni kandidátní identifikatory dodavatele nalezene v jednom emailu nebo jeho priloze. Drzi identifier_type_code, raw/normalized hodnotu, confidence a vazbu na activity_email; volitelne muze ukazovat i na party_requirement_evidence.

Obe fact tabulky predstavuji vrstvu mezi raw email evidence a finalnim agregovanym stavem firmy. Finalni company atributy se i nadale pocitaji do party_requirement_state.

Osmnacty changeset pridava email artifact model navazany na activity_email:

  • public.activity_email_attachment
  • public.activity_email_link

activity_email_attachment uklada metadata priloh k jednomu importovanemu emailu. Je to DB source of truth pro attachment metadata, ale ne pro samotny binární obsah. Binarky zustavaji v existujici file cache modulu reply-pilot-be a relative_path slouzi jako bridge mezi DB a lokalnim storage.

activity_email_link uklada explicitni http(s) URL nalezene v body_text jednoho importovaneho emailu. Drzi raw i normalizovanou podobu, host, path a kratky kontext. Tato tabulka je source of truth pro dalsi feed/url extractory. activity_email.body_html uklada pouze dekodovane text/html MIME body casti, ne raw RFC822 email. body_text zustava source pro summary, search, AI kontext a extrakci odkazu.

Devatenacty changeset rozsiril finalni requirement projection vrstvu:

  • public.party_requirement_state.resolved_delivery_mode_code
  • public.party_requirement_state.resolved_value_type_code
  • public.party_requirement_state.resolved_value_raw
  • public.party_requirement_state.resolved_value_normalized
  • public.requirement_type novy kod SUPPLIER_IDENTIFIER

party_requirement_state tak nove umi drzet nejen finalni state_code, ale i propagovanou konkretni hodnotu vybranou agregatorem:

  • pro FEED finalni delivery_mode a pripadny finalni feed_url
  • pro SUPPLIER_IDENTIFIER finalni identifier_type a hodnotu
  • pro ENLISTMENT_TABLE zustavaji projekcni sloupce prazdne

Dvacity changeset pridava lead import a outreach policy vrstvu:

  • public.party_outreach_policy
  • public.lead_import_batch
  • public.lead_import_item

party_outreach_policy drzi explicitni obchodni rozhodnuti nad firmou, hlavne stav DO_NOT_CONTACT s lidskou poznamkou a vazbou na operatora.

lead_import_batch drzi auditni zaznam o jednom serverovem JSONL souboru, typicky pripravenem mimo Reply Pilot batch modulem reply-pilot-wholesale-scout. Uklada jmeno souboru, hash, cestu v backend storage a agregovany stav review.

lead_import_item drzi jednotlive kandidaty z daneho batch souboru. U kazde polozky uklada puvodni JSON payload, prefill match na existujici firmu, AI draft prvniho osloveni, operatorovu poznamku a vysledek rozhodnuti IMPORTED, DO_NOT_CONTACT nebo SKIPPED. Pri importu muze navazat i lokalni task zaznam a Jira key.

Dvacaty prvni changeset nahrazuje puvodni single-purpose prompt tabulku obecnym typed modelem:

  • public.ai_prompt_type
  • public.ai_prompt

ai_prompt_type je lookup tabulka pro workflow-specific seznamy promptu. Aktualne se seeduji tyto typy:

  • EMAIL_REPLAY
  • IMPORT_WIZARD
  • SUPPLIER_ONBOARDING

ai_prompt drzi konkretni predpripravene prompty. Kazdy radek ma povinnou vazbu na ai_prompt_type, zobrazene name, prompt_text a sort_order.

Pri migraci se puvodni obsah public.email_replay_ai_prompt automaticky presune do public.ai_prompt pod typ EMAIL_REPLAY a legacy tabulka se odstrani.

Stejny changeset rozsiri public.lead_import_item o:

  • ai_prompt_id
  • ai_prompt_snapshot

ai_prompt_id ukazuje na vybrany prompt pouzity pri generovani AI draftu v lead import wizardu. ai_prompt_snapshot uklada presne instrukce, ktere byly do AI requestu poslany, aby audit zustal stabilni i po pozdejsi uprave nebo smazani promptu.

Dvacaty druhy changeset odstranuje z public.ai_prompt legacy sloupec code; unikatnost promptu se dale neridi kodem, ale vazbou na typ a internim ID.

Dvacaty treti changeset pridava:

  • public.app_user_jira_profile

app_user_jira_profile oddeluje Simple Auth identitu v app_user od lokalniho Jira assignment profilu. jira_account_id je stabilni Jira/Atlassian account id pro mapovani assignee z Jiry. jira_email je volitelny override pro vyhledani Jira uzivatele; pokud chybi, pouzije se app_user.email. jira_assignable urcuje, jestli se uzivatel nabizi v dropdown seznamech pro prirazeni Jira tasku.

Dvacaty ctvrty changeset doplni vychozi app_user_jira_profile radky pro existujici uzivatele s jira_assignable = TRUE. Stejny default pro nove uzivatele pri nacitani assignee seznamu prubezne zajistuje aplikacni sluzba.

Dvacaty paty changeset pridava Jira task sync podporu:

  • public.task_jira.jira_updated_at
  • public.task_jira.jira_synced_at
  • public.task_jira.jira_sync_error
  • public.jira_sync_state

jira_sync_state drzi globalni stav periodicke synchronizace Jira tasku. Hlavni watermark je last_seen_jira_updated_at, tedy Jira updated cas, nikoli cas serveru Reply Pilotu. Incremental sync pouziva tento watermark minus konfigurovatelny safety overlap; pokud watermark chybi, job provede full sync projektovych Jira ticketu.

Dvacaty sesty changeset rozsiruje Jira task cache o raw assignee hodnoty z Jiry:

  • public.task_jira.jira_assignee_account_id
  • public.task_jira.jira_assignee_display_name
  • public.task_jira.jira_assignee_email

Tyto sloupce oddeluji skutecneho Jira assignee od lokalni vazby task_jira.user_id. Pokud Jira API vrati email, jira_assignee_email lze porovnat s app_user_jira_profile.jira_email; kdyz email kvuli Jira privacy chybi, UI muze porad zobrazit Jira display name nebo account id.

Dvacaty sedmy changeset pridava do public.app_user_jira_profile:

  • jira_account_id

Mapovani tasku na lokalniho uzivatele pri Jira syncu a filtr Moje ukoly porovnavaji nejdriv task_jira.jira_assignee_account_id proti app_user_jira_profile.jira_account_id; email zustava fallback pro pripady, kdy Jira email vraci.

Dvacaty osmy changeset rozdeluje lokalni Jira task cache podle work typu:

  • public.task_jira_work_type
  • public.task_jira.jira_work_type_code TEXT NOT NULL DEFAULT 'supplier_onboarding'
  • public.task_jira_supplier_onboarding
  • public.task_jira_email_thread_reply

Lookup task_jira_work_type ma hodnoty supplier_onboarding a email_thread_reply. Migrace backfilluje vsechny existujici task_jira radky na supplier_onboarding a zalozi odpovidajici subtype radky v task_jira_supplier_onboarding. Jira-side zmena existujicich work items z puvodniho Task na Supplier Onboarding se dela rucne v Jira Cloud pres bulk move.

Dvacaty devaty changeset presouva onboarding-only pole feed_state_code a enlistment_table z base tabulky task_jira do subtype tabulky task_jira_supplier_onboarding. Email Thread Reply tasky tato pole ve schematu nemaji; jejich workflow stav zustava Jira-owned task_jira.status cache.

Tricaty changeset pridava novy public.ai_prompt_type kod SUPPLIER_ONBOARDING pro predpripravene prompty pouzite pri generovani editovatelneho Jira summary a description pred zalozenim Supplier Onboarding ticketu z detailu firmy.

Tricaty druhy changeset pridava globalni public.app_configuration singleton a firemni party_organization.default_email_sales_user_id pro vychoziho obchodnika emailove komunikace.

Readonly preview user

Operacni skripty DB modulu umi po migracich sladit i volitelneho readonly PostgreSQL uzivatele pro nahled do DB. Konfigurace je v .env.local nebo .env.server:

  • DB_PREVIEW_USER_ENABLED
  • DB_PREVIEW_USER_NAME
  • DB_PREVIEW_USER_PASSWORD

Kdyz je DB_PREVIEW_USER_ENABLED=1, skripty vytvori nebo zapnou login a daji mu CONNECT na databazi, USAGE na public a SELECT na vsechny aktualni i budouci tabulky/sekvence v public. Kdyz je DB_PREVIEW_USER_ENABLED=0, skripty roli prepnou na NOLOGIN, takze jde v produkci vypnout jen nastavenim.

Lokalni start

cd reply-pilot-db
cp .env.example .env.local
./scripts/db-start.sh

db-start.sh po readiness checku automaticky spusti Liquibase validate a update. Stejne tak db-deploy.sh. Rucni migracni skript zustava zachovany a prime Liquibase prikazy lze volat pres ./scripts/db-liquibase.sh.

Pro host publikaci DB plati:

  • HOST_DB_BIND urcuje bind adresu na hostu; default je 127.0.0.1
  • HOST_DB_PORT urcuje host port; default je 5433
  • kdyz je HOST_DB_BIND=0.0.0.0, PostgreSQL je dostupna i mimo serverovy loopback

Ověření

./scripts/db-psql.sh -c '\dt public.*'
./scripts/db-psql.sh -c 'select id, author, filename, orderexecuted from public.databasechangelog order by orderexecuted;'
./scripts/db-liquibase.sh history
./scripts/db-liquibase.sh rollback-count 1

Opakovane spusteni ./scripts/db-migrate.sh musi nechat schema beze zmen. Kazdy migrations/*.sql soubor ma presne jeden Liquibase changeset a musi obsahovat explicitni --rollback.

Workflow při změně schema

Kdyz menis databazove schema:

  1. pridej Liquibase changeset do reply-pilot-db/migrations/
  2. dopln novy soubor do reply-pilot-db/migrations/db.changelog.xml
  3. zajisti explicitni --rollback
  4. over migraci pres:
cd reply-pilot-db
./scripts/db-liquibase.sh validate
./scripts/db-liquibase.sh update
./scripts/db-liquibase.sh rollback-count 1
./scripts/db-liquibase.sh update

Pokud se meni activity/contact data model, aktualizuj i docs/activity-model.md a přegeneruj souvisejici diagramy pres rp.